Compare commits
75 Commits
provider-c
...
v1.0.134
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73258c6193 | ||
|
|
dfe3fb8ed3 | ||
|
|
cd6bfb3f69 | ||
|
|
ba417d80b1 | ||
|
|
40eb8b93e1 | ||
|
|
6e6bd1e171 | ||
|
|
81ee2d2332 | ||
|
|
85974e9acd | ||
|
|
864c098701 | ||
|
|
cfbaf81ef8 | ||
|
|
87a791fdb9 | ||
|
|
ada7cca10d | ||
|
|
52db0f23a6 | ||
|
|
60388f7f03 | ||
|
|
53ed1c912b | ||
|
|
5f7ab83de4 | ||
|
|
05d2f70529 | ||
|
|
f950de95ba | ||
|
|
a4e5a72c36 | ||
|
|
03324d4277 | ||
|
|
e53580cb68 | ||
|
|
332ebe36c3 | ||
|
|
5013d64b28 | ||
|
|
767a81f930 | ||
|
|
78046dac8b | ||
|
|
95168b8267 | ||
|
|
c264e9c364 | ||
|
|
856e1e2948 | ||
|
|
bef4fdfc4b | ||
|
|
095a1ab041 | ||
|
|
71e578eac9 | ||
|
|
4380727727 | ||
|
|
392d46933b | ||
|
|
2bc0b46ff4 | ||
|
|
09f522f0aa | ||
|
|
d82bd430f6 | ||
|
|
49800a00bd | ||
|
|
f9dcd97936 | ||
|
|
d763c11a6d | ||
|
|
b1202ac6db | ||
|
|
d469d7d441 | ||
|
|
48dc520fb8 | ||
|
|
668d5a76d5 | ||
|
|
b9c1f10016 | ||
|
|
8a0c86cbdb | ||
|
|
7f86fe3f61 | ||
|
|
a32cf70d7e | ||
|
|
a607f33552 | ||
|
|
350a32274a | ||
|
|
27c99b46cb | ||
|
|
1d6e3d477b | ||
|
|
efbb973393 | ||
|
|
45bc7a6a9d | ||
|
|
088ebb967f | ||
|
|
bcf740f98a | ||
|
|
6b80fff2bb | ||
|
|
2e63fedb76 | ||
|
|
5a9f4e5c60 | ||
|
|
d0a48a09e2 | ||
|
|
c0a21e7025 | ||
|
|
10cc15aabe | ||
|
|
adf7681100 | ||
|
|
0237905b96 | ||
|
|
e8aa79bab6 | ||
|
|
4ff5783e59 | ||
|
|
dcfeb52983 | ||
|
|
46790e57e9 | ||
|
|
4bc3fa0826 | ||
|
|
88cfb979be | ||
|
|
32b5db754e | ||
|
|
f33f8ca109 | ||
|
|
598d63db63 | ||
|
|
38bff1b372 | ||
|
|
e8c9b21f20 | ||
|
|
6d3fc63658 |
3
.github/workflows/duplicate-issues.yml
vendored
@@ -55,4 +55,7 @@ jobs:
|
||||
|
||||
Feel free to ignore if none of these address your specific case.'
|
||||
|
||||
Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, please add a comment mentioning the pinned keybinds issue #4997:
|
||||
'For keybind-related issues, please also check our pinned keybinds documentation: #4997'
|
||||
|
||||
If no clear duplicates are found, do not comment."
|
||||
|
||||
74
.github/workflows/publish.yml
vendored
@@ -26,6 +26,7 @@ permissions:
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'sst/opencode'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -79,3 +80,76 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
|
||||
publish-tauri:
|
||||
strategy:
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
- host: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
- host: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
- host: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- run: git fetch --force --tags
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: startsWith(matrix.settings.host, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.settings.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: packages/tauri/src-tauri
|
||||
shared-key: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
cd packages/tauri
|
||||
bun ./scripts/prepare.ts
|
||||
env:
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_VERSION: ${{ inputs.version }}
|
||||
OPENCODE_CHANNEL: latest
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
RUST_TARGET: ${{ matrix.settings.target }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
# Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
|
||||
- run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage
|
||||
if: startsWith(matrix.settings.host, 'ubuntu')
|
||||
|
||||
- name: Build and upload artifacts
|
||||
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
with:
|
||||
projectPath: packages/tauri
|
||||
uploadWorkflowArtifacts: true
|
||||
tauriScript: ${{ (startsWith(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||
args: --target ${{ matrix.settings.target }}
|
||||
updaterJsonPreferNsis: true
|
||||
releaseDraft: true
|
||||
tagName: ${{ inputs.version }}
|
||||
releaseName: ${{ inputs.version }}
|
||||
|
||||
29
.github/workflows/review.yml
vendored
@@ -1,35 +1,20 @@
|
||||
name: Guidelines Check
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, ready_for_review]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
check-guidelines:
|
||||
if: |
|
||||
(github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'issue_comment' &&
|
||||
github.event.issue.pull_request &&
|
||||
startsWith(github.event.comment.body, '/review'))
|
||||
runs-on: ubuntu-latest
|
||||
github.event.issue.pull_request &&
|
||||
startsWith(github.event.comment.body, '/review') &&
|
||||
contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association)
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check if user has write permission
|
||||
if: github.event_name == 'issue_comment'
|
||||
run: |
|
||||
PERMISSION=$(gh api /repos/${{ github.repository }}/collaborators/${{ github.event.comment.user.login }}/permission --jq '.permission')
|
||||
if [[ "$PERMISSION" != "write" && "$PERMISSION" != "admin" ]]; then
|
||||
echo "User does not have write permission"
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get PR number
|
||||
id: pr-number
|
||||
run: |
|
||||
@@ -63,7 +48,7 @@ jobs:
|
||||
OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "gh pr review*": "deny", "*": "deny" } }'
|
||||
run: |
|
||||
PR_BODY=$(jq -r .body pr_data.json)
|
||||
opencode run -m anthropic/claude-sonnet-4-5 "A new pull request has been created: '${{ steps.pr-details.outputs.title }}'
|
||||
opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${{ steps.pr-details.outputs.title }}'
|
||||
|
||||
<pr-number>
|
||||
${{ steps.pr-number.outputs.number }}
|
||||
@@ -74,7 +59,9 @@ jobs:
|
||||
</pr-description>
|
||||
|
||||
Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do
|
||||
|
||||
When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage.
|
||||
When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simpliest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts)
|
||||
|
||||
Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
|
||||
|
||||
@@ -88,4 +75,4 @@ jobs:
|
||||
-f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT'
|
||||
\`\`\`
|
||||
|
||||
Only create comments for actual violations. If the code follows all guidelines, don't run any gh commands."
|
||||
Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!."
|
||||
|
||||
1
.gitignore
vendored
@@ -18,3 +18,4 @@ refs
|
||||
Session.vim
|
||||
opencode.json
|
||||
a.out
|
||||
target
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
description: hello world iaosd ioasjdoiasjd oisadjoisajd osiajd oisaj dosaij dsoajsajdaijdoisa jdoias jdoias jdoia jois jo jdois jdoias jdoias j djoasdj
|
||||
---
|
||||
|
||||
hey there $ARGUMENTS
|
||||
|
||||
!`ls`
|
||||
check out @README.md
|
||||
15
.opencode/command/rmslop.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
description: Remove AI code slop
|
||||
---
|
||||
|
||||
Check the diff against dev, and remove all AI generated slop introduced in this branch.
|
||||
|
||||
This includes:
|
||||
|
||||
- Extra comments that a human wouldn't add or is inconsistent with the rest of the file
|
||||
- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths)
|
||||
- Casts to any to get around type issues
|
||||
- Any other style that is inconsistent with the file
|
||||
- Unnecessary emoji usage
|
||||
|
||||
Report at the end with only a 1-3 sentence summary of what you changed
|
||||
2
STATS.md
@@ -159,3 +159,5 @@
|
||||
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
|
||||
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
|
||||
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
|
||||
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
|
||||
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
|
||||
|
||||
288
bun.lock
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -99,7 +99,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -123,7 +123,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -135,11 +135,13 @@
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solid-primitives/websocket": "1.3.1",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
"@thisbeyond/solid-dnd": "0.7.5",
|
||||
"diff": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"ghostty-web": "0.3.0",
|
||||
"luxon": "catalog:",
|
||||
"marked": "16.2.0",
|
||||
"marked-shiki": "1.2.1",
|
||||
@@ -164,7 +166,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -192,7 +194,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
@@ -208,7 +210,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -238,14 +240,15 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.2.8",
|
||||
"@opentui/core": "0.1.55",
|
||||
"@opentui/solid": "0.1.55",
|
||||
"@opentui/core": "0.1.56",
|
||||
"@opentui/solid": "0.1.56",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"ai": "catalog:",
|
||||
"bun-pty": "0.4.2",
|
||||
"chokidar": "4.0.3",
|
||||
"clipboardy": "4.0.0",
|
||||
"decimal.js": "10.5.0",
|
||||
@@ -297,7 +300,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -317,7 +320,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.81.0",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -328,7 +331,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -341,20 +344,27 @@
|
||||
},
|
||||
"packages/tauri": {
|
||||
"name": "@opencode-ai/tauri",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@opencode-ai/desktop": "workspace:*",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-shell": "~2",
|
||||
"@tauri-apps/plugin-updater": "~2",
|
||||
"solid-js": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "4.0.0",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/bun": "catalog:",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.3",
|
||||
"vite": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -386,7 +396,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -397,7 +407,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -457,7 +467,7 @@
|
||||
"ai": "5.0.97",
|
||||
"diff": "8.0.2",
|
||||
"fuzzysort": "3.1.0",
|
||||
"hono": "4.7.10",
|
||||
"hono": "4.10.7",
|
||||
"hono-openapi": "1.1.1",
|
||||
"luxon": "3.6.1",
|
||||
"remeda": "2.26.0",
|
||||
@@ -472,6 +482,8 @@
|
||||
"zod": "4.1.8",
|
||||
},
|
||||
"packages": {
|
||||
"@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="],
|
||||
|
||||
"@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
|
||||
|
||||
"@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="],
|
||||
@@ -614,6 +626,34 @@
|
||||
|
||||
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.1", "", {}, "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww=="],
|
||||
|
||||
"@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="],
|
||||
|
||||
"@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="],
|
||||
|
||||
"@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="],
|
||||
|
||||
"@azure/core-http": ["@azure/core-http@3.0.5", "", { "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", "@azure/core-tracing": "1.0.0-preview.13", "@azure/core-util": "^1.1.1", "@azure/logger": "^1.0.0", "@types/node-fetch": "^2.5.0", "@types/tunnel": "^0.0.3", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "process": "^0.11.10", "tslib": "^2.2.0", "tunnel": "^0.0.6", "uuid": "^8.3.0", "xml2js": "^0.5.0" } }, "sha512-T8r2q/c3DxNu6mEJfPuJtptUVqwchxzjj32gKcnMi06rdiVONS9rar7kT9T2Am+XvER7uOzpsP79WsqNbdgdWg=="],
|
||||
|
||||
"@azure/core-http-compat": ["@azure/core-http-compat@2.3.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g=="],
|
||||
|
||||
"@azure/core-lro": ["@azure/core-lro@2.7.2", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.6.2" } }, "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw=="],
|
||||
|
||||
"@azure/core-paging": ["@azure/core-paging@1.6.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA=="],
|
||||
|
||||
"@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.22.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg=="],
|
||||
|
||||
"@azure/core-tracing": ["@azure/core-tracing@1.0.0-preview.13", "", { "dependencies": { "@opentelemetry/api": "^1.0.1", "tslib": "^2.2.0" } }, "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ=="],
|
||||
|
||||
"@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="],
|
||||
|
||||
"@azure/core-xml": ["@azure/core-xml@1.5.0", "", { "dependencies": { "fast-xml-parser": "^5.0.7", "tslib": "^2.8.1" } }, "sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw=="],
|
||||
|
||||
"@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="],
|
||||
|
||||
"@azure/storage-blob": ["@azure/storage-blob@12.29.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.1.1", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-7ktyY0rfTM0vo7HvtK6E3UvYnI9qfd6Oz6z/+92VhGRveWng3kJwMKeUpqmW/NmwcDNbxHpSlldG+vsUnRFnBg=="],
|
||||
|
||||
"@azure/storage-common": ["@azure/storage-common@12.1.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-eIOH1pqFwI6UmVNnDQvmFeSg0XppuzDLFeUNO/Xht7ODAzRLgGDh7h550pSxoA+lPDxBl1+D2m/KG3jWzCUjTg=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||
@@ -676,6 +716,10 @@
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.10.1", "", {}, "sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg=="],
|
||||
|
||||
"@bufbuild/protoplugin": ["@bufbuild/protoplugin@2.10.1", "", { "dependencies": { "@bufbuild/protobuf": "2.10.1", "@typescript/vfs": "^1.6.2", "typescript": "5.4.5" } }, "sha512-imB8dKEjrOnG5+XqVS+CeYn924WGLU/g3wogKhk11XtX9y9NJ7432OS6h24asuBbLrQcPdEZ6QkfM7KeOCeeyQ=="],
|
||||
|
||||
"@capsizecss/unpack": ["@capsizecss/unpack@2.4.0", "", { "dependencies": { "blob-to-buffer": "^1.2.8", "cross-fetch": "^3.0.4", "fontkit": "^2.0.2" } }, "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q=="],
|
||||
|
||||
"@clack/core": ["@clack/core@1.0.0-alpha.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg=="],
|
||||
@@ -1036,6 +1080,8 @@
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="],
|
||||
|
||||
"@octokit/plugin-retry": ["@octokit/plugin-retry@3.0.9", "", { "dependencies": { "@octokit/types": "^6.0.3", "bottleneck": "^2.15.3" } }, "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ=="],
|
||||
|
||||
"@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="],
|
||||
|
||||
"@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
||||
@@ -1088,21 +1134,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.55", "", { "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.55", "@opentui/core-darwin-x64": "0.1.55", "@opentui/core-linux-arm64": "0.1.55", "@opentui/core-linux-x64": "0.1.55", "@opentui/core-win32-arm64": "0.1.55", "@opentui/core-win32-x64": "0.1.55", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-WafOO8eMf1/fmlFUfgooWyWJclQG5X/72VUebH+jN6/kSoSb91XJxHQgaKL9CQYFBNBIApQhAZn/sF9Qt60+lQ=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.56", "", { "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.56", "@opentui/core-darwin-x64": "0.1.56", "@opentui/core-linux-arm64": "0.1.56", "@opentui/core-linux-x64": "0.1.56", "@opentui/core-win32-arm64": "0.1.56", "@opentui/core-win32-x64": "0.1.56", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-TI5cSCPYythHIQYpAEdXyZhewGACn2TfnfC1qZmrSyEq33zFo4W7zpQ4EZNpy9xZJFCI+elAUVJFARwhudp9EQ=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.55", "", { "os": "darwin", "cpu": "arm64" }, "sha512-z2Prd/KKUbhPaSGBFv2q0nDtiLB/5oI3sGFDgf+YAfs6M6UfP9n0XkPUupbE1dx4lMyvwA9X8/QUnsQApd3E2g=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.56", "", { "os": "darwin", "cpu": "arm64" }, "sha512-x5U9J2k1Fmbb9Mdh1nOd/yZVpg4ARCrV5pFngpaeKrIWDhs8RLpQW3ap+r7uyFLGFkSn4h5wBR0jj6Dg+Tyw+A=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.55", "", { "os": "darwin", "cpu": "x64" }, "sha512-zjgGmIaTCWUvvQ9vIHJ0ypTkuFIA4ykKiZ16QxpG930bPr9fJ1xZ8MYj+6WSyuiao7tm6iWQfuYKT3tzA8+ItQ=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.56", "", { "os": "darwin", "cpu": "x64" }, "sha512-7swq9rV/SaNVBWoUbC7mlP1VNyKBl7SSwmyVMkcaBP71lkm95zWuh4pgGj82fLgZ9gITRBD95TJVDmTovOyW0A=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.55", "", { "os": "linux", "cpu": "arm64" }, "sha512-77EZtLxH0VW/Kw+6kTs9FrFWfhjaIjsK/o39DAWM1ZNdFDTXAa/MQNOFDlBXbNHiNqPOyxd0tol1nUFLr8ZtZg=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.56", "", { "os": "linux", "cpu": "arm64" }, "sha512-v8b+kiTlynAJzR0hFeVpGFzVi5PGqXAe3Zql9iTiQqTExkm/sR34sfC/P6rBOUhuAnos8ovPDKWtDb6eCTSm9g=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.55", "", { "os": "linux", "cpu": "x64" }, "sha512-o4RB1jqKWx4TM9v2trGUij6H2ymJCxID8BK3HWvRIjd71tpKkaMY4SxaMWGzvK89X40u8v9qKE04dileKNa10w=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.56", "", { "os": "linux", "cpu": "x64" }, "sha512-lbxgvAi5SBswK/2hoMPtLhPvJxASgquPUwvGTRHqzDkCvrOChP/loTjBQpL09/nAFc3jbM3SAbZtnEgA2SGYVw=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.55", "", { "os": "win32", "cpu": "arm64" }, "sha512-SYna371ZcQme6XjGI2ESHM2uMUZQRi9kgtJj5E22uH4wgBpPWFwf83EGWv78v+irvsypR+ZJgVfkwkz6JjgVTQ=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.56", "", { "os": "win32", "cpu": "arm64" }, "sha512-RoCAbvDo+59OevX+6GrEGbaueERiBVnTaWJkrS41hRAD2fFS3CZpW7UuS5jIg7zn5clHmOGyfvCiBkTRXmgkhw=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.55", "", { "os": "win32", "cpu": "x64" }, "sha512-ViPBCm+EtZ/4NmLqLAxcz31lVYGCe1ily+YmfAkoq1K/iuiXGhtD3mDrQuc3ayfTT8w5UwiYKyrUibJVJ/noVQ=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.56", "", { "os": "win32", "cpu": "x64" }, "sha512-i6N5TjZU5gRkJsKmH8e/qY9vwSk0rh6A5t37mHDGlzN4E5yO/MbBrYH4ppLp5stps9Zfi1Re51ofJX1s2hZY/Q=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.55", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.55", "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-6NWOt0k32tu5KCiddLkPjVNWd++vW3QNbEccuGOdSiotO5TuwK4g0rcLAG6haPOB7Mf/l6aC06FQNeexpBqvtQ=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.56", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.56", "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-3R7AfxsYHUyehwJK98rt5dI9u2WCT/uH/CYvddZIgXPHyfFm1SHJekMdy3DUoiQTCUllt68eFGKMv9zRi6Laww=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -1230,6 +1276,14 @@
|
||||
|
||||
"@poppinss/exception": ["@poppinss/exception@1.2.2", "", {}, "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg=="],
|
||||
|
||||
"@protobuf-ts/plugin": ["@protobuf-ts/plugin@2.11.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.4.0", "@bufbuild/protoplugin": "^2.4.0", "@protobuf-ts/protoc": "^2.11.1", "@protobuf-ts/runtime": "^2.11.1", "@protobuf-ts/runtime-rpc": "^2.11.1", "typescript": "^3.9" }, "bin": { "protoc-gen-ts": "bin/protoc-gen-ts", "protoc-gen-dump": "bin/protoc-gen-dump" } }, "sha512-HyuprDcw0bEEJqkOWe1rnXUP0gwYLij8YhPuZyZk6cJbIgc/Q0IFgoHQxOXNIXAcXM4Sbehh6kjVnCzasElw1A=="],
|
||||
|
||||
"@protobuf-ts/protoc": ["@protobuf-ts/protoc@2.11.1", "", { "bin": { "protoc": "protoc.js" } }, "sha512-mUZJaV0daGO6HUX90o/atzQ6A7bbN2RSuHtdwo8SSF2Qoe3zHwa4IHyCN1evftTeHfLmdz+45qo47sL+5P8nyg=="],
|
||||
|
||||
"@protobuf-ts/runtime": ["@protobuf-ts/runtime@2.11.1", "", {}, "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ=="],
|
||||
|
||||
"@protobuf-ts/runtime-rpc": ["@protobuf-ts/runtime-rpc@2.11.1", "", { "dependencies": { "@protobuf-ts/runtime": "^2.11.1" } }, "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ=="],
|
||||
|
||||
"@radix-ui/colors": ["@radix-ui/colors@1.0.1", "", {}, "sha512-xySw8f0ZVsAEP+e7iLl3EvcBXX7gsIlC1Zso/sPBW9gIWerBTgz6axrjU+MZ39wD+WFi5h5zdWpsg3+hwt2Qsg=="],
|
||||
|
||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw=="],
|
||||
@@ -1506,6 +1560,8 @@
|
||||
|
||||
"@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="],
|
||||
|
||||
"@solid-primitives/websocket": ["@solid-primitives/websocket@1.3.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-F06tA2FKa5VsnS4E4WEc3jHpsJfXRlMTGOtolugTzCqV3JmJTyvk9UVg1oz6PgGHKGi1CQ91OP8iW34myyJgaQ=="],
|
||||
|
||||
"@solidjs/meta": ["@solidjs/meta@0.29.4", "", { "peerDependencies": { "solid-js": ">=1.8.4" } }, "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g=="],
|
||||
|
||||
"@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="],
|
||||
@@ -1584,8 +1640,14 @@
|
||||
|
||||
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg=="],
|
||||
|
||||
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ=="],
|
||||
|
||||
"@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-shell": ["@tauri-apps/plugin-shell@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ=="],
|
||||
|
||||
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="],
|
||||
|
||||
"@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
@@ -1680,6 +1742,8 @@
|
||||
|
||||
"@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
|
||||
|
||||
"@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
|
||||
|
||||
"@types/turndown": ["@types/turndown@5.0.5", "", {}, "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w=="],
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
@@ -1706,6 +1770,10 @@
|
||||
|
||||
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20251014.1", "", { "os": "win32", "cpu": "x64" }, "sha512-fi53g2ihH7tkQLlz8hZGAb2V+3aNZpcxrZ530CQ4xcWwAqssEj0EaZJX0VLEtIQBar1ttGVK9Pz/wJU9sYyVzg=="],
|
||||
|
||||
"@typescript/vfs": ["@typescript/vfs@1.6.2", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g=="],
|
||||
|
||||
"@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.2", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"@vercel/oidc": ["@vercel/oidc@3.0.5", "", {}, "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw=="],
|
||||
@@ -1766,6 +1834,10 @@
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="],
|
||||
|
||||
"archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="],
|
||||
|
||||
"arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="],
|
||||
|
||||
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
@@ -1796,6 +1868,8 @@
|
||||
|
||||
"astro-expressive-code": ["astro-expressive-code@0.41.3", "", { "dependencies": { "rehype-expressive-code": "^0.41.3" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" } }, "sha512-u+zHMqo/QNLE2eqYRCrK3+XMlKakv33Bzuz+56V1gs8H0y6TZ0hIi3VNbIxeTn51NLn+mJfUV/A0kMNfE4rANw=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
||||
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
@@ -1856,6 +1930,8 @@
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
||||
@@ -1870,6 +1946,8 @@
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
|
||||
|
||||
"bowser": ["bowser@2.12.1", "", {}, "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw=="],
|
||||
|
||||
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
|
||||
@@ -1884,12 +1962,18 @@
|
||||
|
||||
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
|
||||
|
||||
"buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="],
|
||||
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
|
||||
|
||||
"bun-pty": ["bun-pty@0.4.2", "", {}, "sha512-sHImDz6pJDsHAroYpC9ouKVgOyqZ7FP3N+stX5IdMddHve3rf9LIZBDomQcXrACQ7sQDNuwZQHG8BKR7w8krkQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
|
||||
|
||||
"bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
|
||||
@@ -1926,6 +2010,8 @@
|
||||
|
||||
"chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="],
|
||||
|
||||
"chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
||||
@@ -1990,6 +2076,8 @@
|
||||
|
||||
"common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="],
|
||||
|
||||
"compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="],
|
||||
|
||||
"condense-newlines": ["condense-newlines@0.2.1", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", "kind-of": "^3.0.2" } }, "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg=="],
|
||||
|
||||
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||
@@ -2010,8 +2098,14 @@
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
|
||||
|
||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||
|
||||
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
|
||||
|
||||
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
||||
|
||||
"crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="],
|
||||
|
||||
"cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
@@ -2334,6 +2428,8 @@
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
|
||||
|
||||
"ghostty-web": ["ghostty-web@0.3.0", "", {}, "sha512-SAdSHWYF20GMZUB0n8kh1N6Z4ljMnuUqT8iTB2n5FAPswEV10MejEpLlhW/769GL5+BQa1NYwEg9y/XCckV5+A=="],
|
||||
|
||||
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
|
||||
|
||||
"giget": ["giget@1.2.5", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="],
|
||||
@@ -2428,7 +2524,7 @@
|
||||
|
||||
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
||||
|
||||
"hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="],
|
||||
"hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="],
|
||||
|
||||
"hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="],
|
||||
|
||||
@@ -2452,6 +2548,8 @@
|
||||
|
||||
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
|
||||
|
||||
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
|
||||
@@ -2630,6 +2728,8 @@
|
||||
|
||||
"jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="],
|
||||
|
||||
"jwt-decode": ["jwt-decode@3.1.2", "", {}, "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="],
|
||||
|
||||
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
@@ -2640,6 +2740,8 @@
|
||||
|
||||
"language-map": ["language-map@1.5.0", "", {}, "sha512-n7gFZpe+DwEAX9cXVTw43i3wiudWDDtSn28RmdnS/HCPr284dQI/SztsamWanRr75oSlKSaGbV2nmWCTzGCoVg=="],
|
||||
|
||||
"lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
|
||||
|
||||
"leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
||||
@@ -2866,7 +2968,7 @@
|
||||
|
||||
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
|
||||
|
||||
"mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
|
||||
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
|
||||
|
||||
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
||||
|
||||
@@ -3102,6 +3204,8 @@
|
||||
|
||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||
|
||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||
|
||||
"promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="],
|
||||
|
||||
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
|
||||
@@ -3154,10 +3258,12 @@
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
|
||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
||||
|
||||
"readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="],
|
||||
|
||||
"readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="],
|
||||
@@ -3488,6 +3594,8 @@
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="],
|
||||
|
||||
"tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
@@ -3594,6 +3702,8 @@
|
||||
|
||||
"unstorage": ["unstorage@2.0.0-alpha.4", "", { "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4.0.3", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "lru-cache": "^11.2.2", "mongodb": "^6.20.0", "ofetch": "*", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-ywXZMZRfrvmO1giJeMTCw6VUn0ALYxVl8pFqJPStiyQUvgJImejtAHrKvXPj4QGJAoS/iLGcVGF6ljN/lkh1bw=="],
|
||||
|
||||
"unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
@@ -3712,6 +3822,8 @@
|
||||
|
||||
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
||||
|
||||
"zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="],
|
||||
|
||||
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
||||
@@ -3720,6 +3832,14 @@
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@actions/artifact/@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="],
|
||||
|
||||
"@actions/artifact/@octokit/plugin-request-log": ["@octokit/plugin-request-log@1.0.4", "", { "peerDependencies": { "@octokit/core": ">=3" } }, "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA=="],
|
||||
|
||||
"@actions/artifact/@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="],
|
||||
|
||||
"@actions/artifact/@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="],
|
||||
|
||||
"@actions/github/@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="],
|
||||
|
||||
"@actions/github/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
|
||||
@@ -3802,6 +3922,40 @@
|
||||
|
||||
"@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
|
||||
|
||||
"@azure/core-auth/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
|
||||
|
||||
"@azure/core-client/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
|
||||
|
||||
"@azure/core-client/@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="],
|
||||
|
||||
"@azure/core-http/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
||||
|
||||
"@azure/core-http/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
|
||||
|
||||
"@azure/core-http-compat/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
|
||||
|
||||
"@azure/core-lro/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
|
||||
|
||||
"@azure/core-rest-pipeline/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
|
||||
|
||||
"@azure/core-rest-pipeline/@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="],
|
||||
|
||||
"@azure/core-util/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
|
||||
|
||||
"@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
|
||||
|
||||
"@azure/storage-blob/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
|
||||
|
||||
"@azure/storage-blob/@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="],
|
||||
|
||||
"@azure/storage-blob/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
|
||||
"@azure/storage-common/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
|
||||
|
||||
"@azure/storage-common/@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="],
|
||||
|
||||
"@azure/storage-common/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
|
||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
@@ -3810,6 +3964,8 @@
|
||||
|
||||
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],
|
||||
|
||||
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||
@@ -3890,6 +4046,8 @@
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="],
|
||||
|
||||
"@octokit/plugin-retry/@octokit/types": ["@octokit/types@6.41.0", "", { "dependencies": { "@octokit/openapi-types": "^12.11.0" } }, "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg=="],
|
||||
|
||||
"@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||
|
||||
"@octokit/request-error/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||
@@ -3900,8 +4058,6 @@
|
||||
|
||||
"@opencode-ai/tauri/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
|
||||
|
||||
"@opencode-ai/tauri/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
|
||||
|
||||
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.4.2", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/types": "3.4.2" } }, "sha512-I5baLVi/ynLEOZoWSAMlACHNnG+yw5HDmse0oe+GW6U1u+ULdEB3UHiVWaHoJSSONV7tlcVxuaMy74sREDkSvg=="],
|
||||
|
||||
"@opencode-ai/web/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||
@@ -3926,6 +4082,8 @@
|
||||
|
||||
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||
|
||||
"@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="],
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@slack/bolt/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||
@@ -3972,6 +4130,10 @@
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
|
||||
|
||||
"astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
|
||||
@@ -3992,6 +4154,8 @@
|
||||
|
||||
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
@@ -4004,6 +4168,8 @@
|
||||
|
||||
"c12/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||
|
||||
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
@@ -4058,6 +4224,8 @@
|
||||
|
||||
"jsonwebtoken/jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
|
||||
|
||||
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
|
||||
"md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
@@ -4118,7 +4286,11 @@
|
||||
|
||||
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
||||
"readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"readable-stream/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
|
||||
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
||||
|
||||
"router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||
|
||||
@@ -4188,6 +4360,24 @@
|
||||
|
||||
"zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@actions/artifact/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
|
||||
|
||||
"@actions/artifact/@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
|
||||
|
||||
"@actions/artifact/@octokit/core/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
||||
|
||||
"@actions/artifact/@octokit/core/before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
|
||||
|
||||
"@actions/artifact/@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
||||
|
||||
"@actions/artifact/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
|
||||
|
||||
"@actions/artifact/@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
||||
|
||||
"@actions/artifact/@octokit/request/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
||||
|
||||
"@actions/artifact/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
||||
|
||||
"@actions/github/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
|
||||
|
||||
"@actions/github/@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
|
||||
@@ -4248,6 +4438,10 @@
|
||||
|
||||
"@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="],
|
||||
|
||||
"@azure/core-http/xml2js/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
|
||||
|
||||
"@azure/core-xml/fast-xml-parser/strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
@@ -4414,6 +4608,8 @@
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||
|
||||
"@octokit/plugin-retry/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@12.11.0", "", {}, "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="],
|
||||
|
||||
"@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
|
||||
"@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
@@ -4526,6 +4722,12 @@
|
||||
|
||||
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"astro/shiki/@shikijs/core": ["@shikijs/core@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg=="],
|
||||
|
||||
"astro/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg=="],
|
||||
@@ -4614,6 +4816,8 @@
|
||||
|
||||
"giget/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
|
||||
|
||||
"giget/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
|
||||
|
||||
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"js-beautify/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
@@ -4624,6 +4828,10 @@
|
||||
|
||||
"jsonwebtoken/jws/jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
|
||||
|
||||
"lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
|
||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"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=="],
|
||||
@@ -4646,9 +4854,7 @@
|
||||
|
||||
"prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||
|
||||
"readable-web-to-node-stream/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"readable-web-to-node-stream/readable-stream/events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
@@ -4672,6 +4878,12 @@
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@actions/artifact/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||
|
||||
"@actions/artifact/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||
|
||||
"@actions/artifact/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||
|
||||
"@actions/github/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||
|
||||
"@actions/github/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
|
||||
@@ -4770,6 +4982,8 @@
|
||||
|
||||
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"astro/unstorage/h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
|
||||
|
||||
"astro/unstorage/h3/crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
|
||||
@@ -4816,7 +5030,7 @@
|
||||
|
||||
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||
|
||||
"readable-web-to-node-stream/readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
"prebuild-install/tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
|
||||
6
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1764642553,
|
||||
"narHash": "sha256-mvbFFzVBhVK1FjyPHZGMAKpNiqkr7k++xIwy+p/NQvA=",
|
||||
"lastModified": 1764915887,
|
||||
"narHash": "sha256-CeBCJ9BMsuzVgn8GVfuSRZ6xeau7szzG0Xn6O/OxP9M=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f720de59066162ee879adcc8c79e15c51fe6bfb4",
|
||||
"rev": "42e29df35be6ef54091d3a3b4e97056ce0a98ce8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -13,6 +13,10 @@ inputs:
|
||||
description: "Share the opencode session (defaults to true for public repos)"
|
||||
required: false
|
||||
|
||||
prompt:
|
||||
description: "Custom prompt to override the default prompt"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -27,3 +31,4 @@ runs:
|
||||
env:
|
||||
MODEL: ${{ inputs.model }}
|
||||
SHARE: ${{ inputs.share }}
|
||||
PROMPT: ${{ inputs.prompt }}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"nodeModules": "sha256-QhqAa47P3Y2aoMGnr8l1nLq0EQb4qEm75dGfNjyzbpU="
|
||||
"nodeModules": "sha256-Nem37Y/1kAA2vNOxDsMwE2qfkgneIKPg85JOv8Z0oug="
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"ai": "5.0.97",
|
||||
"hono": "4.7.10",
|
||||
"hono": "4.10.7",
|
||||
"hono-openapi": "1.1.1",
|
||||
"fuzzysort": "3.1.0",
|
||||
"luxon": "3.6.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
|
||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { createEffect, createMemo, onCleanup, Show, For } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
@@ -94,8 +94,6 @@ async function getCosts(workspaceID: string, year: number, month: number) {
|
||||
}, workspaceID)
|
||||
}
|
||||
|
||||
const queryCosts = query(getCosts, "costs.get")
|
||||
|
||||
const MODEL_COLORS: Record<string, string> = {
|
||||
"claude-sonnet-4-5": "#D4745C",
|
||||
"claude-sonnet-4": "#E8B4A4",
|
||||
@@ -160,45 +158,25 @@ export function GraphSection() {
|
||||
keyDropdownOpen: false,
|
||||
colorScheme: "light" as "light" | "dark",
|
||||
})
|
||||
const initialData = createAsync(() => queryCosts(params.id!, store.year, store.month))
|
||||
|
||||
createEffect(() => {
|
||||
if (typeof window === "undefined") return
|
||||
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
setStore({ colorScheme: mediaQuery.matches ? "dark" : "light" })
|
||||
|
||||
const handleColorSchemeChange = (e: MediaQueryListEvent) => {
|
||||
setStore({ colorScheme: e.matches ? "dark" : "light" })
|
||||
}
|
||||
|
||||
mediaQuery.addEventListener("change", handleColorSchemeChange)
|
||||
onCleanup(() => mediaQuery.removeEventListener("change", handleColorSchemeChange))
|
||||
})
|
||||
|
||||
const onPreviousMonth = async () => {
|
||||
const month = store.month === 0 ? 11 : store.month - 1
|
||||
const year = store.month === 0 ? store.year - 1 : store.year
|
||||
const data = await getCosts(params.id!, year, month)
|
||||
setStore({ month, year, data })
|
||||
setStore({ month, year })
|
||||
}
|
||||
|
||||
const onNextMonth = async () => {
|
||||
const month = store.month === 11 ? 0 : store.month + 1
|
||||
const year = store.month === 11 ? store.year + 1 : store.year
|
||||
setStore({ month, year, data: await getCosts(params.id!, year, month) })
|
||||
setStore({ month, year })
|
||||
}
|
||||
|
||||
const onSelectModel = (model: string | null) => setStore({ model, modelDropdownOpen: false })
|
||||
|
||||
const onSelectKey = (keyID: string | null) => setStore({ key: keyID, keyDropdownOpen: false })
|
||||
|
||||
const getData = createMemo(() => store.data ?? initialData())
|
||||
|
||||
const getModels = createMemo(() => {
|
||||
const data = getData()
|
||||
if (!data?.usage) return []
|
||||
return Array.from(new Set(data.usage.map((row) => row.model))).sort()
|
||||
if (!store.data?.usage) return []
|
||||
return Array.from(new Set(store.data.usage.map((row) => row.model))).sort()
|
||||
})
|
||||
|
||||
const getDates = createMemo(() => {
|
||||
@@ -221,9 +199,7 @@ export function GraphSection() {
|
||||
const isCurrentMonth = () => store.year === now.getFullYear() && store.month === now.getMonth()
|
||||
|
||||
const chartConfig = createMemo((): ChartConfiguration | null => {
|
||||
if (typeof window === "undefined") return null
|
||||
|
||||
const data = getData()
|
||||
const data = store.data
|
||||
const dates = getDates()
|
||||
if (!data?.usage?.length) return null
|
||||
|
||||
@@ -365,15 +341,32 @@ export function GraphSection() {
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(async () => {
|
||||
const data = await getCosts(params.id!, store.year, store.month)
|
||||
setStore({ data })
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const config = chartConfig()
|
||||
if (!config || !canvasRef) return
|
||||
|
||||
if (chartInstance) chartInstance.destroy()
|
||||
chartInstance = new Chart(canvasRef, config)
|
||||
|
||||
onCleanup(() => chartInstance?.destroy())
|
||||
})
|
||||
|
||||
onCleanup(() => chartInstance?.destroy())
|
||||
createEffect(() => {
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
setStore({ colorScheme: mediaQuery.matches ? "dark" : "light" })
|
||||
|
||||
const handleColorSchemeChange = (e: MediaQueryListEvent) => {
|
||||
setStore({ colorScheme: e.matches ? "dark" : "light" })
|
||||
}
|
||||
|
||||
mediaQuery.addEventListener("change", handleColorSchemeChange)
|
||||
onCleanup(() => mediaQuery.removeEventListener("change", handleColorSchemeChange))
|
||||
})
|
||||
|
||||
return (
|
||||
<section class={styles.root}>
|
||||
@@ -382,55 +375,53 @@ export function GraphSection() {
|
||||
<p>Usage costs broken down by model.</p>
|
||||
</div>
|
||||
|
||||
<Show when={getData()}>
|
||||
<div data-slot="filter-container">
|
||||
<div data-slot="month-picker">
|
||||
<button data-slot="month-button" onClick={onPreviousMonth}>
|
||||
<IconChevronLeft />
|
||||
</button>
|
||||
<span data-slot="month-label">{formatMonthYear()}</span>
|
||||
<button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}>
|
||||
<IconChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
<Dropdown
|
||||
trigger={store.model === null ? "All Models" : store.model}
|
||||
open={store.modelDropdownOpen}
|
||||
onOpenChange={(open) => setStore({ modelDropdownOpen: open })}
|
||||
>
|
||||
<>
|
||||
<button data-slot="model-item" onClick={() => onSelectModel(null)}>
|
||||
<span>All Models</span>
|
||||
</button>
|
||||
<For each={getModels()}>
|
||||
{(model) => (
|
||||
<button data-slot="model-item" onClick={() => onSelectModel(model)}>
|
||||
<span>{model}</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
trigger={getKeyName(store.key)}
|
||||
open={store.keyDropdownOpen}
|
||||
onOpenChange={(open) => setStore({ keyDropdownOpen: open })}
|
||||
>
|
||||
<>
|
||||
<button data-slot="model-item" onClick={() => onSelectKey(null)}>
|
||||
<span>All Keys</span>
|
||||
</button>
|
||||
<For each={getData()?.keys || []}>
|
||||
{(key) => (
|
||||
<button data-slot="model-item" onClick={() => onSelectKey(key.id)}>
|
||||
<span>{key.displayName}</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</>
|
||||
</Dropdown>
|
||||
<div data-slot="filter-container">
|
||||
<div data-slot="month-picker">
|
||||
<button data-slot="month-button" onClick={onPreviousMonth}>
|
||||
<IconChevronLeft />
|
||||
</button>
|
||||
<span data-slot="month-label">{formatMonthYear()}</span>
|
||||
<button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}>
|
||||
<IconChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<Dropdown
|
||||
trigger={store.model === null ? "All Models" : store.model}
|
||||
open={store.modelDropdownOpen}
|
||||
onOpenChange={(open) => setStore({ modelDropdownOpen: open })}
|
||||
>
|
||||
<>
|
||||
<button data-slot="model-item" onClick={() => onSelectModel(null)}>
|
||||
<span>All Models</span>
|
||||
</button>
|
||||
<For each={getModels()}>
|
||||
{(model) => (
|
||||
<button data-slot="model-item" onClick={() => onSelectModel(model)}>
|
||||
<span>{model}</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
trigger={getKeyName(store.key)}
|
||||
open={store.keyDropdownOpen}
|
||||
onOpenChange={(open) => setStore({ keyDropdownOpen: open })}
|
||||
>
|
||||
<>
|
||||
<button data-slot="model-item" onClick={() => onSelectKey(null)}>
|
||||
<span>All Keys</span>
|
||||
</button>
|
||||
<For each={store.data?.keys || []}>
|
||||
{(key) => (
|
||||
<button data-slot="model-item" onClick={() => onSelectKey(key.id)}>
|
||||
<span>{key.displayName}</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={chartConfig()}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { oaCompatHelper } from "./provider/openai-compatible"
|
||||
import { createRateLimiter } from "./rateLimiter"
|
||||
import { createDataDumper } from "./dataDumper"
|
||||
import { createTrialLimiter } from "./trialLimiter"
|
||||
import { createStickyTracker } from "./stickyProviderTracker"
|
||||
|
||||
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
||||
type RetryOptions = {
|
||||
@@ -68,10 +69,20 @@ export async function handler(
|
||||
const isTrial = await trialLimiter?.isTrial()
|
||||
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
|
||||
await rateLimiter?.check()
|
||||
const stickyTracker = createStickyTracker(modelInfo.stickyProvider ?? false, sessionId)
|
||||
const stickyProvider = await stickyTracker?.get()
|
||||
|
||||
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
|
||||
const providerInfo = selectProvider(zenData, modelInfo, sessionId, isTrial ?? false, retry)
|
||||
const authInfo = await authenticate(modelInfo, providerInfo)
|
||||
const authInfo = await authenticate(modelInfo)
|
||||
const providerInfo = selectProvider(
|
||||
zenData,
|
||||
authInfo,
|
||||
modelInfo,
|
||||
sessionId,
|
||||
isTrial ?? false,
|
||||
retry,
|
||||
stickyProvider,
|
||||
)
|
||||
validateBilling(authInfo, modelInfo)
|
||||
validateModelSettings(authInfo)
|
||||
updateProviderKey(authInfo, providerInfo)
|
||||
@@ -121,6 +132,9 @@ export async function handler(
|
||||
dataDumper?.provideModel(providerInfo.storeModel)
|
||||
dataDumper?.provideRequest(reqBody)
|
||||
|
||||
// Store sticky provider
|
||||
await stickyTracker?.set(providerInfo.id)
|
||||
|
||||
// Scrub response headers
|
||||
const resHeaders = new Headers()
|
||||
const keepHeaders = ["content-type", "cache-control"]
|
||||
@@ -285,16 +299,27 @@ export async function handler(
|
||||
|
||||
function selectProvider(
|
||||
zenData: ZenData,
|
||||
authInfo: AuthInfo,
|
||||
modelInfo: ModelInfo,
|
||||
sessionId: string,
|
||||
isTrial: boolean,
|
||||
retry: RetryOptions,
|
||||
stickyProvider: string | undefined,
|
||||
) {
|
||||
const provider = (() => {
|
||||
if (authInfo?.provider?.credentials) {
|
||||
return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)
|
||||
}
|
||||
|
||||
if (isTrial) {
|
||||
return modelInfo.providers.find((provider) => provider.id === modelInfo.trial!.provider)
|
||||
}
|
||||
|
||||
if (stickyProvider) {
|
||||
const provider = modelInfo.providers.find((provider) => provider.id === stickyProvider)
|
||||
if (provider) return provider
|
||||
}
|
||||
|
||||
if (retry.retryCount === MAX_RETRIES) {
|
||||
return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider)
|
||||
}
|
||||
@@ -330,7 +355,7 @@ export async function handler(
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticate(modelInfo: ModelInfo, providerInfo: ProviderInfo) {
|
||||
async function authenticate(modelInfo: ModelInfo) {
|
||||
const apiKey = opts.parseApiKey(input.request.headers)
|
||||
if (!apiKey || apiKey === "public") {
|
||||
if (modelInfo.allowAnonymous) return
|
||||
@@ -368,7 +393,12 @@ export async function handler(
|
||||
.leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, modelInfo.id)))
|
||||
.leftJoin(
|
||||
ProviderTable,
|
||||
and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, providerInfo.id)),
|
||||
modelInfo.byokProvider
|
||||
? and(
|
||||
eq(ProviderTable.workspaceID, KeyTable.workspaceID),
|
||||
eq(ProviderTable.provider, modelInfo.byokProvider),
|
||||
)
|
||||
: sql`false`,
|
||||
)
|
||||
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
|
||||
.then((rows) => rows[0]),
|
||||
@@ -445,8 +475,7 @@ export async function handler(
|
||||
}
|
||||
|
||||
function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) {
|
||||
if (!authInfo) return
|
||||
if (!authInfo.provider?.credentials) return
|
||||
if (!authInfo?.provider?.credentials) return
|
||||
providerInfo.apiKey = authInfo.provider.credentials
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
|
||||
export function createStickyTracker(stickyProvider: boolean, session: string) {
|
||||
if (!stickyProvider) return
|
||||
if (!session) return
|
||||
const key = `sticky:${session}`
|
||||
|
||||
return {
|
||||
get: async () => {
|
||||
return await Resource.GatewayKv.get(key)
|
||||
},
|
||||
set: async (providerId: string) => {
|
||||
await Resource.GatewayKv.put(key, providerId, { expirationTtl: 86400 })
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -24,6 +24,8 @@ export namespace ZenData {
|
||||
cost: ModelCostSchema,
|
||||
cost200K: ModelCostSchema.optional(),
|
||||
allowAnonymous: z.boolean().optional(),
|
||||
byokProvider: z.enum(["openai", "anthropic", "google"]).optional(),
|
||||
stickyProvider: z.boolean().optional(),
|
||||
trial: z
|
||||
.object({
|
||||
limit: z.number(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
"./vite": "./vite.js"
|
||||
},
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
"start": "vite",
|
||||
@@ -33,11 +37,13 @@
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solid-primitives/websocket": "1.3.1",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
"@thisbeyond/solid-dnd": "0.7.5",
|
||||
"diff": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"ghostty-web": "0.3.0",
|
||||
"luxon": "catalog:",
|
||||
"marked": "16.2.0",
|
||||
"marked-shiki": "1.2.1",
|
||||
|
||||
642
packages/desktop/src/addons/serialize.ts
Normal file
@@ -0,0 +1,642 @@
|
||||
/**
|
||||
* SerializeAddon - Serialize terminal buffer contents
|
||||
*
|
||||
* Port of xterm.js addon-serialize for ghostty-web.
|
||||
* Enables serialization of terminal contents to a string that can
|
||||
* be written back to restore terminal state.
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
* const serializeAddon = new SerializeAddon();
|
||||
* term.loadAddon(serializeAddon);
|
||||
* const content = serializeAddon.serialize();
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { ITerminalAddon, ITerminalCore, IBufferRange } from "ghostty-web"
|
||||
|
||||
// ============================================================================
|
||||
// Buffer Types (matching ghostty-web internal interfaces)
|
||||
// ============================================================================
|
||||
|
||||
interface IBuffer {
|
||||
readonly type: "normal" | "alternate"
|
||||
readonly cursorX: number
|
||||
readonly cursorY: number
|
||||
readonly viewportY: number
|
||||
readonly baseY: number
|
||||
readonly length: number
|
||||
getLine(y: number): IBufferLine | undefined
|
||||
getNullCell(): IBufferCell
|
||||
}
|
||||
|
||||
interface IBufferLine {
|
||||
readonly length: number
|
||||
readonly isWrapped: boolean
|
||||
getCell(x: number): IBufferCell | undefined
|
||||
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string
|
||||
}
|
||||
|
||||
interface IBufferCell {
|
||||
getChars(): string
|
||||
getCode(): number
|
||||
getWidth(): number
|
||||
getFgColorMode(): number
|
||||
getBgColorMode(): number
|
||||
getFgColor(): number
|
||||
getBgColor(): number
|
||||
isBold(): number
|
||||
isItalic(): number
|
||||
isUnderline(): number
|
||||
isStrikethrough(): number
|
||||
isBlink(): number
|
||||
isInverse(): number
|
||||
isInvisible(): number
|
||||
isFaint(): number
|
||||
isDim(): boolean
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export interface ISerializeOptions {
|
||||
/**
|
||||
* The row range to serialize. When an explicit range is specified, the cursor
|
||||
* will get its final repositioning.
|
||||
*/
|
||||
range?: ISerializeRange
|
||||
/**
|
||||
* The number of rows in the scrollback buffer to serialize, starting from
|
||||
* the bottom of the scrollback buffer. When not specified, all available
|
||||
* rows in the scrollback buffer will be serialized.
|
||||
*/
|
||||
scrollback?: number
|
||||
/**
|
||||
* Whether to exclude the terminal modes from the serialization.
|
||||
* Default: false
|
||||
*/
|
||||
excludeModes?: boolean
|
||||
/**
|
||||
* Whether to exclude the alt buffer from the serialization.
|
||||
* Default: false
|
||||
*/
|
||||
excludeAltBuffer?: boolean
|
||||
}
|
||||
|
||||
export interface ISerializeRange {
|
||||
/**
|
||||
* The line to start serializing (inclusive).
|
||||
*/
|
||||
start: number
|
||||
/**
|
||||
* The line to end serializing (inclusive).
|
||||
*/
|
||||
end: number
|
||||
}
|
||||
|
||||
export interface IHTMLSerializeOptions {
|
||||
/**
|
||||
* The number of rows in the scrollback buffer to serialize, starting from
|
||||
* the bottom of the scrollback buffer.
|
||||
*/
|
||||
scrollback?: number
|
||||
/**
|
||||
* Whether to only serialize the selection.
|
||||
* Default: false
|
||||
*/
|
||||
onlySelection?: boolean
|
||||
/**
|
||||
* Whether to include the global background of the terminal.
|
||||
* Default: false
|
||||
*/
|
||||
includeGlobalBackground?: boolean
|
||||
/**
|
||||
* The range to serialize. This is prioritized over onlySelection.
|
||||
*/
|
||||
range?: {
|
||||
startLine: number
|
||||
endLine: number
|
||||
startCol: number
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
function constrain(value: number, low: number, high: number): number {
|
||||
return Math.max(low, Math.min(value, high))
|
||||
}
|
||||
|
||||
function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return cell1.getFgColorMode() === cell2.getFgColorMode() && cell1.getFgColor() === cell2.getFgColor()
|
||||
}
|
||||
|
||||
function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return cell1.getBgColorMode() === cell2.getBgColorMode() && cell1.getBgColor() === cell2.getBgColor()
|
||||
}
|
||||
|
||||
function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return (
|
||||
!!cell1.isInverse() === !!cell2.isInverse() &&
|
||||
!!cell1.isBold() === !!cell2.isBold() &&
|
||||
!!cell1.isUnderline() === !!cell2.isUnderline() &&
|
||||
!!cell1.isBlink() === !!cell2.isBlink() &&
|
||||
!!cell1.isInvisible() === !!cell2.isInvisible() &&
|
||||
!!cell1.isItalic() === !!cell2.isItalic() &&
|
||||
!!cell1.isDim() === !!cell2.isDim() &&
|
||||
!!cell1.isStrikethrough() === !!cell2.isStrikethrough()
|
||||
)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Base Serialize Handler
|
||||
// ============================================================================
|
||||
|
||||
abstract class BaseSerializeHandler {
|
||||
constructor(protected readonly _buffer: IBuffer) {}
|
||||
|
||||
public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {
|
||||
let oldCell = this._buffer.getNullCell()
|
||||
|
||||
const startRow = range.start.y
|
||||
const endRow = range.end.y
|
||||
const startColumn = range.start.x
|
||||
const endColumn = range.end.x
|
||||
|
||||
this._beforeSerialize(endRow - startRow, startRow, endRow)
|
||||
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const line = this._buffer.getLine(row)
|
||||
if (line) {
|
||||
const startLineColumn = row === range.start.y ? startColumn : 0
|
||||
const endLineColumn = row === range.end.y ? endColumn : line.length
|
||||
for (let col = startLineColumn; col < endLineColumn; col++) {
|
||||
const c = line.getCell(col)
|
||||
if (!c) {
|
||||
continue
|
||||
}
|
||||
this._nextCell(c, oldCell, row, col)
|
||||
oldCell = c
|
||||
}
|
||||
}
|
||||
this._rowEnd(row, row === endRow)
|
||||
}
|
||||
|
||||
this._afterSerialize()
|
||||
|
||||
return this._serializeString(excludeFinalCursorPosition)
|
||||
}
|
||||
|
||||
protected _nextCell(_cell: IBufferCell, _oldCell: IBufferCell, _row: number, _col: number): void {}
|
||||
protected _rowEnd(_row: number, _isLastRow: boolean): void {}
|
||||
protected _beforeSerialize(_rows: number, _startRow: number, _endRow: number): void {}
|
||||
protected _afterSerialize(): void {}
|
||||
protected _serializeString(_excludeFinalCursorPosition?: boolean): string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// String Serialize Handler
|
||||
// ============================================================================
|
||||
|
||||
class StringSerializeHandler extends BaseSerializeHandler {
|
||||
private _rowIndex: number = 0
|
||||
private _allRows: string[] = []
|
||||
private _allRowSeparators: string[] = []
|
||||
private _currentRow: string = ""
|
||||
private _nullCellCount: number = 0
|
||||
private _cursorStyle: IBufferCell
|
||||
private _cursorStyleRow: number = 0
|
||||
private _cursorStyleCol: number = 0
|
||||
private _backgroundCell: IBufferCell
|
||||
private _firstRow: number = 0
|
||||
private _lastCursorRow: number = 0
|
||||
private _lastCursorCol: number = 0
|
||||
private _lastContentCursorRow: number = 0
|
||||
private _lastContentCursorCol: number = 0
|
||||
private _thisRowLastChar: IBufferCell
|
||||
private _thisRowLastSecondChar: IBufferCell
|
||||
private _nextRowFirstChar: IBufferCell
|
||||
|
||||
constructor(
|
||||
buffer: IBuffer,
|
||||
private readonly _terminal: ITerminalCore,
|
||||
) {
|
||||
super(buffer)
|
||||
this._cursorStyle = this._buffer.getNullCell()
|
||||
this._backgroundCell = this._buffer.getNullCell()
|
||||
this._thisRowLastChar = this._buffer.getNullCell()
|
||||
this._thisRowLastSecondChar = this._buffer.getNullCell()
|
||||
this._nextRowFirstChar = this._buffer.getNullCell()
|
||||
}
|
||||
|
||||
protected _beforeSerialize(rows: number, start: number, _end: number): void {
|
||||
this._allRows = new Array<string>(rows)
|
||||
this._lastContentCursorRow = start
|
||||
this._lastCursorRow = start
|
||||
this._firstRow = start
|
||||
}
|
||||
|
||||
protected _rowEnd(row: number, isLastRow: boolean): void {
|
||||
// if there is colorful empty cell at line end, we must pad it back
|
||||
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||
this._currentRow += `\u001b[${this._nullCellCount}X`
|
||||
}
|
||||
|
||||
let rowSeparator = ""
|
||||
|
||||
if (!isLastRow) {
|
||||
// Enable BCE
|
||||
if (row - this._firstRow >= this._terminal.rows) {
|
||||
const line = this._buffer.getLine(this._cursorStyleRow)
|
||||
const cell = line?.getCell(this._cursorStyleCol)
|
||||
if (cell) {
|
||||
this._backgroundCell = cell
|
||||
}
|
||||
}
|
||||
|
||||
const currentLine = this._buffer.getLine(row)!
|
||||
const nextLine = this._buffer.getLine(row + 1)!
|
||||
|
||||
if (!nextLine.isWrapped) {
|
||||
rowSeparator = "\r\n"
|
||||
this._lastCursorRow = row + 1
|
||||
this._lastCursorCol = 0
|
||||
} else {
|
||||
rowSeparator = ""
|
||||
const thisRowLastChar = currentLine.getCell(currentLine.length - 1)
|
||||
const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2)
|
||||
const nextRowFirstChar = nextLine.getCell(0)
|
||||
|
||||
if (thisRowLastChar) this._thisRowLastChar = thisRowLastChar
|
||||
if (thisRowLastSecondChar) this._thisRowLastSecondChar = thisRowLastSecondChar
|
||||
if (nextRowFirstChar) this._nextRowFirstChar = nextRowFirstChar
|
||||
|
||||
const isNextRowFirstCharDoubleWidth = this._nextRowFirstChar.getWidth() > 1
|
||||
|
||||
let isValid = false
|
||||
|
||||
if (
|
||||
this._nextRowFirstChar.getChars() &&
|
||||
(isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0)
|
||||
) {
|
||||
if (
|
||||
(this._thisRowLastChar.getChars() || this._thisRowLastChar.getWidth() === 0) &&
|
||||
equalBg(this._thisRowLastChar, this._nextRowFirstChar)
|
||||
) {
|
||||
isValid = true
|
||||
}
|
||||
|
||||
if (
|
||||
isNextRowFirstCharDoubleWidth &&
|
||||
(this._thisRowLastSecondChar.getChars() || this._thisRowLastSecondChar.getWidth() === 0) &&
|
||||
equalBg(this._thisRowLastChar, this._nextRowFirstChar) &&
|
||||
equalBg(this._thisRowLastSecondChar, this._nextRowFirstChar)
|
||||
) {
|
||||
isValid = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
rowSeparator = "-".repeat(this._nullCellCount + 1)
|
||||
rowSeparator += "\u001b[1D\u001b[1X"
|
||||
|
||||
if (this._nullCellCount > 0) {
|
||||
rowSeparator += "\u001b[A"
|
||||
rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C`
|
||||
rowSeparator += `\u001b[${this._nullCellCount}X`
|
||||
rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D`
|
||||
rowSeparator += "\u001b[B"
|
||||
}
|
||||
|
||||
this._lastContentCursorRow = row + 1
|
||||
this._lastContentCursorCol = 0
|
||||
this._lastCursorRow = row + 1
|
||||
this._lastCursorCol = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._allRows[this._rowIndex] = this._currentRow
|
||||
this._allRowSeparators[this._rowIndex++] = rowSeparator
|
||||
this._currentRow = ""
|
||||
this._nullCellCount = 0
|
||||
}
|
||||
|
||||
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] {
|
||||
const sgrSeq: number[] = []
|
||||
const fgChanged = !equalFg(cell, oldCell)
|
||||
const bgChanged = !equalBg(cell, oldCell)
|
||||
const flagsChanged = !equalFlags(cell, oldCell)
|
||||
|
||||
if (fgChanged || bgChanged || flagsChanged) {
|
||||
if (this._isAttributeDefault(cell)) {
|
||||
if (!this._isAttributeDefault(oldCell)) {
|
||||
sgrSeq.push(0)
|
||||
}
|
||||
} else {
|
||||
if (fgChanged) {
|
||||
const color = cell.getFgColor()
|
||||
const mode = cell.getFgColorMode()
|
||||
if (mode === 2) {
|
||||
// RGB
|
||||
sgrSeq.push(38, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
|
||||
} else if (mode === 1) {
|
||||
// Palette
|
||||
if (color >= 16) {
|
||||
sgrSeq.push(38, 5, color)
|
||||
} else {
|
||||
sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7))
|
||||
}
|
||||
} else {
|
||||
sgrSeq.push(39)
|
||||
}
|
||||
}
|
||||
if (bgChanged) {
|
||||
const color = cell.getBgColor()
|
||||
const mode = cell.getBgColorMode()
|
||||
if (mode === 2) {
|
||||
// RGB
|
||||
sgrSeq.push(48, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
|
||||
} else if (mode === 1) {
|
||||
// Palette
|
||||
if (color >= 16) {
|
||||
sgrSeq.push(48, 5, color)
|
||||
} else {
|
||||
sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7))
|
||||
}
|
||||
} else {
|
||||
sgrSeq.push(49)
|
||||
}
|
||||
}
|
||||
if (flagsChanged) {
|
||||
if (!!cell.isInverse() !== !!oldCell.isInverse()) {
|
||||
sgrSeq.push(cell.isInverse() ? 7 : 27)
|
||||
}
|
||||
if (!!cell.isBold() !== !!oldCell.isBold()) {
|
||||
sgrSeq.push(cell.isBold() ? 1 : 22)
|
||||
}
|
||||
if (!!cell.isUnderline() !== !!oldCell.isUnderline()) {
|
||||
sgrSeq.push(cell.isUnderline() ? 4 : 24)
|
||||
}
|
||||
if (!!cell.isBlink() !== !!oldCell.isBlink()) {
|
||||
sgrSeq.push(cell.isBlink() ? 5 : 25)
|
||||
}
|
||||
if (!!cell.isInvisible() !== !!oldCell.isInvisible()) {
|
||||
sgrSeq.push(cell.isInvisible() ? 8 : 28)
|
||||
}
|
||||
if (!!cell.isItalic() !== !!oldCell.isItalic()) {
|
||||
sgrSeq.push(cell.isItalic() ? 3 : 23)
|
||||
}
|
||||
if (!!cell.isDim() !== !!oldCell.isDim()) {
|
||||
sgrSeq.push(cell.isDim() ? 2 : 22)
|
||||
}
|
||||
if (!!cell.isStrikethrough() !== !!oldCell.isStrikethrough()) {
|
||||
sgrSeq.push(cell.isStrikethrough() ? 9 : 29)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sgrSeq
|
||||
}
|
||||
|
||||
private _isAttributeDefault(cell: IBufferCell): boolean {
|
||||
return (
|
||||
cell.getFgColorMode() === 0 &&
|
||||
cell.getBgColorMode() === 0 &&
|
||||
!cell.isBold() &&
|
||||
!cell.isItalic() &&
|
||||
!cell.isUnderline() &&
|
||||
!cell.isBlink() &&
|
||||
!cell.isInverse() &&
|
||||
!cell.isInvisible() &&
|
||||
!cell.isDim() &&
|
||||
!cell.isStrikethrough()
|
||||
)
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, _oldCell: IBufferCell, row: number, col: number): void {
|
||||
const isPlaceHolderCell = cell.getWidth() === 0
|
||||
|
||||
if (isPlaceHolderCell) {
|
||||
return
|
||||
}
|
||||
|
||||
const isEmptyCell = cell.getChars() === ""
|
||||
|
||||
const sgrSeq = this._diffStyle(cell, this._cursorStyle)
|
||||
|
||||
const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0
|
||||
|
||||
if (styleChanged) {
|
||||
if (this._nullCellCount > 0) {
|
||||
if (!equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||
this._currentRow += `\u001b[${this._nullCellCount}X`
|
||||
}
|
||||
this._currentRow += `\u001b[${this._nullCellCount}C`
|
||||
this._nullCellCount = 0
|
||||
}
|
||||
|
||||
this._lastContentCursorRow = this._lastCursorRow = row
|
||||
this._lastContentCursorCol = this._lastCursorCol = col
|
||||
|
||||
this._currentRow += `\u001b[${sgrSeq.join(";")}m`
|
||||
|
||||
const line = this._buffer.getLine(row)
|
||||
const cellFromLine = line?.getCell(col)
|
||||
if (cellFromLine) {
|
||||
this._cursorStyle = cellFromLine
|
||||
this._cursorStyleRow = row
|
||||
this._cursorStyleCol = col
|
||||
}
|
||||
}
|
||||
|
||||
if (isEmptyCell) {
|
||||
this._nullCellCount += cell.getWidth()
|
||||
} else {
|
||||
if (this._nullCellCount > 0) {
|
||||
if (equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||
this._currentRow += `\u001b[${this._nullCellCount}C`
|
||||
} else {
|
||||
this._currentRow += `\u001b[${this._nullCellCount}X`
|
||||
this._currentRow += `\u001b[${this._nullCellCount}C`
|
||||
}
|
||||
this._nullCellCount = 0
|
||||
}
|
||||
|
||||
this._currentRow += cell.getChars()
|
||||
|
||||
this._lastContentCursorRow = this._lastCursorRow = row
|
||||
this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth()
|
||||
}
|
||||
}
|
||||
|
||||
protected _serializeString(excludeFinalCursorPosition?: boolean): string {
|
||||
let rowEnd = this._allRows.length
|
||||
|
||||
if (this._buffer.length - this._firstRow <= this._terminal.rows) {
|
||||
rowEnd = this._lastContentCursorRow + 1 - this._firstRow
|
||||
this._lastCursorCol = this._lastContentCursorCol
|
||||
this._lastCursorRow = this._lastContentCursorRow
|
||||
}
|
||||
|
||||
let content = ""
|
||||
|
||||
for (let i = 0; i < rowEnd; i++) {
|
||||
content += this._allRows[i]
|
||||
if (i + 1 < rowEnd) {
|
||||
content += this._allRowSeparators[i]
|
||||
}
|
||||
}
|
||||
|
||||
if (!excludeFinalCursorPosition) {
|
||||
const absoluteCursorRow = (this._buffer.baseY ?? 0) + this._buffer.cursorY
|
||||
const cursorRow = constrain(absoluteCursorRow - this._firstRow + 1, 1, Number.MAX_SAFE_INTEGER)
|
||||
const cursorCol = this._buffer.cursorX + 1
|
||||
content += `\u001b[${cursorRow};${cursorCol}H`
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SerializeAddon Class
|
||||
// ============================================================================
|
||||
|
||||
export class SerializeAddon implements ITerminalAddon {
|
||||
private _terminal?: ITerminalCore
|
||||
|
||||
/**
|
||||
* Activate the addon (called by Terminal.loadAddon)
|
||||
*/
|
||||
public activate(terminal: ITerminalCore): void {
|
||||
this._terminal = terminal
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the addon and clean up resources
|
||||
*/
|
||||
public dispose(): void {
|
||||
this._terminal = undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes terminal rows into a string that can be written back to the
|
||||
* terminal to restore the state. The cursor will also be positioned to the
|
||||
* correct cell.
|
||||
*
|
||||
* @param options Custom options to allow control over what gets serialized.
|
||||
*/
|
||||
public serialize(options?: ISerializeOptions): string {
|
||||
if (!this._terminal) {
|
||||
throw new Error("Cannot use addon until it has been loaded")
|
||||
}
|
||||
|
||||
const terminal = this._terminal as any
|
||||
const buffer = terminal.buffer
|
||||
|
||||
if (!buffer) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const normalBuffer = buffer.normal || buffer.active
|
||||
const altBuffer = buffer.alternate
|
||||
|
||||
if (!normalBuffer) {
|
||||
return ""
|
||||
}
|
||||
|
||||
let content = options?.range
|
||||
? this._serializeBufferByRange(normalBuffer, options.range, true)
|
||||
: this._serializeBufferByScrollback(normalBuffer, options?.scrollback)
|
||||
|
||||
if (!options?.excludeAltBuffer && buffer.active?.type === "alternate" && altBuffer) {
|
||||
const alternateContent = this._serializeBufferByScrollback(altBuffer, undefined)
|
||||
content += `\u001b[?1049h\u001b[H${alternateContent}`
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes terminal content as plain text (no escape sequences)
|
||||
* @param options Custom options to allow control over what gets serialized.
|
||||
*/
|
||||
public serializeAsText(options?: { scrollback?: number; trimWhitespace?: boolean }): string {
|
||||
if (!this._terminal) {
|
||||
throw new Error("Cannot use addon until it has been loaded")
|
||||
}
|
||||
|
||||
const terminal = this._terminal as any
|
||||
const buffer = terminal.buffer
|
||||
|
||||
if (!buffer) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const activeBuffer = buffer.active || buffer.normal
|
||||
if (!activeBuffer) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const maxRows = activeBuffer.length
|
||||
const scrollback = options?.scrollback
|
||||
const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + this._terminal.rows, 0, maxRows)
|
||||
|
||||
const startRow = maxRows - correctRows
|
||||
const endRow = maxRows - 1
|
||||
const lines: string[] = []
|
||||
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const line = activeBuffer.getLine(row)
|
||||
if (line) {
|
||||
const text = line.translateToString(options?.trimWhitespace ?? true)
|
||||
lines.push(text)
|
||||
}
|
||||
}
|
||||
|
||||
// Trim trailing empty lines if requested
|
||||
if (options?.trimWhitespace) {
|
||||
while (lines.length > 0 && lines[lines.length - 1] === "") {
|
||||
lines.pop()
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
private _serializeBufferByScrollback(buffer: IBuffer, scrollback?: number): string {
|
||||
const maxRows = buffer.length
|
||||
const rows = this._terminal?.rows ?? 24
|
||||
const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + rows, 0, maxRows)
|
||||
return this._serializeBufferByRange(
|
||||
buffer,
|
||||
{
|
||||
start: maxRows - correctRows,
|
||||
end: maxRows - 1,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
private _serializeBufferByRange(
|
||||
buffer: IBuffer,
|
||||
range: ISerializeRange,
|
||||
excludeFinalCursorPosition: boolean,
|
||||
): string {
|
||||
const handler = new StringSerializeHandler(buffer, this._terminal!)
|
||||
const cols = this._terminal?.cols ?? 80
|
||||
return handler.serialize(
|
||||
{
|
||||
start: { x: 0, y: range.start },
|
||||
end: { x: cols, y: range.end },
|
||||
},
|
||||
excludeFinalCursorPosition,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -456,9 +456,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<img src={`https://models.dev/logos/${i.provider.id}.svg`} class="size-6 p-0.5 shrink-0" />
|
||||
<div class="flex gap-x-3 items-baseline flex-[1_0_0]">
|
||||
<span class="text-14-medium text-text-strong overflow-hidden text-ellipsis">{i.name}</span>
|
||||
<Show when={i.release_date}>
|
||||
<Show when={false}>
|
||||
<span class="text-12-medium text-text-weak overflow-hidden text-ellipsis truncate min-w-0">
|
||||
{DateTime.fromFormat(i.release_date, "yyyy-MM-dd").toFormat("LLL yyyy")}
|
||||
{DateTime.fromFormat("unknown", "yyyy-MM-dd").toFormat("LLL yyyy")}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
147
packages/desktop/src/components/terminal.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { init, Terminal as Term, FitAddon } from "ghostty-web"
|
||||
import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { SerializeAddon } from "@/addons/serialize"
|
||||
import { LocalPTY } from "@/context/session"
|
||||
|
||||
await init()
|
||||
|
||||
export interface TerminalProps extends ComponentProps<"div"> {
|
||||
pty: LocalPTY
|
||||
onSubmit?: () => void
|
||||
onCleanup?: (pty: LocalPTY) => void
|
||||
onConnectError?: (error: unknown) => void
|
||||
}
|
||||
|
||||
export const Terminal = (props: TerminalProps) => {
|
||||
const sdk = useSDK()
|
||||
let container!: HTMLDivElement
|
||||
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"])
|
||||
let ws: WebSocket
|
||||
let term: Term
|
||||
let serializeAddon: SerializeAddon
|
||||
let fitAddon: FitAddon
|
||||
|
||||
onMount(async () => {
|
||||
ws = new WebSocket(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
|
||||
term = new Term({
|
||||
cursorBlink: true,
|
||||
fontSize: 14,
|
||||
fontFamily: "TX-02, monospace",
|
||||
allowTransparency: true,
|
||||
theme: {
|
||||
background: "#191515",
|
||||
foreground: "#d4d4d4",
|
||||
},
|
||||
scrollback: 10_000,
|
||||
})
|
||||
term.attachCustomKeyEventHandler((event) => {
|
||||
// allow for ctrl-` to toggle terminal in parent
|
||||
if (event.ctrlKey && event.key.toLowerCase() === "`") {
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
fitAddon = new FitAddon()
|
||||
serializeAddon = new SerializeAddon()
|
||||
term.loadAddon(serializeAddon)
|
||||
term.loadAddon(fitAddon)
|
||||
|
||||
term.open(container)
|
||||
|
||||
if (local.pty.buffer) {
|
||||
if (local.pty.rows && local.pty.cols) {
|
||||
term.resize(local.pty.cols, local.pty.rows)
|
||||
}
|
||||
term.reset()
|
||||
term.write(local.pty.buffer)
|
||||
if (local.pty.scrollY) {
|
||||
term.scrollToLine(local.pty.scrollY)
|
||||
}
|
||||
}
|
||||
|
||||
container.focus()
|
||||
|
||||
fitAddon.fit()
|
||||
fitAddon.observeResize()
|
||||
window.addEventListener("resize", () => fitAddon.fit())
|
||||
term.onResize(async (size) => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
await sdk.client.pty.update({
|
||||
path: { id: local.pty.id },
|
||||
body: {
|
||||
size: {
|
||||
cols: size.cols,
|
||||
rows: size.rows,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
term.onData((data) => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(data)
|
||||
}
|
||||
})
|
||||
term.onKey((key) => {
|
||||
if (key.key == "Enter") {
|
||||
props.onSubmit?.()
|
||||
}
|
||||
})
|
||||
// term.onScroll((ydisp) => {
|
||||
// console.log("Scroll position:", ydisp)
|
||||
// })
|
||||
ws.addEventListener("open", () => {
|
||||
console.log("WebSocket connected")
|
||||
sdk.client.pty.update({
|
||||
path: { id: local.pty.id },
|
||||
body: {
|
||||
size: {
|
||||
cols: term.cols,
|
||||
rows: term.rows,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
ws.addEventListener("message", (event) => {
|
||||
term.write(event.data)
|
||||
})
|
||||
ws.addEventListener("error", (error) => {
|
||||
console.error("WebSocket error:", error)
|
||||
props.onConnectError?.(error)
|
||||
})
|
||||
ws.addEventListener("close", () => {
|
||||
console.log("WebSocket disconnected")
|
||||
})
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
if (serializeAddon && props.onCleanup) {
|
||||
const buffer = serializeAddon.serialize()
|
||||
props.onCleanup({
|
||||
...local.pty,
|
||||
buffer,
|
||||
rows: term.rows,
|
||||
cols: term.cols,
|
||||
scrollY: term.getViewportY(),
|
||||
})
|
||||
}
|
||||
ws?.close()
|
||||
term?.dispose()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={container}
|
||||
data-component="terminal"
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
"size-full px-6 py-3 font-mono": true,
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -15,12 +15,16 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
opened: true,
|
||||
width: 280,
|
||||
},
|
||||
terminal: {
|
||||
opened: false,
|
||||
height: 280,
|
||||
},
|
||||
review: {
|
||||
state: "pane" as "pane" | "tab",
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "___default-layout",
|
||||
name: "____default-layout",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -61,6 +65,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
setStore("sidebar", "width", width)
|
||||
},
|
||||
},
|
||||
terminal: {
|
||||
opened: createMemo(() => store.terminal.opened),
|
||||
open() {
|
||||
setStore("terminal", "opened", true)
|
||||
},
|
||||
close() {
|
||||
setStore("terminal", "opened", false)
|
||||
},
|
||||
toggle() {
|
||||
setStore("terminal", "opened", (x) => !x)
|
||||
},
|
||||
height: createMemo(() => store.terminal.height),
|
||||
resize(height: number) {
|
||||
setStore("terminal", "height", height)
|
||||
},
|
||||
},
|
||||
review: {
|
||||
state: createMemo(() => store.review?.state ?? "closed"),
|
||||
pane() {
|
||||
|
||||
@@ -27,6 +27,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||
abort.abort()
|
||||
})
|
||||
|
||||
return { directory: props.directory, client: sdk, event: emitter }
|
||||
return { directory: props.directory, client: sdk, event: emitter, url: globalSDK.url }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -8,14 +8,25 @@ import { pipe, sumBy } from "remeda"
|
||||
import { AssistantMessage, UserMessage } from "@opencode-ai/sdk"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { base64Encode } from "@/utils"
|
||||
import { useSDK } from "./sdk"
|
||||
|
||||
export type LocalPTY = {
|
||||
id: string
|
||||
title: string
|
||||
rows?: number
|
||||
cols?: number
|
||||
buffer?: string
|
||||
scrollY?: number
|
||||
}
|
||||
|
||||
export const { use: useSession, provider: SessionProvider } = createSimpleContext({
|
||||
name: "Session",
|
||||
init: () => {
|
||||
const sdk = useSDK()
|
||||
const params = useParams()
|
||||
const sync = useSync()
|
||||
const name = createMemo(
|
||||
() => `___${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`,
|
||||
() => `${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}.v1`,
|
||||
)
|
||||
|
||||
const [store, setStore] = makePersisted(
|
||||
@@ -23,16 +34,21 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
messageId?: string
|
||||
tabs: {
|
||||
active?: string
|
||||
opened: string[]
|
||||
all: string[]
|
||||
}
|
||||
prompt: Prompt
|
||||
cursor?: number
|
||||
terminals: {
|
||||
active?: string
|
||||
all: LocalPTY[]
|
||||
}
|
||||
}>({
|
||||
tabs: {
|
||||
opened: [],
|
||||
all: [],
|
||||
},
|
||||
prompt: clonePrompt(DEFAULT_PROMPT),
|
||||
cursor: undefined,
|
||||
terminals: { all: [] },
|
||||
}),
|
||||
{
|
||||
name: name(),
|
||||
@@ -138,7 +154,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
setStore("tabs", "active", tab)
|
||||
},
|
||||
setOpenedTabs(tabs: string[]) {
|
||||
setStore("tabs", "opened", tabs)
|
||||
setStore("tabs", "all", tabs)
|
||||
},
|
||||
async openTab(tab: string) {
|
||||
if (tab === "chat") {
|
||||
@@ -146,8 +162,8 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
return
|
||||
}
|
||||
if (tab !== "review") {
|
||||
if (!store.tabs.opened.includes(tab)) {
|
||||
setStore("tabs", "opened", [...store.tabs.opened, tab])
|
||||
if (!store.tabs.all.includes(tab)) {
|
||||
setStore("tabs", "all", [...store.tabs.all, tab])
|
||||
}
|
||||
}
|
||||
setStore("tabs", "active", tab)
|
||||
@@ -156,28 +172,100 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
||||
batch(() => {
|
||||
setStore(
|
||||
"tabs",
|
||||
"opened",
|
||||
store.tabs.opened.filter((x) => x !== tab),
|
||||
"all",
|
||||
store.tabs.all.filter((x) => x !== tab),
|
||||
)
|
||||
if (store.tabs.active === tab) {
|
||||
const index = store.tabs.opened.findIndex((f) => f === tab)
|
||||
const previous = store.tabs.opened[Math.max(0, index - 1)]
|
||||
const index = store.tabs.all.findIndex((f) => f === tab)
|
||||
const previous = store.tabs.all[Math.max(0, index - 1)]
|
||||
setStore("tabs", "active", previous)
|
||||
}
|
||||
})
|
||||
},
|
||||
moveTab(tab: string, to: number) {
|
||||
const index = store.tabs.opened.findIndex((f) => f === tab)
|
||||
const index = store.tabs.all.findIndex((f) => f === tab)
|
||||
if (index === -1) return
|
||||
setStore(
|
||||
"tabs",
|
||||
"opened",
|
||||
"all",
|
||||
produce((opened) => {
|
||||
opened.splice(to, 0, opened.splice(index, 1)[0])
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
terminal: {
|
||||
all: createMemo(() => Object.values(store.terminals.all)),
|
||||
active: createMemo(() => store.terminals.active),
|
||||
new() {
|
||||
sdk.client.pty.create({ body: { title: `Terminal ${store.terminals.all.length + 1}` } }).then((pty) => {
|
||||
const id = pty.data?.id
|
||||
if (!id) return
|
||||
setStore("terminals", "all", [
|
||||
...store.terminals.all,
|
||||
{
|
||||
id,
|
||||
title: pty.data?.title ?? "Terminal",
|
||||
},
|
||||
])
|
||||
setStore("terminals", "active", id)
|
||||
})
|
||||
},
|
||||
update(pty: Partial<LocalPTY> & { id: string }) {
|
||||
setStore("terminals", "all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x)))
|
||||
sdk.client.pty.update({
|
||||
path: { id: pty.id },
|
||||
body: { title: pty.title, size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined },
|
||||
})
|
||||
},
|
||||
async clone(id: string) {
|
||||
const index = store.terminals.all.findIndex((x) => x.id === id)
|
||||
const pty = store.terminals.all[index]
|
||||
if (!pty) return
|
||||
const clone = await sdk.client.pty.create({
|
||||
body: {
|
||||
title: pty.title,
|
||||
},
|
||||
})
|
||||
if (!clone.data) return
|
||||
setStore("terminals", "all", index, {
|
||||
...pty,
|
||||
...clone.data,
|
||||
})
|
||||
if (store.terminals.active === pty.id) {
|
||||
setStore("terminals", "active", clone.data.id)
|
||||
}
|
||||
},
|
||||
open(id: string) {
|
||||
setStore("terminals", "active", id)
|
||||
},
|
||||
async close(id: string) {
|
||||
batch(() => {
|
||||
setStore(
|
||||
"terminals",
|
||||
"all",
|
||||
store.terminals.all.filter((x) => x.id !== id),
|
||||
)
|
||||
if (store.terminals.active === id) {
|
||||
const index = store.terminals.all.findIndex((f) => f.id === id)
|
||||
const previous = store.tabs.all[Math.max(0, index - 1)]
|
||||
setStore("terminals", "active", previous)
|
||||
}
|
||||
})
|
||||
await sdk.client.pty.remove({ path: { id } })
|
||||
},
|
||||
move(id: string, to: number) {
|
||||
const index = store.terminals.all.findIndex((f) => f.id === id)
|
||||
if (index === -1) return
|
||||
setStore(
|
||||
"terminals",
|
||||
"all",
|
||||
produce((all) => {
|
||||
all.splice(to, 0, all.splice(index, 1)[0])
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createMemo, For, ParentProps, Show } from "solid-js"
|
||||
import { DateTime } from "luxon"
|
||||
import { A, useParams } from "@solidjs/router"
|
||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { base64Encode } from "@/utils"
|
||||
import { base64Decode, base64Encode } from "@/utils"
|
||||
import { Mark } from "@opencode-ai/ui/logo"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
@@ -12,11 +12,22 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { Collapsible } from "@opencode-ai/ui/collapsible"
|
||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import { Select } from "@opencode-ai/ui/select"
|
||||
import { Session } from "@opencode-ai/sdk/client"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const navigate = useNavigate()
|
||||
const params = useParams()
|
||||
const globalSync = useGlobalSync()
|
||||
const layout = useLayout()
|
||||
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
||||
const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? [])
|
||||
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
|
||||
|
||||
function navigateToSession(session: Session | undefined) {
|
||||
if (!session) return
|
||||
navigate(`/${params.dir}/session/${session?.id}`)
|
||||
}
|
||||
|
||||
const handleOpenProject = async () => {
|
||||
// layout.projects.open(dir.)
|
||||
@@ -24,7 +35,7 @@ export default function Layout(props: ParentProps) {
|
||||
|
||||
return (
|
||||
<div class="relative h-screen flex flex-col">
|
||||
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base">
|
||||
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region>
|
||||
<A
|
||||
href="/"
|
||||
classList={{
|
||||
@@ -33,19 +44,115 @@ export default function Layout(props: ParentProps) {
|
||||
"border-r border-border-weak-base": true,
|
||||
}}
|
||||
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<Mark class="shrink-0" />
|
||||
</A>
|
||||
<div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Select
|
||||
options={layout.projects.list().map((project) => getFilename(project.directory))}
|
||||
current={getFilename(currentDirectory())}
|
||||
class="text-14-regular text-text-base"
|
||||
variant="ghost"
|
||||
/>
|
||||
<div class="text-text-weaker">/</div>
|
||||
<Select
|
||||
options={sessions()}
|
||||
current={currentSession()}
|
||||
placeholder="Select session"
|
||||
label={(x) => x.title}
|
||||
value={(x) => x.id}
|
||||
onSelect={navigateToSession}
|
||||
class="text-14-regular text-text-base max-w-3xs"
|
||||
variant="ghost"
|
||||
/>
|
||||
</div>
|
||||
<Button as={A} href={`/${params.dir}/session`} icon="plus-small">
|
||||
New session
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<Tooltip
|
||||
class="shrink-0"
|
||||
value={
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Toggle terminal</span>
|
||||
<span class="text-icon-base text-12-medium">Ctrl `</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
|
||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||
<Icon
|
||||
size="small"
|
||||
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
|
||||
class="group-hover/terminal-toggle:hidden"
|
||||
/>
|
||||
<Icon
|
||||
size="small"
|
||||
name="layout-bottom-partial"
|
||||
class="hidden group-hover/terminal-toggle:inline-block"
|
||||
/>
|
||||
<Icon
|
||||
size="small"
|
||||
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
|
||||
class="hidden group-active/terminal-toggle:inline-block"
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="h-[calc(100vh-3rem)] flex">
|
||||
<div
|
||||
classList={{
|
||||
"@container w-12 pb-5 shrink-0 bg-background-base": true,
|
||||
"relative @container w-12 pb-5 shrink-0 bg-background-base": true,
|
||||
"flex flex-col gap-5.5 items-start self-stretch justify-between": true,
|
||||
"border-r border-border-weak-base": true,
|
||||
}}
|
||||
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
|
||||
>
|
||||
<Show when={layout.sidebar.opened()}>
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 z-10 w-2 translate-x-1/2 cursor-ew-resize"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
const startX = e.clientX
|
||||
const startWidth = layout.sidebar.width()
|
||||
const maxWidth = window.innerWidth * 0.3
|
||||
const minWidth = 150
|
||||
const collapseThreshold = 80
|
||||
let currentWidth = startWidth
|
||||
|
||||
document.body.style.userSelect = "none"
|
||||
document.body.style.overflow = "hidden"
|
||||
|
||||
const onMouseMove = (moveEvent: MouseEvent) => {
|
||||
const deltaX = moveEvent.clientX - startX
|
||||
currentWidth = startWidth + deltaX
|
||||
const clampedWidth = Math.min(maxWidth, Math.max(minWidth, currentWidth))
|
||||
layout.sidebar.resize(clampedWidth)
|
||||
}
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.body.style.userSelect = ""
|
||||
document.body.style.overflow = ""
|
||||
document.removeEventListener("mousemove", onMouseMove)
|
||||
document.removeEventListener("mouseup", onMouseUp)
|
||||
|
||||
if (currentWidth < collapseThreshold) {
|
||||
layout.sidebar.close()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousemove", onMouseMove)
|
||||
document.addEventListener("mouseup", onMouseUp)
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
<div class="grow flex flex-col items-start self-stretch gap-4 p-2 min-h-0">
|
||||
<Tooltip class="shrink-0" placement="right" value="Toggle sidebar" inactive={layout.sidebar.opened()}>
|
||||
<Button
|
||||
@@ -109,7 +216,7 @@ export default function Layout(props: ParentProps) {
|
||||
>
|
||||
<Tooltip placement="right" value={session.title}>
|
||||
<div
|
||||
class="w-full px-2 py-1 rounded-md
|
||||
class="w-full px-2 py-1 rounded-md
|
||||
group-data-[active=true]/session:bg-surface-raised-base-hover
|
||||
group-hover/session:bg-surface-raised-base-hover
|
||||
group-focus/session:bg-surface-raised-base-hover"
|
||||
@@ -197,7 +304,7 @@ export default function Layout(props: ParentProps) {
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<main class="size-full overflow-x-hidden">{props.children}</main>
|
||||
<main class="size-full overflow-x-hidden flex flex-col items-start">{props.children}</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo } from "solid-js"
|
||||
import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect } from "solid-js"
|
||||
import { useLocal, type LocalFile } from "@/context/local"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { PromptInput } from "@/components/prompt-input"
|
||||
@@ -31,6 +31,7 @@ import { useSession } from "@/context/session"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||
import { Diff } from "@opencode-ai/ui/diff"
|
||||
import { Terminal } from "@/components/terminal"
|
||||
|
||||
export default function Page() {
|
||||
const layout = useLayout()
|
||||
@@ -54,6 +55,14 @@ export default function Page() {
|
||||
document.removeEventListener("keydown", handleKeyDown)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (layout.terminal.opened()) {
|
||||
if (session.terminal.all().length === 0) {
|
||||
session.terminal.new()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") {
|
||||
event.preventDefault()
|
||||
@@ -73,6 +82,20 @@ export default function Page() {
|
||||
document.documentElement.setAttribute("data-theme", nextTheme)
|
||||
return
|
||||
}
|
||||
if (event.ctrlKey && event.key.toLowerCase() === "`") {
|
||||
event.preventDefault()
|
||||
if (event.shiftKey) {
|
||||
session.terminal.new()
|
||||
return
|
||||
}
|
||||
layout.terminal.toggle()
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
if (document.activeElement?.dataset?.component === "terminal") {
|
||||
return
|
||||
}
|
||||
|
||||
const focused = document.activeElement === inputRef
|
||||
if (focused) {
|
||||
@@ -141,7 +164,7 @@ export default function Page() {
|
||||
const handleDragOver = (event: DragEvent) => {
|
||||
const { draggable, droppable } = event
|
||||
if (draggable && droppable) {
|
||||
const currentTabs = session.layout.tabs.opened
|
||||
const currentTabs = session.layout.tabs.all
|
||||
const fromIndex = currentTabs?.indexOf(draggable.id.toString())
|
||||
const toIndex = currentTabs?.indexOf(droppable.id.toString())
|
||||
if (fromIndex !== toIndex && toIndex !== undefined) {
|
||||
@@ -259,317 +282,401 @@ export default function Page() {
|
||||
const wide = createMemo(() => layout.review.state() === "tab" || !session.diffs().length)
|
||||
|
||||
return (
|
||||
<div class="relative bg-background-base size-full overflow-x-hidden">
|
||||
<DragDropProvider
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
collisionDetector={closestCenter}
|
||||
>
|
||||
<DragDropSensors />
|
||||
<ConstrainDragYAxis />
|
||||
<Tabs value={session.layout.tabs.active ?? "chat"} onChange={session.layout.openTab}>
|
||||
<div class="sticky top-0 shrink-0 flex">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="chat">
|
||||
<div class="flex gap-x-[17px] items-center">
|
||||
<div>Session</div>
|
||||
<Tooltip
|
||||
value={`${new Intl.NumberFormat("en-US", {
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
}).format(session.usage.tokens() ?? 0)} Tokens`}
|
||||
class="flex items-center gap-1.5"
|
||||
>
|
||||
<ProgressCircle percentage={session.usage.context() ?? 0} />
|
||||
<div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tabs.Trigger>
|
||||
<Show when={layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Trigger
|
||||
value="review"
|
||||
closeButton={
|
||||
<IconButton icon="collapse" size="normal" variant="ghost" onClick={layout.review.pane} />
|
||||
}
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<Show when={session.diffs()}>
|
||||
<DiffChanges changes={session.diffs()} variant="bars" />
|
||||
</Show>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div>Review</div>
|
||||
<Show when={session.info()?.summary?.files}>
|
||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
||||
{session.info()?.summary?.files ?? 0}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="relative bg-background-base size-full overflow-x-hidden flex flex-col items-start">
|
||||
<div class="min-h-0 grow w-full">
|
||||
<DragDropProvider
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
collisionDetector={closestCenter}
|
||||
>
|
||||
<DragDropSensors />
|
||||
<ConstrainDragYAxis />
|
||||
<Tabs value={session.layout.tabs.active ?? "chat"} onChange={session.layout.openTab}>
|
||||
<div class="sticky top-0 shrink-0 flex">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="chat">
|
||||
<div class="flex gap-x-[17px] items-center">
|
||||
<div>Session</div>
|
||||
<Tooltip
|
||||
value={`${new Intl.NumberFormat("en-US", {
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
}).format(session.usage.tokens() ?? 0)} Tokens`}
|
||||
class="flex items-center gap-1.5"
|
||||
>
|
||||
<ProgressCircle percentage={session.usage.context() ?? 0} />
|
||||
<div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tabs.Trigger>
|
||||
</Show>
|
||||
<SortableProvider ids={session.layout.tabs.opened ?? []}>
|
||||
<For each={session.layout.tabs.opened ?? []}>
|
||||
{(tab) => <SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={session.layout.closeTab} />}
|
||||
</For>
|
||||
</SortableProvider>
|
||||
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
|
||||
<Tooltip value="Open file" class="flex items-center">
|
||||
<IconButton
|
||||
icon="plus-small"
|
||||
variant="ghost"
|
||||
iconSize="large"
|
||||
onClick={() => setStore("fileSelectOpen", true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tabs.List>
|
||||
</div>
|
||||
<Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
|
||||
<div
|
||||
classList={{
|
||||
"w-full flex-1 min-h-0": true,
|
||||
grid: layout.review.state() === "tab",
|
||||
flex: layout.review.state() === "pane",
|
||||
}}
|
||||
>
|
||||
<Show when={layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Trigger
|
||||
value="review"
|
||||
closeButton={
|
||||
<IconButton icon="collapse" size="normal" variant="ghost" onClick={layout.review.pane} />
|
||||
}
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<Show when={session.diffs()}>
|
||||
<DiffChanges changes={session.diffs()} variant="bars" />
|
||||
</Show>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div>Review</div>
|
||||
<Show when={session.info()?.summary?.files}>
|
||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
||||
{session.info()?.summary?.files ?? 0}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Trigger>
|
||||
</Show>
|
||||
<SortableProvider ids={session.layout.tabs.all ?? []}>
|
||||
<For each={session.layout.tabs.all ?? []}>
|
||||
{(tab) => (
|
||||
<SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={session.layout.closeTab} />
|
||||
)}
|
||||
</For>
|
||||
</SortableProvider>
|
||||
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
|
||||
<Tooltip value="Open file" class="flex items-center">
|
||||
<IconButton
|
||||
icon="plus-small"
|
||||
variant="ghost"
|
||||
iconSize="large"
|
||||
onClick={() => setStore("fileSelectOpen", true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tabs.List>
|
||||
</div>
|
||||
<Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
|
||||
<div
|
||||
classList={{
|
||||
"relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full": true,
|
||||
"max-w-146 mx-auto": !wide(),
|
||||
"w-full flex-1 min-h-0": true,
|
||||
grid: layout.review.state() === "tab",
|
||||
flex: layout.review.state() === "pane",
|
||||
}}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={session.id}>
|
||||
<div class="flex items-start justify-start h-full min-h-0">
|
||||
<SessionMessageRail
|
||||
messages={session.messages.user()}
|
||||
current={session.messages.active()}
|
||||
onMessageSelect={session.messages.setActive}
|
||||
working={session.working()}
|
||||
wide={wide()}
|
||||
/>
|
||||
<SessionTurn
|
||||
sessionID={session.id!}
|
||||
messageID={session.messages.active()?.id!}
|
||||
classes={{
|
||||
root: "pb-20 flex-1 min-w-0",
|
||||
content: "pb-20",
|
||||
container:
|
||||
"w-full " +
|
||||
(wide()
|
||||
? "max-w-146 mx-auto px-6"
|
||||
: session.messages.user().length > 1
|
||||
? "pr-6 pl-18"
|
||||
: "px-6"),
|
||||
}}
|
||||
diffComponent={Diff}
|
||||
/>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-146 mx-auto px-6">
|
||||
<div class="text-20-medium text-text-weaker">New session</div>
|
||||
<div class="flex justify-center items-center gap-3">
|
||||
<Icon name="folder" size="small" />
|
||||
<div class="text-12-medium text-text-weak">
|
||||
{getDirectory(sync.data.path.directory)}
|
||||
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center items-center gap-3">
|
||||
<Icon name="pencil-line" size="small" />
|
||||
<div class="text-12-medium text-text-weak">
|
||||
Last modified
|
||||
<span class="text-text-strong">
|
||||
{DateTime.fromMillis(sync.data.project.time.created).toRelative()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
<div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
|
||||
<div class="w-full max-w-146 px-6">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={layout.review.state() === "pane" && session.diffs().length}>
|
||||
<div
|
||||
classList={{
|
||||
"relative grow pt-3 flex-1 min-h-0 border-l border-border-weak-base": true,
|
||||
"relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full": true,
|
||||
"max-w-146 mx-auto": !wide(),
|
||||
}}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={session.id}>
|
||||
<div class="flex items-start justify-start h-full min-h-0">
|
||||
<SessionMessageRail
|
||||
messages={session.messages.user()}
|
||||
current={session.messages.active()}
|
||||
onMessageSelect={session.messages.setActive}
|
||||
working={session.working()}
|
||||
wide={wide()}
|
||||
/>
|
||||
<SessionTurn
|
||||
sessionID={session.id!}
|
||||
messageID={session.messages.active()?.id!}
|
||||
classes={{
|
||||
root: "pb-20 flex-1 min-w-0",
|
||||
content: "pb-20",
|
||||
container:
|
||||
"w-full " +
|
||||
(wide()
|
||||
? "max-w-146 mx-auto px-6"
|
||||
: session.messages.user().length > 1
|
||||
? "pr-6 pl-18"
|
||||
: "px-6"),
|
||||
}}
|
||||
diffComponent={Diff}
|
||||
/>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-146 mx-auto px-6">
|
||||
<div class="text-20-medium text-text-weaker">New session</div>
|
||||
<div class="flex justify-center items-center gap-3">
|
||||
<Icon name="folder" size="small" />
|
||||
<div class="text-12-medium text-text-weak">
|
||||
{getDirectory(sync.data.path.directory)}
|
||||
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center items-center gap-3">
|
||||
<Icon name="pencil-line" size="small" />
|
||||
<div class="text-12-medium text-text-weak">
|
||||
Last modified
|
||||
<span class="text-text-strong">
|
||||
{DateTime.fromMillis(sync.data.project.time.created).toRelative()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
<div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
|
||||
<div class="w-full max-w-146 px-6">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={layout.review.state() === "pane" && session.diffs().length}>
|
||||
<div
|
||||
classList={{
|
||||
"relative grow pt-3 flex-1 min-h-0 border-l border-border-weak-base": true,
|
||||
}}
|
||||
>
|
||||
<SessionReview
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
diffs={session.diffs()}
|
||||
diffComponent={Diff}
|
||||
actions={
|
||||
<Tooltip value="Open in tab">
|
||||
<IconButton
|
||||
icon="expand"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
layout.review.tab()
|
||||
session.layout.setActiveTab("review")
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Show when={layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden">
|
||||
<div
|
||||
classList={{
|
||||
"relative pt-3 flex-1 min-h-0 overflow-hidden": true,
|
||||
}}
|
||||
>
|
||||
<SessionReview
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
root: "pb-40",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
diffs={session.diffs()}
|
||||
diffComponent={Diff}
|
||||
actions={
|
||||
<Tooltip value="Open in tab">
|
||||
<IconButton
|
||||
icon="expand"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
layout.review.tab()
|
||||
session.layout.setActiveTab("review")
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
split
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Show when={layout.review.state() === "tab" && session.diffs().length}>
|
||||
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden">
|
||||
</Tabs.Content>
|
||||
</Show>
|
||||
<For each={session.layout.tabs.all}>
|
||||
{(tab) => {
|
||||
const [file] = createResource(
|
||||
() => tab,
|
||||
async (tab) => {
|
||||
if (tab.startsWith("file://")) {
|
||||
return local.file.node(tab.replace("file://", ""))
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
return (
|
||||
<Tabs.Content value={tab} class="select-text mt-3">
|
||||
<Switch>
|
||||
<Match when={file()}>
|
||||
{(f) => (
|
||||
<Code
|
||||
file={{ name: f().path, contents: f().content?.content ?? "" }}
|
||||
overflow="scroll"
|
||||
class="pb-40"
|
||||
/>
|
||||
)}
|
||||
</Match>
|
||||
</Switch>
|
||||
</Tabs.Content>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</Tabs>
|
||||
<DragOverlay>
|
||||
<Show when={store.activeDraggable}>
|
||||
{(draggedFile) => {
|
||||
const [file] = createResource(
|
||||
() => draggedFile(),
|
||||
async (tab) => {
|
||||
if (tab.startsWith("file://")) {
|
||||
return local.file.node(tab.replace("file://", ""))
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
return (
|
||||
<div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
|
||||
<Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
</DragOverlay>
|
||||
</DragDropProvider>
|
||||
<Show when={session.layout.tabs.active}>
|
||||
<div class="absolute inset-x-0 px-6 max-w-146 flex flex-col justify-center items-center z-50 mx-auto bottom-8">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="hidden shrink-0 w-56 p-2 h-full overflow-y-auto">
|
||||
{/* <FileTree path="" onFileClick={ handleTabClick} /> */}
|
||||
</div>
|
||||
<div class="hidden shrink-0 w-56 p-2">
|
||||
<Show
|
||||
when={local.file.changes().length}
|
||||
fallback={<div class="px-2 text-xs text-text-muted">No changes</div>}
|
||||
>
|
||||
<ul class="">
|
||||
<For each={local.file.changes()}>
|
||||
{(path) => (
|
||||
<li>
|
||||
<button
|
||||
onClick={() => local.file.open(path, { view: "diff-unified", pinned: true })}
|
||||
class="w-full flex items-center px-2 py-0.5 gap-x-2 text-text-muted grow min-w-0 hover:bg-background-element"
|
||||
>
|
||||
<FileIcon node={{ path, type: "file" }} class="shrink-0 size-3" />
|
||||
<span class="text-xs text-text whitespace-nowrap">{getFilename(path)}</span>
|
||||
<span class="text-xs text-text-muted/60 whitespace-nowrap truncate min-w-0">
|
||||
{getDirectory(path)}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={store.fileSelectOpen}>
|
||||
<SelectDialog
|
||||
defaultOpen
|
||||
title="Select file"
|
||||
placeholder="Search files"
|
||||
emptyMessage="No files found"
|
||||
items={local.file.searchFiles}
|
||||
key={(x) => x}
|
||||
onOpenChange={(open) => setStore("fileSelectOpen", open)}
|
||||
onSelect={(x) => {
|
||||
if (x) {
|
||||
local.file.open(x)
|
||||
return session.layout.openTab("file://" + x)
|
||||
}
|
||||
return undefined
|
||||
}}
|
||||
>
|
||||
{(i) => (
|
||||
<div
|
||||
classList={{
|
||||
"relative pt-3 flex-1 min-h-0 overflow-hidden": true,
|
||||
"w-full flex items-center justify-between rounded-md": true,
|
||||
}}
|
||||
>
|
||||
<SessionReview
|
||||
classes={{
|
||||
root: "pb-40",
|
||||
header: "px-6",
|
||||
container: "px-6",
|
||||
}}
|
||||
diffs={session.diffs()}
|
||||
diffComponent={Diff}
|
||||
split
|
||||
/>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Show>
|
||||
<For each={session.layout.tabs.opened}>
|
||||
{(tab) => {
|
||||
const [file] = createResource(
|
||||
() => tab,
|
||||
async (tab) => {
|
||||
if (tab.startsWith("file://")) {
|
||||
return local.file.node(tab.replace("file://", ""))
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
return (
|
||||
<Tabs.Content value={tab} class="select-text mt-3">
|
||||
<Switch>
|
||||
<Match when={file()}>
|
||||
{(f) => (
|
||||
<Code
|
||||
file={{ name: f().path, contents: f().content?.content ?? "" }}
|
||||
overflow="scroll"
|
||||
class="pb-40"
|
||||
/>
|
||||
)}
|
||||
</Match>
|
||||
</Switch>
|
||||
</Tabs.Content>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</Tabs>
|
||||
<DragOverlay>
|
||||
<Show when={store.activeDraggable}>
|
||||
{(draggedFile) => {
|
||||
const [file] = createResource(
|
||||
() => draggedFile(),
|
||||
async (tab) => {
|
||||
if (tab.startsWith("file://")) {
|
||||
return local.file.node(tab.replace("file://", ""))
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
return (
|
||||
<div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
|
||||
<Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
</DragOverlay>
|
||||
</DragDropProvider>
|
||||
<Show when={session.layout.tabs.active}>
|
||||
<div class="absolute inset-x-0 px-6 max-w-146 flex flex-col justify-center items-center z-50 mx-auto bottom-8">
|
||||
<PromptInput
|
||||
ref={(el) => {
|
||||
inputRef = el
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="hidden shrink-0 w-56 p-2 h-full overflow-y-auto">
|
||||
{/* <FileTree path="" onFileClick={ handleTabClick} /> */}
|
||||
</div>
|
||||
<div class="hidden shrink-0 w-56 p-2">
|
||||
<Show when={local.file.changes().length} fallback={<div class="px-2 text-xs text-text-muted">No changes</div>}>
|
||||
<ul class="">
|
||||
<For each={local.file.changes()}>
|
||||
{(path) => (
|
||||
<li>
|
||||
<button
|
||||
onClick={() => local.file.open(path, { view: "diff-unified", pinned: true })}
|
||||
class="w-full flex items-center px-2 py-0.5 gap-x-2 text-text-muted grow min-w-0 hover:bg-background-element"
|
||||
>
|
||||
<FileIcon node={{ path, type: "file" }} class="shrink-0 size-3" />
|
||||
<span class="text-xs text-text whitespace-nowrap">{getFilename(path)}</span>
|
||||
<span class="text-xs text-text-muted/60 whitespace-nowrap truncate min-w-0">
|
||||
{getDirectory(path)}
|
||||
<div class="flex items-center gap-x-2 grow min-w-0">
|
||||
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
|
||||
<div class="flex items-center text-14-regular">
|
||||
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
||||
{getDirectory(i)}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
|
||||
</div>
|
||||
)}
|
||||
</SelectDialog>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={store.fileSelectOpen}>
|
||||
<SelectDialog
|
||||
defaultOpen
|
||||
title="Select file"
|
||||
placeholder="Search files"
|
||||
emptyMessage="No files found"
|
||||
items={local.file.searchFiles}
|
||||
key={(x) => x}
|
||||
onOpenChange={(open) => setStore("fileSelectOpen", open)}
|
||||
onSelect={(x) => {
|
||||
if (x) {
|
||||
local.file.open(x)
|
||||
return session.layout.openTab("file://" + x)
|
||||
}
|
||||
return undefined
|
||||
}}
|
||||
<Show when={layout.terminal.opened()}>
|
||||
<div
|
||||
class="relative w-full flex flex-col shrink-0 border-t border-border-weak-base"
|
||||
style={{ height: `${layout.terminal.height()}px` }}
|
||||
>
|
||||
{(i) => (
|
||||
<div
|
||||
classList={{
|
||||
"w-full flex items-center justify-between rounded-md": true,
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center gap-x-2 grow min-w-0">
|
||||
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
|
||||
<div class="flex items-center text-14-regular">
|
||||
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
||||
{getDirectory(i)}
|
||||
</span>
|
||||
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-x-0 top-0 z-10 h-2 -translate-y-1/2 cursor-ns-resize"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
const startY = e.clientY
|
||||
const startHeight = layout.terminal.height()
|
||||
const maxHeight = window.innerHeight * 0.6
|
||||
const minHeight = 100
|
||||
const collapseThreshold = 50
|
||||
let currentHeight = startHeight
|
||||
|
||||
document.body.style.userSelect = "none"
|
||||
document.body.style.overflow = "hidden"
|
||||
|
||||
const onMouseMove = (moveEvent: MouseEvent) => {
|
||||
const deltaY = startY - moveEvent.clientY
|
||||
currentHeight = startHeight + deltaY
|
||||
const clampedHeight = Math.min(maxHeight, Math.max(minHeight, currentHeight))
|
||||
layout.terminal.resize(clampedHeight)
|
||||
}
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.body.style.userSelect = ""
|
||||
document.body.style.overflow = ""
|
||||
document.removeEventListener("mousemove", onMouseMove)
|
||||
document.removeEventListener("mouseup", onMouseUp)
|
||||
|
||||
if (currentHeight < collapseThreshold) {
|
||||
layout.terminal.close()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousemove", onMouseMove)
|
||||
document.addEventListener("mouseup", onMouseUp)
|
||||
}}
|
||||
/>
|
||||
<Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}>
|
||||
<Tabs.List class="h-10">
|
||||
<For each={session.terminal.all()}>
|
||||
{(terminal) => (
|
||||
<Tabs.Trigger
|
||||
value={terminal.id}
|
||||
closeButton={
|
||||
session.terminal.all().length > 1 && (
|
||||
<IconButton icon="close" variant="ghost" onClick={() => session.terminal.close(terminal.id)} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{terminal.title}
|
||||
</Tabs.Trigger>
|
||||
)}
|
||||
</For>
|
||||
<div class="h-full flex items-center justify-center">
|
||||
<Tooltip value="Open file" class="flex items-center">
|
||||
<IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={session.terminal.new} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
|
||||
</div>
|
||||
)}
|
||||
</SelectDialog>
|
||||
</Tabs.List>
|
||||
<For each={session.terminal.all()}>
|
||||
{(terminal) => (
|
||||
<Tabs.Content value={terminal.id}>
|
||||
<Terminal
|
||||
pty={terminal}
|
||||
onCleanup={session.terminal.update}
|
||||
onConnectError={() => session.terminal.clone(terminal.id)}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
)}
|
||||
</For>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { defineConfig } from "vite"
|
||||
import solidPlugin from "vite-plugin-solid"
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
import path from "path"
|
||||
import desktopPlugin from "./vite"
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
plugins: [tailwindcss(), solidPlugin()] as any,
|
||||
plugins: [desktopPlugin] as any,
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
allowedHosts: true,
|
||||
@@ -18,7 +11,4 @@ export default defineConfig({
|
||||
build: {
|
||||
target: "esnext",
|
||||
},
|
||||
worker: {
|
||||
format: "es",
|
||||
},
|
||||
})
|
||||
|
||||
26
packages/desktop/vite.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import solidPlugin from "vite-plugin-solid"
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
/**
|
||||
* @type {import("vite").PluginOption}
|
||||
*/
|
||||
export default [
|
||||
{
|
||||
name: "opencode-desktop:config",
|
||||
config() {
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
format: "es",
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
tailwindcss(),
|
||||
solidPlugin(),
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The AI coding agent built for the terminal"
|
||||
version = "1.0.129"
|
||||
version = "1.0.134"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/sst/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/opencode-linux-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-linux-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/opencode-linux-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-linux-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.134/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -64,14 +64,15 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.2.8",
|
||||
"@opentui/core": "0.1.55",
|
||||
"@opentui/solid": "0.1.55",
|
||||
"@opentui/core": "0.1.56",
|
||||
"@opentui/solid": "0.1.56",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"ai": "catalog:",
|
||||
"bun-pty": "0.4.2",
|
||||
"chokidar": "4.0.3",
|
||||
"clipboardy": "4.0.0",
|
||||
"decimal.js": "10.5.0",
|
||||
|
||||
@@ -33,6 +33,7 @@ export namespace Agent {
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.string(), z.boolean()),
|
||||
options: z.record(z.string(), z.any()),
|
||||
maxSteps: z.number().int().positive().optional(),
|
||||
})
|
||||
.meta({
|
||||
ref: "Agent",
|
||||
@@ -182,7 +183,20 @@ export namespace Agent {
|
||||
tools: {},
|
||||
builtIn: false,
|
||||
}
|
||||
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, color, ...extra } = value
|
||||
const {
|
||||
name,
|
||||
model,
|
||||
prompt,
|
||||
tools,
|
||||
description,
|
||||
temperature,
|
||||
top_p,
|
||||
mode,
|
||||
permission,
|
||||
color,
|
||||
maxSteps,
|
||||
...extra
|
||||
} = value
|
||||
item.options = {
|
||||
...item.options,
|
||||
...extra,
|
||||
@@ -205,6 +219,7 @@ export namespace Agent {
|
||||
if (color) item.color = color
|
||||
// just here for consistency & to prevent it from being added as an option
|
||||
if (name) item.name = name
|
||||
if (maxSteps != undefined) item.maxSteps = maxSteps
|
||||
|
||||
if (permission ?? cfg.permission) {
|
||||
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
|
||||
@@ -222,12 +237,15 @@ export namespace Agent {
|
||||
}
|
||||
|
||||
export async function generate(input: { description: string }) {
|
||||
const cfg = await Config.get()
|
||||
const defaultModel = await Provider.defaultModel()
|
||||
const model = await Provider.getModel(defaultModel.providerID, defaultModel.modelID)
|
||||
const language = await Provider.getLanguage(model)
|
||||
const system = SystemPrompt.header(defaultModel.providerID)
|
||||
system.push(PROMPT_GENERATE)
|
||||
const existing = await list()
|
||||
const result = await generateObject({
|
||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
||||
temperature: 0.3,
|
||||
prompt: [
|
||||
...system.map(
|
||||
@@ -241,7 +259,7 @@ export namespace Agent {
|
||||
content: `Create an agent configuration based on this request: \"${input.description}\".\n\nIMPORTANT: The following identifiers already exist and must NOT be used: ${existing.map((i) => i.name).join(", ")}\n Return ONLY the JSON object, no other text, do not wrap in backticks`,
|
||||
},
|
||||
],
|
||||
model: model.language,
|
||||
model: language,
|
||||
schema: z.object({
|
||||
identifier: z.string(),
|
||||
whenToUse: z.string(),
|
||||
|
||||
@@ -562,6 +562,11 @@ export const GithubRunCommand = cmd({
|
||||
}
|
||||
|
||||
async function getUserPrompt() {
|
||||
const customPrompt = process.env["PROMPT"]
|
||||
if (customPrompt) {
|
||||
return { userPrompt: customPrompt, promptFiles: [] }
|
||||
}
|
||||
|
||||
const reviewContext = getReviewCommentContext()
|
||||
let prompt = (() => {
|
||||
const body = payload.comment.body.trim()
|
||||
|
||||
@@ -38,7 +38,7 @@ export const ModelsCommand = cmd({
|
||||
|
||||
function printModels(providerID: string, verbose?: boolean) {
|
||||
const provider = providers[providerID]
|
||||
const sortedModels = Object.entries(provider.info.models).sort(([a], [b]) => a.localeCompare(b))
|
||||
const sortedModels = Object.entries(provider.models).sort(([a], [b]) => a.localeCompare(b))
|
||||
for (const [modelID, model] of sortedModels) {
|
||||
process.stdout.write(`${providerID}/${modelID}`)
|
||||
process.stdout.write(EOL)
|
||||
|
||||
@@ -32,6 +32,7 @@ import { KVProvider, useKV } from "./context/kv"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ArgsProvider, useArgs, type Args } from "./context/args"
|
||||
import open from "open"
|
||||
import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
||||
|
||||
async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
||||
// can't set raw mode if not a TTY
|
||||
@@ -119,7 +120,9 @@ export function tui(input: { url: string; args: Args; onExit?: () => Promise<voi
|
||||
<DialogProvider>
|
||||
<CommandProvider>
|
||||
<PromptHistoryProvider>
|
||||
<App />
|
||||
<PromptRefProvider>
|
||||
<App />
|
||||
</PromptRefProvider>
|
||||
</PromptHistoryProvider>
|
||||
</CommandProvider>
|
||||
</DialogProvider>
|
||||
@@ -160,6 +163,7 @@ function App() {
|
||||
const { theme, mode, setMode } = useTheme()
|
||||
const sync = useSync()
|
||||
const exit = useExit()
|
||||
const promptRef = usePromptRef()
|
||||
|
||||
createEffect(() => {
|
||||
console.log(JSON.stringify(route.data))
|
||||
@@ -225,8 +229,12 @@ function App() {
|
||||
keybind: "session_new",
|
||||
category: "Session",
|
||||
onSelect: () => {
|
||||
const current = promptRef.current
|
||||
// Don't require focus - if there's any text, preserve it
|
||||
const currentPrompt = current?.current?.input ? current.current : undefined
|
||||
route.navigate({
|
||||
type: "home",
|
||||
initialPrompt: currentPrompt,
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
|
||||
@@ -352,8 +352,8 @@ export function Autocomplete(props: {
|
||||
function select() {
|
||||
const selected = options()[store.selected]
|
||||
if (!selected) return
|
||||
selected.onSelect?.()
|
||||
hide()
|
||||
selected.onSelect?.()
|
||||
}
|
||||
|
||||
function show(mode: "@" | "/") {
|
||||
@@ -374,6 +374,10 @@ export function Autocomplete(props: {
|
||||
if (store.visible === "/" && !text.endsWith(" ") && text.startsWith("/")) {
|
||||
const cursor = props.input().logicalCursor
|
||||
props.input().deleteRange(0, 0, cursor.row, cursor.col)
|
||||
// Sync the prompt store immediately since onContentChange is async
|
||||
props.setPrompt((draft) => {
|
||||
draft.input = props.input().plainText
|
||||
})
|
||||
}
|
||||
command.keybinds(true)
|
||||
setStore("visible", false)
|
||||
|
||||
@@ -37,6 +37,7 @@ export type PromptProps = {
|
||||
|
||||
export type PromptRef = {
|
||||
focused: boolean
|
||||
current: PromptInfo
|
||||
set(prompt: PromptInfo): void
|
||||
reset(): void
|
||||
blur(): void
|
||||
@@ -269,7 +270,7 @@ export function Prompt(props: PromptProps) {
|
||||
|
||||
createEffect(() => {
|
||||
if (props.disabled) input.cursorColor = theme.backgroundElement
|
||||
if (!props.disabled) input.cursorColor = theme.primary
|
||||
if (!props.disabled) input.cursorColor = theme.text
|
||||
})
|
||||
|
||||
const [store, setStore] = createStore<{
|
||||
@@ -377,6 +378,9 @@ export function Prompt(props: PromptProps) {
|
||||
get focused() {
|
||||
return input.focused
|
||||
},
|
||||
get current() {
|
||||
return store.prompt
|
||||
},
|
||||
focus() {
|
||||
input.focus()
|
||||
},
|
||||
@@ -805,12 +809,12 @@ export function Prompt(props: PromptProps) {
|
||||
ref={(r: TextareaRenderable) => {
|
||||
input = r
|
||||
setTimeout(() => {
|
||||
input.cursorColor = highlight()
|
||||
input.cursorColor = theme.text
|
||||
}, 0)
|
||||
}}
|
||||
onMouseDown={(r: MouseEvent) => r.target?.focus()}
|
||||
focusedBackgroundColor={theme.backgroundElement}
|
||||
cursorColor={highlight()}
|
||||
cursorColor={theme.text}
|
||||
syntaxStyle={syntax()}
|
||||
/>
|
||||
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
|
||||
|
||||
18
packages/opencode/src/cli/cmd/tui/context/prompt.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { createSimpleContext } from "./helper"
|
||||
import type { PromptRef } from "../component/prompt"
|
||||
|
||||
export const { use: usePromptRef, provider: PromptRefProvider } = createSimpleContext({
|
||||
name: "PromptRef",
|
||||
init: () => {
|
||||
let current: PromptRef | undefined
|
||||
|
||||
return {
|
||||
get current() {
|
||||
return current
|
||||
},
|
||||
set(ref: PromptRef | undefined) {
|
||||
current = ref
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import type { PromptInfo } from "../component/prompt/history"
|
||||
|
||||
export type HomeRoute = {
|
||||
type: "home"
|
||||
initialPrompt?: PromptInfo
|
||||
}
|
||||
|
||||
export type SessionRoute = {
|
||||
|
||||
@@ -25,6 +25,7 @@ import rosepine from "./theme/rosepine.json" with { type: "json" }
|
||||
import solarized from "./theme/solarized.json" with { type: "json" }
|
||||
import synthwave84 from "./theme/synthwave84.json" with { type: "json" }
|
||||
import tokyonight from "./theme/tokyonight.json" with { type: "json" }
|
||||
import vercel from "./theme/vercel.json" with { type: "json" }
|
||||
import vesper from "./theme/vesper.json" with { type: "json" }
|
||||
import zenburn from "./theme/zenburn.json" with { type: "json" }
|
||||
import { useKV } from "./kv"
|
||||
@@ -149,6 +150,7 @@ export const DEFAULT_THEMES: Record<string, ThemeJson> = {
|
||||
synthwave84,
|
||||
tokyonight,
|
||||
vesper,
|
||||
vercel,
|
||||
zenburn,
|
||||
}
|
||||
|
||||
|
||||
245
packages/opencode/src/cli/cmd/tui/context/theme/vercel.json
Normal file
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/theme.json",
|
||||
"defs": {
|
||||
"background100": "#0A0A0A",
|
||||
"background200": "#000000",
|
||||
"gray100": "#1A1A1A",
|
||||
"gray200": "#1F1F1F",
|
||||
"gray300": "#292929",
|
||||
"gray400": "#2E2E2E",
|
||||
"gray500": "#454545",
|
||||
"gray600": "#878787",
|
||||
"gray700": "#8F8F8F",
|
||||
"gray900": "#A1A1A1",
|
||||
"gray1000": "#EDEDED",
|
||||
"blue600": "#0099FF",
|
||||
"blue700": "#0070F3",
|
||||
"blue900": "#52A8FF",
|
||||
"blue1000": "#EBF8FF",
|
||||
"red700": "#E5484D",
|
||||
"red900": "#FF6166",
|
||||
"red1000": "#FDECED",
|
||||
"amber700": "#FFB224",
|
||||
"amber900": "#F2A700",
|
||||
"amber1000": "#FDF4DC",
|
||||
"green700": "#46A758",
|
||||
"green900": "#63C46D",
|
||||
"green1000": "#E6F9E9",
|
||||
"teal700": "#12A594",
|
||||
"teal900": "#0AC7AC",
|
||||
"purple700": "#8E4EC6",
|
||||
"purple900": "#BF7AF0",
|
||||
"pink700": "#E93D82",
|
||||
"pink900": "#F75590",
|
||||
"highlightPink": "#FF0080",
|
||||
"highlightPurple": "#F81CE5",
|
||||
"cyan": "#50E3C2",
|
||||
"lightBackground": "#FFFFFF",
|
||||
"lightGray100": "#FAFAFA",
|
||||
"lightGray200": "#EAEAEA",
|
||||
"lightGray600": "#666666",
|
||||
"lightGray1000": "#171717"
|
||||
},
|
||||
"theme": {
|
||||
"primary": {
|
||||
"dark": "blue700",
|
||||
"light": "blue700"
|
||||
},
|
||||
"secondary": {
|
||||
"dark": "blue900",
|
||||
"light": "#0062D1"
|
||||
},
|
||||
"accent": {
|
||||
"dark": "purple700",
|
||||
"light": "purple700"
|
||||
},
|
||||
"error": {
|
||||
"dark": "red700",
|
||||
"light": "#DC3545"
|
||||
},
|
||||
"warning": {
|
||||
"dark": "amber700",
|
||||
"light": "#FF9500"
|
||||
},
|
||||
"success": {
|
||||
"dark": "green700",
|
||||
"light": "#388E3C"
|
||||
},
|
||||
"info": {
|
||||
"dark": "blue900",
|
||||
"light": "blue700"
|
||||
},
|
||||
"text": {
|
||||
"dark": "gray1000",
|
||||
"light": "lightGray1000"
|
||||
},
|
||||
"textMuted": {
|
||||
"dark": "gray600",
|
||||
"light": "lightGray600"
|
||||
},
|
||||
"background": {
|
||||
"dark": "background200",
|
||||
"light": "lightBackground"
|
||||
},
|
||||
"backgroundPanel": {
|
||||
"dark": "gray100",
|
||||
"light": "lightGray100"
|
||||
},
|
||||
"backgroundElement": {
|
||||
"dark": "gray300",
|
||||
"light": "lightGray200"
|
||||
},
|
||||
"border": {
|
||||
"dark": "gray200",
|
||||
"light": "lightGray200"
|
||||
},
|
||||
"borderActive": {
|
||||
"dark": "gray500",
|
||||
"light": "#999999"
|
||||
},
|
||||
"borderSubtle": {
|
||||
"dark": "gray100",
|
||||
"light": "#EAEAEA"
|
||||
},
|
||||
"diffAdded": {
|
||||
"dark": "green900",
|
||||
"light": "green700"
|
||||
},
|
||||
"diffRemoved": {
|
||||
"dark": "red900",
|
||||
"light": "red700"
|
||||
},
|
||||
"diffContext": {
|
||||
"dark": "gray600",
|
||||
"light": "lightGray600"
|
||||
},
|
||||
"diffHunkHeader": {
|
||||
"dark": "gray600",
|
||||
"light": "lightGray600"
|
||||
},
|
||||
"diffHighlightAdded": {
|
||||
"dark": "green900",
|
||||
"light": "green700"
|
||||
},
|
||||
"diffHighlightRemoved": {
|
||||
"dark": "red900",
|
||||
"light": "red700"
|
||||
},
|
||||
"diffAddedBg": {
|
||||
"dark": "#0B1D0F",
|
||||
"light": "#E6F9E9"
|
||||
},
|
||||
"diffRemovedBg": {
|
||||
"dark": "#2A1314",
|
||||
"light": "#FDECED"
|
||||
},
|
||||
"diffContextBg": {
|
||||
"dark": "background200",
|
||||
"light": "lightBackground"
|
||||
},
|
||||
"diffLineNumber": {
|
||||
"dark": "gray600",
|
||||
"light": "lightGray600"
|
||||
},
|
||||
"diffAddedLineNumberBg": {
|
||||
"dark": "#0F2613",
|
||||
"light": "#D6F5D6"
|
||||
},
|
||||
"diffRemovedLineNumberBg": {
|
||||
"dark": "#3C1618",
|
||||
"light": "#FFE5E5"
|
||||
},
|
||||
"markdownText": {
|
||||
"dark": "gray1000",
|
||||
"light": "lightGray1000"
|
||||
},
|
||||
"markdownHeading": {
|
||||
"dark": "purple900",
|
||||
"light": "purple700"
|
||||
},
|
||||
"markdownLink": {
|
||||
"dark": "blue900",
|
||||
"light": "blue700"
|
||||
},
|
||||
"markdownLinkText": {
|
||||
"dark": "teal900",
|
||||
"light": "teal700"
|
||||
},
|
||||
"markdownCode": {
|
||||
"dark": "green900",
|
||||
"light": "green700"
|
||||
},
|
||||
"markdownBlockQuote": {
|
||||
"dark": "gray600",
|
||||
"light": "lightGray600"
|
||||
},
|
||||
"markdownEmph": {
|
||||
"dark": "amber900",
|
||||
"light": "amber700"
|
||||
},
|
||||
"markdownStrong": {
|
||||
"dark": "pink900",
|
||||
"light": "pink700"
|
||||
},
|
||||
"markdownHorizontalRule": {
|
||||
"dark": "gray500",
|
||||
"light": "#999999"
|
||||
},
|
||||
"markdownListItem": {
|
||||
"dark": "gray1000",
|
||||
"light": "lightGray1000"
|
||||
},
|
||||
"markdownListEnumeration": {
|
||||
"dark": "blue900",
|
||||
"light": "blue700"
|
||||
},
|
||||
"markdownImage": {
|
||||
"dark": "teal900",
|
||||
"light": "teal700"
|
||||
},
|
||||
"markdownImageText": {
|
||||
"dark": "cyan",
|
||||
"light": "teal700"
|
||||
},
|
||||
"markdownCodeBlock": {
|
||||
"dark": "gray1000",
|
||||
"light": "lightGray1000"
|
||||
},
|
||||
"syntaxComment": {
|
||||
"dark": "gray600",
|
||||
"light": "#888888"
|
||||
},
|
||||
"syntaxKeyword": {
|
||||
"dark": "pink900",
|
||||
"light": "pink700"
|
||||
},
|
||||
"syntaxFunction": {
|
||||
"dark": "purple900",
|
||||
"light": "purple700"
|
||||
},
|
||||
"syntaxVariable": {
|
||||
"dark": "blue900",
|
||||
"light": "blue700"
|
||||
},
|
||||
"syntaxString": {
|
||||
"dark": "green900",
|
||||
"light": "green700"
|
||||
},
|
||||
"syntaxNumber": {
|
||||
"dark": "amber900",
|
||||
"light": "amber700"
|
||||
},
|
||||
"syntaxType": {
|
||||
"dark": "teal900",
|
||||
"light": "teal700"
|
||||
},
|
||||
"syntaxOperator": {
|
||||
"dark": "pink900",
|
||||
"light": "pink700"
|
||||
},
|
||||
"syntaxPunctuation": {
|
||||
"dark": "gray1000",
|
||||
"light": "lightGray1000"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
||||
import { createMemo, Match, onMount, Show, Switch, type ParentProps } from "solid-js"
|
||||
import { createMemo, Match, onMount, Show, Switch } from "solid-js"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import type { KeybindsConfig } from "@opencode-ai/sdk"
|
||||
import { Logo } from "../component/logo"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { useSync } from "../context/sync"
|
||||
import { Toast } from "../ui/toast"
|
||||
import { useArgs } from "../context/args"
|
||||
import { Global } from "@/global"
|
||||
import { useDirectory } from "../context/directory"
|
||||
import { useRoute, useRouteData } from "@tui/context/route"
|
||||
import { usePromptRef } from "../context/prompt"
|
||||
|
||||
// TODO: what is the best way to do this?
|
||||
let once = false
|
||||
@@ -17,6 +16,8 @@ let once = false
|
||||
export function Home() {
|
||||
const sync = useSync()
|
||||
const { theme } = useTheme()
|
||||
const route = useRouteData("home")
|
||||
const promptRef = usePromptRef()
|
||||
const mcp = createMemo(() => Object.keys(sync.data.mcp).length > 0)
|
||||
const mcpError = createMemo(() => {
|
||||
return Object.values(sync.data.mcp).some((x) => x.status === "failed")
|
||||
@@ -45,7 +46,10 @@ export function Home() {
|
||||
const args = useArgs()
|
||||
onMount(() => {
|
||||
if (once) return
|
||||
if (args.prompt) {
|
||||
if (route.initialPrompt) {
|
||||
prompt.set(route.initialPrompt)
|
||||
once = true
|
||||
} else if (args.prompt) {
|
||||
prompt.set({ input: args.prompt, parts: [] })
|
||||
once = true
|
||||
}
|
||||
@@ -57,7 +61,13 @@ export function Home() {
|
||||
<box flexGrow={1} justifyContent="center" alignItems="center" paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<Logo />
|
||||
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
|
||||
<Prompt ref={(r) => (prompt = r)} hint={Hint} />
|
||||
<Prompt
|
||||
ref={(r) => {
|
||||
prompt = r
|
||||
promptRef.set(r)
|
||||
}}
|
||||
hint={Hint}
|
||||
/>
|
||||
</box>
|
||||
<Toast />
|
||||
</box>
|
||||
|
||||
@@ -63,6 +63,7 @@ import { useKV } from "../../context/kv.tsx"
|
||||
import { Editor } from "../../util/editor"
|
||||
import stripAnsi from "strip-ansi"
|
||||
import { Footer } from "./footer.tsx"
|
||||
import { usePromptRef } from "../../context/prompt"
|
||||
|
||||
addDefaultParsers(parsers.parsers)
|
||||
|
||||
@@ -99,6 +100,7 @@ export function Session() {
|
||||
const sync = useSync()
|
||||
const kv = useKV()
|
||||
const { theme } = useTheme()
|
||||
const promptRef = usePromptRef()
|
||||
const session = createMemo(() => sync.session.get(route.sessionID)!)
|
||||
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
|
||||
const permissions = createMemo(() => sync.data.permission[route.sessionID] ?? [])
|
||||
@@ -138,7 +140,7 @@ export function Session() {
|
||||
return new CustomSpeedScroll(tui.scroll_speed)
|
||||
}
|
||||
|
||||
return new CustomSpeedScroll(process.platform === "win32" ? 3 : 1)
|
||||
return new CustomSpeedScroll(3)
|
||||
})
|
||||
|
||||
createEffect(async () => {
|
||||
@@ -949,7 +951,10 @@ export function Session() {
|
||||
</scrollbox>
|
||||
<box flexShrink={0}>
|
||||
<Prompt
|
||||
ref={(r) => (prompt = r)}
|
||||
ref={(r) => {
|
||||
prompt = r
|
||||
promptRef.set(r)
|
||||
}}
|
||||
disabled={permissions().length > 0}
|
||||
onSubmit={() => {
|
||||
toBottom()
|
||||
|
||||
@@ -299,11 +299,13 @@ function Option(props: {
|
||||
fg={props.active ? fg : props.current ? theme.primary : theme.text}
|
||||
attributes={props.active ? TextAttributes.BOLD : undefined}
|
||||
overflow="hidden"
|
||||
wrapMode="none"
|
||||
wrapMode="word"
|
||||
paddingLeft={3}
|
||||
>
|
||||
{Locale.truncate(props.title, 62)}
|
||||
<span style={{ fg: props.active ? fg : theme.textMuted }}> {props.description}</span>
|
||||
<Show when={props.description}>
|
||||
<span style={{ fg: props.active ? fg : theme.textMuted }}> {props.description}</span>
|
||||
</Show>
|
||||
</text>
|
||||
<Show when={props.footer}>
|
||||
<box flexShrink={0}>
|
||||
|
||||
@@ -2,8 +2,10 @@ import { Installation } from "@/installation"
|
||||
import { Server } from "@/server/server"
|
||||
import { Log } from "@/util/log"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { Rpc } from "@/util/rpc"
|
||||
import { upgrade } from "@/cli/upgrade"
|
||||
import type { BunWebSocketData } from "hono/bun"
|
||||
|
||||
await Log.init({
|
||||
print: process.argv.includes("--print-logs"),
|
||||
@@ -26,7 +28,7 @@ process.on("uncaughtException", (e) => {
|
||||
})
|
||||
})
|
||||
|
||||
let server: Bun.Server<undefined>
|
||||
let server: Bun.Server<BunWebSocketData>
|
||||
export const rpc = {
|
||||
async server(input: { port: number; hostname: string }) {
|
||||
if (server) await server.stop(true)
|
||||
@@ -43,6 +45,7 @@ export const rpc = {
|
||||
async checkUpgrade(input: { directory: string }) {
|
||||
await Instance.provide({
|
||||
directory: input.directory,
|
||||
init: InstanceBootstrap,
|
||||
fn: async () => {
|
||||
await upgrade().catch(() => {})
|
||||
},
|
||||
@@ -51,7 +54,9 @@ export const rpc = {
|
||||
async shutdown() {
|
||||
Log.Default.info("worker shutting down")
|
||||
await Instance.disposeAll()
|
||||
await server.stop(true)
|
||||
// TODO: this should be awaited, but ws connections are
|
||||
// causing this to hang, need to revisit this
|
||||
server.stop(true)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -375,6 +375,12 @@ export namespace Config {
|
||||
.regex(/^#[0-9a-fA-F]{6}$/, "Invalid hex color format")
|
||||
.optional()
|
||||
.describe("Hex color code for the agent (e.g., #FF5733)"),
|
||||
maxSteps: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe("Maximum number of agentic iterations before forcing text-only response"),
|
||||
permission: z
|
||||
.object({
|
||||
edit: Permission.optional(),
|
||||
@@ -470,6 +476,42 @@ export namespace Config {
|
||||
})
|
||||
export type Layout = z.infer<typeof Layout>
|
||||
|
||||
export const Provider = ModelsDev.Provider.partial()
|
||||
.extend({
|
||||
whitelist: z.array(z.string()).optional(),
|
||||
blacklist: z.array(z.string()).optional(),
|
||||
models: z.record(z.string(), ModelsDev.Model.partial()).optional(),
|
||||
options: z
|
||||
.object({
|
||||
apiKey: z.string().optional(),
|
||||
baseURL: z.string().optional(),
|
||||
enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
|
||||
setCacheKey: z.boolean().optional().describe("Enable promptCacheKey for this provider (default false)"),
|
||||
timeout: z
|
||||
.union([
|
||||
z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.describe(
|
||||
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
|
||||
),
|
||||
z.literal(false).describe("Disable timeout for this provider entirely."),
|
||||
])
|
||||
.optional()
|
||||
.describe(
|
||||
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
|
||||
),
|
||||
})
|
||||
.catchall(z.any())
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
.meta({
|
||||
ref: "ProviderConfig",
|
||||
})
|
||||
export type Provider = z.infer<typeof Provider>
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
|
||||
@@ -536,43 +578,7 @@ export namespace Config {
|
||||
.optional()
|
||||
.describe("Agent configuration, see https://opencode.ai/docs/agent"),
|
||||
provider: z
|
||||
.record(
|
||||
z.string(),
|
||||
ModelsDev.Provider.partial()
|
||||
.extend({
|
||||
whitelist: z.array(z.string()).optional(),
|
||||
blacklist: z.array(z.string()).optional(),
|
||||
models: z.record(z.string(), ModelsDev.Model.partial()).optional(),
|
||||
options: z
|
||||
.object({
|
||||
apiKey: z.string().optional(),
|
||||
baseURL: z.string().optional(),
|
||||
enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
|
||||
setCacheKey: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Enable promptCacheKey for this provider (default false)"),
|
||||
timeout: z
|
||||
.union([
|
||||
z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.describe(
|
||||
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
|
||||
),
|
||||
z.literal(false).describe("Disable timeout for this provider entirely."),
|
||||
])
|
||||
.optional()
|
||||
.describe(
|
||||
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
|
||||
),
|
||||
})
|
||||
.catchall(z.any())
|
||||
.optional(),
|
||||
})
|
||||
.strict(),
|
||||
)
|
||||
.record(z.string(), Provider)
|
||||
.optional()
|
||||
.describe("Custom provider configurations and model overrides"),
|
||||
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
|
||||
@@ -670,6 +676,10 @@ export namespace Config {
|
||||
chatMaxRetries: z.number().optional().describe("Number of retries for chat completions on failure"),
|
||||
disable_paste_summary: z.boolean().optional(),
|
||||
batch_tool: z.boolean().optional().describe("Enable the batch tool"),
|
||||
openTelemetry: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)"),
|
||||
primary_tools: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
|
||||
@@ -30,7 +30,7 @@ await Promise.all([
|
||||
fs.mkdir(Global.Path.bin, { recursive: true }),
|
||||
])
|
||||
|
||||
const CACHE_VERSION = "12"
|
||||
const CACHE_VERSION = "13"
|
||||
|
||||
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||
.text()
|
||||
|
||||
@@ -8,6 +8,7 @@ export namespace Identifier {
|
||||
permission: "per",
|
||||
user: "usr",
|
||||
part: "prt",
|
||||
pty: "pty",
|
||||
} as const
|
||||
|
||||
export function schema(prefix: keyof typeof prefixes) {
|
||||
|
||||
@@ -28,8 +28,8 @@ export namespace Plugin {
|
||||
}
|
||||
const plugins = [...(config.plugin ?? [])]
|
||||
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
||||
plugins.push("opencode-copilot-auth@0.0.7")
|
||||
plugins.push("opencode-anthropic-auth@0.0.3")
|
||||
plugins.push("opencode-copilot-auth@0.0.8")
|
||||
plugins.push("opencode-anthropic-auth@0.0.4")
|
||||
}
|
||||
for (let plugin of plugins) {
|
||||
log.info("loading plugin", { path: plugin })
|
||||
|
||||
@@ -5,6 +5,8 @@ import { $ } from "bun"
|
||||
import { Storage } from "../storage/storage"
|
||||
import { Log } from "../util/log"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { Session } from "../session"
|
||||
import { work } from "../util/queue"
|
||||
|
||||
export namespace Project {
|
||||
const log = Log.create({ service: "project" })
|
||||
@@ -64,17 +66,6 @@ export namespace Project {
|
||||
if (id) Bun.file(path.join(git, "opencode")).write(id)
|
||||
}
|
||||
timer.stop()
|
||||
if (!id) {
|
||||
const project: Info = {
|
||||
id: "global",
|
||||
worktree: "/",
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
}
|
||||
await Storage.write<Info>(["project", "global"], project)
|
||||
return project
|
||||
}
|
||||
worktree = await $`git rev-parse --show-toplevel`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
@@ -87,8 +78,13 @@ export namespace Project {
|
||||
.cwd(worktree)
|
||||
.text()
|
||||
.then((x) => path.resolve(worktree, x.trim()))
|
||||
const projectID = id || "global"
|
||||
const existing = id ? await Storage.read<Info>(["project", id]).catch(() => undefined) : undefined
|
||||
if (!existing) {
|
||||
await migrateFromGlobal(projectID, worktree)
|
||||
}
|
||||
const project: Info = {
|
||||
id,
|
||||
id: projectID,
|
||||
worktree,
|
||||
vcsDir,
|
||||
vcs: "git",
|
||||
@@ -96,10 +92,34 @@ export namespace Project {
|
||||
created: Date.now(),
|
||||
},
|
||||
}
|
||||
await Storage.write<Info>(["project", id], project)
|
||||
await Storage.write<Info>(["project", projectID], project)
|
||||
return project
|
||||
}
|
||||
|
||||
async function migrateFromGlobal(newProjectID: string, worktree: string) {
|
||||
const globalProject = await Storage.read<Info>(["project", "global"]).catch(() => undefined)
|
||||
if (!globalProject) return
|
||||
|
||||
const globalSessions = await Storage.list(["session", "global"]).catch(() => [])
|
||||
if (globalSessions.length === 0) return
|
||||
|
||||
log.info("migrating sessions from global", { newProjectID, worktree, count: globalSessions.length })
|
||||
|
||||
await work(10, globalSessions, async (key) => {
|
||||
const sessionID = key[key.length - 1]
|
||||
const session = await Storage.read<Session.Info>(key).catch(() => undefined)
|
||||
if (!session) return
|
||||
if (session.directory && session.directory !== worktree) return
|
||||
|
||||
session.projectID = newProjectID
|
||||
log.info("migrating session", { sessionID, from: "global", to: newProjectID })
|
||||
await Storage.write(["session", newProjectID, sessionID], session)
|
||||
await Storage.remove(key)
|
||||
}).catch((error) => {
|
||||
log.error("failed to migrate sessions from global to project", { error, projectId: newProjectID })
|
||||
})
|
||||
}
|
||||
|
||||
export async function setInitialized(projectID: string) {
|
||||
await Storage.update<Info>(["project", projectID], (draft) => {
|
||||
draft.time.initialized = Date.now()
|
||||
|
||||
@@ -9,16 +9,16 @@ export namespace ModelsDev {
|
||||
const log = Log.create({ service: "models.dev" })
|
||||
const filepath = path.join(Global.Path.cache, "models.json")
|
||||
|
||||
export const Model = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
release_date: z.string(),
|
||||
attachment: z.boolean(),
|
||||
reasoning: z.boolean(),
|
||||
temperature: z.boolean(),
|
||||
tool_call: z.boolean(),
|
||||
cost: z.object({
|
||||
export const Model = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
release_date: z.string(),
|
||||
attachment: z.boolean(),
|
||||
reasoning: z.boolean(),
|
||||
temperature: z.boolean(),
|
||||
tool_call: z.boolean(),
|
||||
cost: z
|
||||
.object({
|
||||
input: z.number(),
|
||||
output: z.number(),
|
||||
cache_read: z.number().optional(),
|
||||
@@ -31,40 +31,34 @@ export namespace ModelsDev {
|
||||
cache_write: z.number().optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
limit: z.object({
|
||||
context: z.number(),
|
||||
output: z.number(),
|
||||
}),
|
||||
modalities: z
|
||||
.object({
|
||||
input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
|
||||
output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
|
||||
})
|
||||
.optional(),
|
||||
experimental: z.boolean().optional(),
|
||||
status: z.enum(["alpha", "beta", "deprecated"]).optional(),
|
||||
options: z.record(z.string(), z.any()),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
provider: z.object({ npm: z.string() }).optional(),
|
||||
})
|
||||
.meta({
|
||||
ref: "Model",
|
||||
})
|
||||
})
|
||||
.optional(),
|
||||
limit: z.object({
|
||||
context: z.number(),
|
||||
output: z.number(),
|
||||
}),
|
||||
modalities: z
|
||||
.object({
|
||||
input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
|
||||
output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
|
||||
})
|
||||
.optional(),
|
||||
experimental: z.boolean().optional(),
|
||||
status: z.enum(["alpha", "beta", "deprecated"]).optional(),
|
||||
options: z.record(z.string(), z.any()),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
provider: z.object({ npm: z.string() }).optional(),
|
||||
})
|
||||
export type Model = z.infer<typeof Model>
|
||||
|
||||
export const Provider = z
|
||||
.object({
|
||||
api: z.string().optional(),
|
||||
name: z.string(),
|
||||
env: z.array(z.string()),
|
||||
id: z.string(),
|
||||
npm: z.string().optional(),
|
||||
models: z.record(z.string(), Model),
|
||||
})
|
||||
.meta({
|
||||
ref: "Provider",
|
||||
})
|
||||
export const Provider = z.object({
|
||||
api: z.string().optional(),
|
||||
name: z.string(),
|
||||
env: z.array(z.string()),
|
||||
id: z.string(),
|
||||
npm: z.string().optional(),
|
||||
models: z.record(z.string(), Model),
|
||||
})
|
||||
|
||||
export type Provider = z.infer<typeof Provider>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import z from "zod"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import { Config } from "../config/config"
|
||||
import { mergeDeep, sortBy } from "remeda"
|
||||
import { NoSuchModelError, type LanguageModel, type Provider as SDK } from "ai"
|
||||
import { mapValues, mergeDeep, sortBy } from "remeda"
|
||||
import { NoSuchModelError, type Provider as SDK } from "ai"
|
||||
import { Log } from "../util/log"
|
||||
import { BunProc } from "../bun"
|
||||
import { Plugin } from "../plugin"
|
||||
@@ -23,7 +23,7 @@ import { createVertex } from "@ai-sdk/google-vertex"
|
||||
import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic"
|
||||
import { createOpenAI } from "@ai-sdk/openai"
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
|
||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
|
||||
import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider"
|
||||
import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/openai-compatible/src"
|
||||
|
||||
export namespace Provider {
|
||||
@@ -43,14 +43,13 @@ export namespace Provider {
|
||||
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
|
||||
}
|
||||
|
||||
type CustomLoader = (provider: ModelsDev.Provider) => Promise<{
|
||||
type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
|
||||
type CustomLoader = (provider: Info) => Promise<{
|
||||
autoload: boolean
|
||||
getModel?: (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
|
||||
getModel?: CustomModelLoader
|
||||
options?: Record<string, any>
|
||||
}>
|
||||
|
||||
type Source = "env" | "config" | "custom" | "api"
|
||||
|
||||
const CUSTOM_LOADERS: Record<string, CustomLoader> = {
|
||||
async anthropic() {
|
||||
return {
|
||||
@@ -280,12 +279,26 @@ export namespace Provider {
|
||||
project,
|
||||
location,
|
||||
},
|
||||
async getModel(sdk: any, modelID: string) {
|
||||
async getModel(sdk: any, modelID) {
|
||||
const id = String(modelID).trim()
|
||||
return sdk.languageModel(id)
|
||||
},
|
||||
}
|
||||
},
|
||||
"sap-ai-core": async () => {
|
||||
const auth = await Auth.get("sap-ai-core")
|
||||
const serviceKey = Env.get("SAP_AI_SERVICE_KEY") || (auth?.type === "api" ? auth.key : undefined)
|
||||
const deploymentId = Env.get("SAP_AI_DEPLOYMENT_ID") || "d65d81e7c077e583"
|
||||
const resourceGroup = Env.get("SAP_AI_RESOURCE_GROUP") || "default"
|
||||
|
||||
return {
|
||||
autoload: !!serviceKey,
|
||||
options: serviceKey ? { serviceKey, deploymentId, resourceGroup } : {},
|
||||
async getModel(sdk: any, modelID: string) {
|
||||
return sdk(modelID)
|
||||
},
|
||||
}
|
||||
},
|
||||
zenmux: async () => {
|
||||
return {
|
||||
autoload: false,
|
||||
@@ -299,10 +312,156 @@ export namespace Provider {
|
||||
},
|
||||
}
|
||||
|
||||
export const Model = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
providerID: z.string(),
|
||||
api: z.object({
|
||||
id: z.string(),
|
||||
url: z.string(),
|
||||
npm: z.string(),
|
||||
}),
|
||||
name: z.string(),
|
||||
capabilities: z.object({
|
||||
temperature: z.boolean(),
|
||||
reasoning: z.boolean(),
|
||||
attachment: z.boolean(),
|
||||
toolcall: z.boolean(),
|
||||
input: z.object({
|
||||
text: z.boolean(),
|
||||
audio: z.boolean(),
|
||||
image: z.boolean(),
|
||||
video: z.boolean(),
|
||||
pdf: z.boolean(),
|
||||
}),
|
||||
output: z.object({
|
||||
text: z.boolean(),
|
||||
audio: z.boolean(),
|
||||
image: z.boolean(),
|
||||
video: z.boolean(),
|
||||
pdf: z.boolean(),
|
||||
}),
|
||||
}),
|
||||
cost: z.object({
|
||||
input: z.number(),
|
||||
output: z.number(),
|
||||
cache: z.object({
|
||||
read: z.number(),
|
||||
write: z.number(),
|
||||
}),
|
||||
experimentalOver200K: z
|
||||
.object({
|
||||
input: z.number(),
|
||||
output: z.number(),
|
||||
cache: z.object({
|
||||
read: z.number(),
|
||||
write: z.number(),
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
limit: z.object({
|
||||
context: z.number(),
|
||||
output: z.number(),
|
||||
}),
|
||||
status: z.enum(["alpha", "beta", "deprecated", "active"]),
|
||||
options: z.record(z.string(), z.any()),
|
||||
headers: z.record(z.string(), z.string()),
|
||||
})
|
||||
.meta({
|
||||
ref: "Model",
|
||||
})
|
||||
export type Model = z.infer<typeof Model>
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
source: z.enum(["env", "config", "custom", "api"]),
|
||||
env: z.string().array(),
|
||||
key: z.string().optional(),
|
||||
options: z.record(z.string(), z.any()),
|
||||
models: z.record(z.string(), Model),
|
||||
})
|
||||
.meta({
|
||||
ref: "Provider",
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
|
||||
return {
|
||||
id: model.id,
|
||||
providerID: provider.id,
|
||||
name: model.name,
|
||||
api: {
|
||||
id: model.id,
|
||||
url: provider.api!,
|
||||
npm: model.provider?.npm ?? provider.npm ?? provider.id,
|
||||
},
|
||||
status: model.status ?? "active",
|
||||
headers: model.headers ?? {},
|
||||
options: model.options ?? {},
|
||||
cost: {
|
||||
input: model.cost?.input ?? 0,
|
||||
output: model.cost?.output ?? 0,
|
||||
cache: {
|
||||
read: model.cost?.cache_read ?? 0,
|
||||
write: model.cost?.cache_write ?? 0,
|
||||
},
|
||||
experimentalOver200K: model.cost?.context_over_200k
|
||||
? {
|
||||
cache: {
|
||||
read: model.cost.context_over_200k.cache_read ?? 0,
|
||||
write: model.cost.context_over_200k.cache_write ?? 0,
|
||||
},
|
||||
input: model.cost.context_over_200k.input,
|
||||
output: model.cost.context_over_200k.output,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
limit: {
|
||||
context: model.limit.context,
|
||||
output: model.limit.output,
|
||||
},
|
||||
capabilities: {
|
||||
temperature: model.temperature,
|
||||
reasoning: model.reasoning,
|
||||
attachment: model.attachment,
|
||||
toolcall: model.tool_call,
|
||||
input: {
|
||||
text: model.modalities?.input?.includes("text") ?? false,
|
||||
audio: model.modalities?.input?.includes("audio") ?? false,
|
||||
image: model.modalities?.input?.includes("image") ?? false,
|
||||
video: model.modalities?.input?.includes("video") ?? false,
|
||||
pdf: model.modalities?.input?.includes("pdf") ?? false,
|
||||
},
|
||||
output: {
|
||||
text: model.modalities?.output?.includes("text") ?? false,
|
||||
audio: model.modalities?.output?.includes("audio") ?? false,
|
||||
image: model.modalities?.output?.includes("image") ?? false,
|
||||
video: model.modalities?.output?.includes("video") ?? false,
|
||||
pdf: model.modalities?.output?.includes("pdf") ?? false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
||||
return {
|
||||
id: provider.id,
|
||||
source: "custom",
|
||||
name: provider.name,
|
||||
env: provider.env ?? [],
|
||||
options: {},
|
||||
models: mapValues(provider.models, (model) => fromModelsDevModel(provider, model)),
|
||||
}
|
||||
}
|
||||
|
||||
const state = Instance.state(async () => {
|
||||
using _ = log.time("state")
|
||||
const config = await Config.get()
|
||||
const database = await ModelsDev.get()
|
||||
const modelsDev = await ModelsDev.get()
|
||||
const database = mapValues(modelsDev, fromModelsDevProvider)
|
||||
|
||||
const disabled = new Set(config.disabled_providers ?? [])
|
||||
const enabled = config.enabled_providers ? new Set(config.enabled_providers) : null
|
||||
@@ -313,54 +472,15 @@ export namespace Provider {
|
||||
return true
|
||||
}
|
||||
|
||||
const providers: {
|
||||
[providerID: string]: {
|
||||
source: Source
|
||||
info: ModelsDev.Provider
|
||||
getModel?: (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
|
||||
options: Record<string, any>
|
||||
}
|
||||
const providers: { [providerID: string]: Info } = {}
|
||||
const languages = new Map<string, LanguageModelV2>()
|
||||
const modelLoaders: {
|
||||
[providerID: string]: CustomModelLoader
|
||||
} = {}
|
||||
const models = new Map<
|
||||
string,
|
||||
{
|
||||
providerID: string
|
||||
modelID: string
|
||||
info: ModelsDev.Model
|
||||
language: LanguageModel
|
||||
npm?: string
|
||||
}
|
||||
>()
|
||||
const sdk = new Map<number, SDK>()
|
||||
// Maps `${provider}/${key}` to the provider’s actual model ID for custom aliases.
|
||||
const realIdByKey = new Map<string, string>()
|
||||
|
||||
log.info("init")
|
||||
|
||||
function mergeProvider(
|
||||
id: string,
|
||||
options: Record<string, any>,
|
||||
source: Source,
|
||||
getModel?: (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>,
|
||||
) {
|
||||
const provider = providers[id]
|
||||
if (!provider) {
|
||||
const info = database[id]
|
||||
if (!info) return
|
||||
if (info.api && !options["baseURL"]) options["baseURL"] = info.api
|
||||
providers[id] = {
|
||||
source,
|
||||
info,
|
||||
options,
|
||||
getModel,
|
||||
}
|
||||
return
|
||||
}
|
||||
provider.options = mergeDeep(provider.options, options)
|
||||
provider.source = source
|
||||
provider.getModel = getModel ?? provider.getModel
|
||||
}
|
||||
|
||||
const configProviders = Object.entries(config.provider ?? {})
|
||||
|
||||
// Add GitHub Copilot Enterprise provider that inherits from GitHub Copilot
|
||||
@@ -370,74 +490,93 @@ export namespace Provider {
|
||||
...githubCopilot,
|
||||
id: "github-copilot-enterprise",
|
||||
name: "GitHub Copilot Enterprise",
|
||||
// Enterprise uses a different API endpoint - will be set dynamically based on auth
|
||||
api: undefined,
|
||||
models: mapValues(githubCopilot.models, (model) => ({
|
||||
...model,
|
||||
providerID: "github-copilot-enterprise",
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
function mergeProvider(providerID: string, provider: Partial<Info>) {
|
||||
const existing = providers[providerID]
|
||||
if (existing) {
|
||||
// @ts-expect-error
|
||||
providers[providerID] = mergeDeep(existing, provider)
|
||||
return
|
||||
}
|
||||
const match = database[providerID]
|
||||
if (!match) return
|
||||
// @ts-expect-error
|
||||
providers[providerID] = mergeDeep(match, provider)
|
||||
}
|
||||
|
||||
// extend database from config
|
||||
for (const [providerID, provider] of configProviders) {
|
||||
const existing = database[providerID]
|
||||
const parsed: ModelsDev.Provider = {
|
||||
const parsed: Info = {
|
||||
id: providerID,
|
||||
npm: provider.npm ?? existing?.npm,
|
||||
name: provider.name ?? existing?.name ?? providerID,
|
||||
env: provider.env ?? existing?.env ?? [],
|
||||
api: provider.api ?? existing?.api,
|
||||
options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
|
||||
source: "config",
|
||||
models: existing?.models ?? {},
|
||||
}
|
||||
|
||||
for (const [modelID, model] of Object.entries(provider.models ?? {})) {
|
||||
const existing = parsed.models[model.id ?? modelID]
|
||||
const existingModel = parsed.models[model.id ?? modelID]
|
||||
const name = iife(() => {
|
||||
if (model.name) return model.name
|
||||
if (model.id && model.id !== modelID) return modelID
|
||||
return existing?.name ?? modelID
|
||||
return existingModel?.name ?? modelID
|
||||
})
|
||||
const parsedModel: ModelsDev.Model = {
|
||||
const parsedModel: Model = {
|
||||
id: modelID,
|
||||
name,
|
||||
release_date: model.release_date ?? existing?.release_date,
|
||||
attachment: model.attachment ?? existing?.attachment ?? false,
|
||||
reasoning: model.reasoning ?? existing?.reasoning ?? false,
|
||||
temperature: model.temperature ?? existing?.temperature ?? false,
|
||||
tool_call: model.tool_call ?? existing?.tool_call ?? true,
|
||||
cost:
|
||||
!model.cost && !existing?.cost
|
||||
? {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
}
|
||||
: {
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
...existing?.cost,
|
||||
...model.cost,
|
||||
},
|
||||
options: {
|
||||
...existing?.options,
|
||||
...model.options,
|
||||
api: {
|
||||
id: model.id ?? existingModel?.api.id ?? modelID,
|
||||
npm:
|
||||
model.provider?.npm ?? provider.npm ?? existingModel?.api.npm ?? modelsDev[providerID]?.npm ?? providerID,
|
||||
url: provider?.api ?? existingModel?.api.url,
|
||||
},
|
||||
limit: model.limit ??
|
||||
existing?.limit ?? {
|
||||
context: 0,
|
||||
output: 0,
|
||||
status: model.status ?? existingModel?.status ?? "active",
|
||||
name,
|
||||
providerID,
|
||||
capabilities: {
|
||||
temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
|
||||
reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
|
||||
attachment: model.attachment ?? existingModel?.capabilities.attachment ?? false,
|
||||
toolcall: model.tool_call ?? existingModel?.capabilities.toolcall ?? true,
|
||||
input: {
|
||||
text: model.modalities?.input?.includes("text") ?? existingModel?.capabilities.input.text ?? true,
|
||||
audio: model.modalities?.input?.includes("audio") ?? existingModel?.capabilities.input.audio ?? false,
|
||||
image: model.modalities?.input?.includes("image") ?? existingModel?.capabilities.input.image ?? false,
|
||||
video: model.modalities?.input?.includes("video") ?? existingModel?.capabilities.input.video ?? false,
|
||||
pdf: model.modalities?.input?.includes("pdf") ?? existingModel?.capabilities.input.pdf ?? false,
|
||||
},
|
||||
modalities: model.modalities ??
|
||||
existing?.modalities ?? {
|
||||
input: ["text"],
|
||||
output: ["text"],
|
||||
output: {
|
||||
text: model.modalities?.output?.includes("text") ?? existingModel?.capabilities.output.text ?? true,
|
||||
audio: model.modalities?.output?.includes("audio") ?? existingModel?.capabilities.output.audio ?? false,
|
||||
image: model.modalities?.output?.includes("image") ?? existingModel?.capabilities.output.image ?? false,
|
||||
video: model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
|
||||
pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
|
||||
},
|
||||
headers: model.headers,
|
||||
provider: model.provider ?? existing?.provider,
|
||||
}
|
||||
if (model.id && model.id !== modelID) {
|
||||
realIdByKey.set(`${providerID}/${modelID}`, model.id)
|
||||
},
|
||||
cost: {
|
||||
input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
|
||||
output: model?.cost?.output ?? existingModel?.cost?.output ?? 0,
|
||||
cache: {
|
||||
read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0,
|
||||
write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0,
|
||||
},
|
||||
},
|
||||
options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}),
|
||||
limit: {
|
||||
context: model.limit?.context ?? existingModel?.limit?.context ?? 0,
|
||||
output: model.limit?.output ?? existingModel?.limit?.output ?? 0,
|
||||
},
|
||||
headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
|
||||
}
|
||||
parsed.models[modelID] = parsedModel
|
||||
}
|
||||
|
||||
database[providerID] = parsed
|
||||
}
|
||||
|
||||
@@ -447,19 +586,20 @@ export namespace Provider {
|
||||
if (disabled.has(providerID)) continue
|
||||
const apiKey = provider.env.map((item) => env[item]).find(Boolean)
|
||||
if (!apiKey) continue
|
||||
mergeProvider(
|
||||
providerID,
|
||||
// only include apiKey if there's only one potential option
|
||||
provider.env.length === 1 ? { apiKey } : {},
|
||||
"env",
|
||||
)
|
||||
mergeProvider(providerID, {
|
||||
source: "env",
|
||||
key: provider.env.length === 1 ? apiKey : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// load apikeys
|
||||
for (const [providerID, provider] of Object.entries(await Auth.all())) {
|
||||
if (disabled.has(providerID)) continue
|
||||
if (provider.type === "api") {
|
||||
mergeProvider(providerID, { apiKey: provider.key }, "api")
|
||||
mergeProvider(providerID, {
|
||||
source: "api",
|
||||
key: provider.key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,7 +625,10 @@ export namespace Provider {
|
||||
// Load for the main provider if auth exists
|
||||
if (auth) {
|
||||
const options = await plugin.auth.loader(() => Auth.get(providerID) as any, database[plugin.auth.provider])
|
||||
mergeProvider(plugin.auth.provider, options ?? {}, "custom")
|
||||
mergeProvider(plugin.auth.provider, {
|
||||
source: "custom",
|
||||
options: options,
|
||||
})
|
||||
}
|
||||
|
||||
// If this is github-copilot plugin, also register for github-copilot-enterprise if auth exists
|
||||
@@ -498,7 +641,10 @@ export namespace Provider {
|
||||
() => Auth.get(enterpriseProviderID) as any,
|
||||
database[enterpriseProviderID],
|
||||
)
|
||||
mergeProvider(enterpriseProviderID, enterpriseOptions ?? {}, "custom")
|
||||
mergeProvider(enterpriseProviderID, {
|
||||
source: "custom",
|
||||
options: enterpriseOptions,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -508,13 +654,21 @@ export namespace Provider {
|
||||
if (disabled.has(providerID)) continue
|
||||
const result = await fn(database[providerID])
|
||||
if (result && (result.autoload || providers[providerID])) {
|
||||
mergeProvider(providerID, result.options ?? {}, "custom", result.getModel)
|
||||
if (result.getModel) modelLoaders[providerID] = result.getModel
|
||||
mergeProvider(providerID, {
|
||||
source: "custom",
|
||||
options: result.options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// load config
|
||||
for (const [providerID, provider] of configProviders) {
|
||||
mergeProvider(providerID, provider.options ?? {}, "config")
|
||||
const partial: Partial<Info> = { source: "config" }
|
||||
if (provider.env) partial.env = provider.env
|
||||
if (provider.name) partial.name = provider.name
|
||||
if (provider.options) partial.options = provider.options
|
||||
mergeProvider(providerID, partial)
|
||||
}
|
||||
|
||||
for (const [providerID, provider] of Object.entries(providers)) {
|
||||
@@ -524,49 +678,43 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
if (providerID === "github-copilot" || providerID === "github-copilot-enterprise") {
|
||||
provider.info.npm = "@ai-sdk/github-copilot"
|
||||
provider.models = mapValues(provider.models, (model) => ({
|
||||
...model,
|
||||
api: {
|
||||
...model.api,
|
||||
npm: "@ai-sdk/github-copilot",
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
const configProvider = config.provider?.[providerID]
|
||||
const filteredModels = Object.fromEntries(
|
||||
Object.entries(provider.info.models)
|
||||
// Filter out blacklisted models
|
||||
.filter(
|
||||
([modelID]) =>
|
||||
modelID !== "gpt-5-chat-latest" && !(providerID === "openrouter" && modelID === "openai/gpt-5-chat"),
|
||||
)
|
||||
// Filter out experimental models
|
||||
.filter(
|
||||
([, model]) =>
|
||||
((!model.experimental && model.status !== "alpha") || Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) &&
|
||||
model.status !== "deprecated",
|
||||
)
|
||||
// Filter by provider's whitelist/blacklist from config
|
||||
.filter(([modelID]) => {
|
||||
if (!configProvider) return true
|
||||
|
||||
return (
|
||||
(!configProvider.blacklist || !configProvider.blacklist.includes(modelID)) &&
|
||||
(!configProvider.whitelist || configProvider.whitelist.includes(modelID))
|
||||
)
|
||||
}),
|
||||
)
|
||||
for (const [modelID, model] of Object.entries(provider.models)) {
|
||||
model.api.id = model.api.id ?? model.id ?? modelID
|
||||
if (modelID === "gpt-5-chat-latest" || (providerID === "openrouter" && modelID === "openai/gpt-5-chat"))
|
||||
delete provider.models[modelID]
|
||||
if ((model.status === "alpha" && !Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) || model.status === "deprecated")
|
||||
delete provider.models[modelID]
|
||||
if (
|
||||
(configProvider?.blacklist && configProvider.blacklist.includes(modelID)) ||
|
||||
(configProvider?.whitelist && !configProvider.whitelist.includes(modelID))
|
||||
)
|
||||
delete provider.models[modelID]
|
||||
}
|
||||
|
||||
provider.info.models = filteredModels
|
||||
|
||||
if (Object.keys(provider.info.models).length === 0) {
|
||||
if (Object.keys(provider.models).length === 0) {
|
||||
delete providers[providerID]
|
||||
continue
|
||||
}
|
||||
|
||||
log.info("found", { providerID, npm: provider.info.npm })
|
||||
log.info("found", { providerID })
|
||||
}
|
||||
|
||||
return {
|
||||
models,
|
||||
models: languages,
|
||||
providers,
|
||||
sdk,
|
||||
realIdByKey,
|
||||
modelLoaders,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -574,19 +722,28 @@ export namespace Provider {
|
||||
return state().then((state) => state.providers)
|
||||
}
|
||||
|
||||
async function getSDK(provider: ModelsDev.Provider, model: ModelsDev.Model) {
|
||||
return (async () => {
|
||||
async function getSDK(model: Model) {
|
||||
try {
|
||||
using _ = log.time("getSDK", {
|
||||
providerID: provider.id,
|
||||
providerID: model.providerID,
|
||||
})
|
||||
const s = await state()
|
||||
const pkg = model.provider?.npm ?? provider.npm ?? provider.id
|
||||
const options = { ...s.providers[provider.id]?.options }
|
||||
if (pkg.includes("@ai-sdk/openai-compatible") && options["includeUsage"] === undefined) {
|
||||
const provider = s.providers[model.providerID]
|
||||
const options = { ...provider.options }
|
||||
|
||||
if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) {
|
||||
options["includeUsage"] = true
|
||||
}
|
||||
|
||||
const key = Bun.hash.xxHash32(JSON.stringify({ pkg, options }))
|
||||
if (!options["baseURL"]) options["baseURL"] = model.api.url
|
||||
if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
|
||||
if (model.headers)
|
||||
options["headers"] = {
|
||||
...options["headers"],
|
||||
...model.headers,
|
||||
}
|
||||
|
||||
const key = Bun.hash.xxHash32(JSON.stringify({ npm: model.api.npm, options }))
|
||||
const existing = s.sdk.get(key)
|
||||
if (existing) return existing
|
||||
|
||||
@@ -615,12 +772,13 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
// Special case: google-vertex-anthropic uses a subpath import
|
||||
const bundledKey = provider.id === "google-vertex-anthropic" ? "@ai-sdk/google-vertex/anthropic" : pkg
|
||||
const bundledKey =
|
||||
model.providerID === "google-vertex-anthropic" ? "@ai-sdk/google-vertex/anthropic" : model.api.npm
|
||||
const bundledFn = BUNDLED_PROVIDERS[bundledKey]
|
||||
if (bundledFn) {
|
||||
log.info("using bundled provider", { providerID: provider.id, pkg: bundledKey })
|
||||
log.info("using bundled provider", { providerID: model.providerID, pkg: bundledKey })
|
||||
const loaded = bundledFn({
|
||||
name: provider.id,
|
||||
name: model.providerID,
|
||||
...options,
|
||||
})
|
||||
s.sdk.set(key, loaded)
|
||||
@@ -628,25 +786,25 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
let installedPath: string
|
||||
if (!pkg.startsWith("file://")) {
|
||||
installedPath = await BunProc.install(pkg, "latest")
|
||||
if (!model.api.npm.startsWith("file://")) {
|
||||
installedPath = await BunProc.install(model.api.npm, "latest")
|
||||
} else {
|
||||
log.info("loading local provider", { pkg })
|
||||
installedPath = pkg
|
||||
log.info("loading local provider", { pkg: model.api.npm })
|
||||
installedPath = model.api.npm
|
||||
}
|
||||
|
||||
const mod = await import(installedPath)
|
||||
|
||||
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
|
||||
const loaded = fn({
|
||||
name: provider.id,
|
||||
const loaded = await fn({
|
||||
name: model.providerID,
|
||||
...options,
|
||||
})
|
||||
s.sdk.set(key, loaded)
|
||||
return loaded as SDK
|
||||
})().catch((e) => {
|
||||
throw new InitError({ providerID: provider.id }, { cause: e })
|
||||
})
|
||||
} catch (e) {
|
||||
throw new InitError({ providerID: model.providerID }, { cause: e })
|
||||
}
|
||||
}
|
||||
|
||||
export async function getProvider(providerID: string) {
|
||||
@@ -654,15 +812,7 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
export async function getModel(providerID: string, modelID: string) {
|
||||
const key = `${providerID}/${modelID}`
|
||||
const s = await state()
|
||||
if (s.models.has(key)) return s.models.get(key)!
|
||||
|
||||
log.info("getModel", {
|
||||
providerID,
|
||||
modelID,
|
||||
})
|
||||
|
||||
const provider = s.providers[providerID]
|
||||
if (!provider) {
|
||||
const availableProviders = Object.keys(s.providers)
|
||||
@@ -671,43 +821,36 @@ export namespace Provider {
|
||||
throw new ModelNotFoundError({ providerID, modelID, suggestions })
|
||||
}
|
||||
|
||||
const info = provider.info.models[modelID]
|
||||
const info = provider.models[modelID]
|
||||
if (!info) {
|
||||
const availableModels = Object.keys(provider.info.models)
|
||||
const availableModels = Object.keys(provider.models)
|
||||
const matches = fuzzysort.go(modelID, availableModels, { limit: 3, threshold: -10000 })
|
||||
const suggestions = matches.map((m) => m.target)
|
||||
throw new ModelNotFoundError({ providerID, modelID, suggestions })
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
const sdk = await getSDK(provider.info, info)
|
||||
export async function getLanguage(model: Model) {
|
||||
const s = await state()
|
||||
const key = `${model.providerID}/${model.id}`
|
||||
if (s.models.has(key)) return s.models.get(key)!
|
||||
|
||||
const provider = s.providers[model.providerID]
|
||||
const sdk = await getSDK(model)
|
||||
|
||||
try {
|
||||
const keyReal = `${providerID}/${modelID}`
|
||||
const realID = s.realIdByKey.get(keyReal) ?? info.id
|
||||
const language = provider.getModel
|
||||
? await provider.getModel(sdk, realID, provider.options)
|
||||
: sdk.languageModel(realID)
|
||||
log.info("found", { providerID, modelID })
|
||||
s.models.set(key, {
|
||||
providerID,
|
||||
modelID,
|
||||
info,
|
||||
language,
|
||||
npm: info.provider?.npm ?? provider.info.npm,
|
||||
})
|
||||
return {
|
||||
modelID,
|
||||
providerID,
|
||||
info,
|
||||
language,
|
||||
npm: info.provider?.npm ?? provider.info.npm,
|
||||
}
|
||||
const language = s.modelLoaders[model.providerID]
|
||||
? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options)
|
||||
: sdk.languageModel(model.api.id)
|
||||
s.models.set(key, language)
|
||||
return language
|
||||
} catch (e) {
|
||||
if (e instanceof NoSuchModelError)
|
||||
throw new ModelNotFoundError(
|
||||
{
|
||||
modelID: modelID,
|
||||
providerID,
|
||||
modelID: model.id,
|
||||
providerID: model.providerID,
|
||||
},
|
||||
{ cause: e },
|
||||
)
|
||||
@@ -720,7 +863,7 @@ export namespace Provider {
|
||||
const provider = s.providers[providerID]
|
||||
if (!provider) return undefined
|
||||
for (const item of query) {
|
||||
for (const modelID of Object.keys(provider.info.models)) {
|
||||
for (const modelID of Object.keys(provider.models)) {
|
||||
if (modelID.includes(item))
|
||||
return {
|
||||
providerID,
|
||||
@@ -756,7 +899,7 @@ export namespace Provider {
|
||||
priority = ["gpt-5-nano"]
|
||||
}
|
||||
for (const item of priority) {
|
||||
for (const model of Object.keys(provider.info.models)) {
|
||||
for (const model of Object.keys(provider.models)) {
|
||||
if (model.includes(item)) return getModel(providerID, model)
|
||||
}
|
||||
}
|
||||
@@ -764,7 +907,7 @@ export namespace Provider {
|
||||
|
||||
// Check if opencode provider is available before using it
|
||||
const opencodeProvider = await state().then((state) => state.providers["opencode"])
|
||||
if (opencodeProvider && opencodeProvider.info.models["gpt-5-nano"]) {
|
||||
if (opencodeProvider && opencodeProvider.models["gpt-5-nano"]) {
|
||||
return getModel("opencode", "gpt-5-nano")
|
||||
}
|
||||
|
||||
@@ -772,7 +915,7 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
|
||||
export function sort(models: ModelsDev.Model[]) {
|
||||
export function sort(models: Model[]) {
|
||||
return sortBy(
|
||||
models,
|
||||
[(model) => priority.findIndex((filter) => model.id.includes(filter)), "desc"],
|
||||
@@ -787,12 +930,12 @@ export namespace Provider {
|
||||
|
||||
const provider = await list()
|
||||
.then((val) => Object.values(val))
|
||||
.then((x) => x.find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.info.id)))
|
||||
.then((x) => x.find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id)))
|
||||
if (!provider) throw new Error("no providers found")
|
||||
const [model] = sort(Object.values(provider.info.models))
|
||||
const [model] = sort(Object.values(provider.models))
|
||||
if (!model) throw new Error("no models found")
|
||||
return {
|
||||
providerID: provider.info.id,
|
||||
providerID: provider.id,
|
||||
modelID: model.id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { APICallError, ModelMessage } from "ai"
|
||||
import { unique } from "remeda"
|
||||
import type { JSONSchema } from "zod/v4/core"
|
||||
import type { Provider } from "./provider"
|
||||
|
||||
export namespace ProviderTransform {
|
||||
function normalizeMessages(msgs: ModelMessage[], providerID: string, modelID: string): ModelMessage[] {
|
||||
if (modelID.includes("claude")) {
|
||||
function normalizeMessages(msgs: ModelMessage[], model: Provider.Model): ModelMessage[] {
|
||||
if (model.api.id.includes("claude")) {
|
||||
return msgs.map((msg) => {
|
||||
if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
|
||||
msg.content = msg.content.map((part) => {
|
||||
@@ -20,7 +21,7 @@ export namespace ProviderTransform {
|
||||
return msg
|
||||
})
|
||||
}
|
||||
if (providerID === "mistral" || modelID.toLowerCase().includes("mistral")) {
|
||||
if (model.providerID === "mistral" || model.api.id.toLowerCase().includes("mistral")) {
|
||||
const result: ModelMessage[] = []
|
||||
for (let i = 0; i < msgs.length; i++) {
|
||||
const msg = msgs[i]
|
||||
@@ -62,6 +63,46 @@ export namespace ProviderTransform {
|
||||
return result
|
||||
}
|
||||
|
||||
// DeepSeek: Handle reasoning_content for tool call continuations
|
||||
// - With tool calls: Include reasoning_content in providerOptions so model can continue reasoning
|
||||
// - Without tool calls: Strip reasoning (new turn doesn't need previous reasoning)
|
||||
// See: https://api-docs.deepseek.com/guides/thinking_mode
|
||||
if (model.providerID === "deepseek" || model.api.id.toLowerCase().includes("deepseek")) {
|
||||
return msgs.map((msg) => {
|
||||
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
||||
const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning")
|
||||
const hasToolCalls = msg.content.some((part: any) => part.type === "tool-call")
|
||||
const reasoningText = reasoningParts.map((part: any) => part.text).join("")
|
||||
|
||||
// Filter out reasoning parts from content
|
||||
const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning")
|
||||
|
||||
// If this message has tool calls and reasoning, include reasoning_content
|
||||
// so DeepSeek can continue reasoning after tool execution
|
||||
if (hasToolCalls && reasoningText) {
|
||||
return {
|
||||
...msg,
|
||||
content: filteredContent,
|
||||
providerOptions: {
|
||||
...msg.providerOptions,
|
||||
openaiCompatible: {
|
||||
...(msg.providerOptions as any)?.openaiCompatible,
|
||||
reasoning_content: reasoningText,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// For final answers (no tool calls), just strip reasoning
|
||||
return {
|
||||
...msg,
|
||||
content: filteredContent,
|
||||
}
|
||||
}
|
||||
return msg
|
||||
})
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
@@ -107,67 +148,68 @@ export namespace ProviderTransform {
|
||||
return msgs
|
||||
}
|
||||
|
||||
export function message(msgs: ModelMessage[], providerID: string, modelID: string) {
|
||||
msgs = normalizeMessages(msgs, providerID, modelID)
|
||||
if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) {
|
||||
msgs = applyCaching(msgs, providerID)
|
||||
export function message(msgs: ModelMessage[], model: Provider.Model) {
|
||||
msgs = normalizeMessages(msgs, model)
|
||||
if (model.providerID === "anthropic" || model.api.id.includes("anthropic") || model.api.id.includes("claude")) {
|
||||
msgs = applyCaching(msgs, model.providerID)
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
export function temperature(_providerID: string, modelID: string) {
|
||||
if (modelID.toLowerCase().includes("qwen")) return 0.55
|
||||
if (modelID.toLowerCase().includes("claude")) return undefined
|
||||
if (modelID.toLowerCase().includes("gemini-3-pro")) return 1.0
|
||||
export function temperature(model: Provider.Model) {
|
||||
if (model.api.id.toLowerCase().includes("qwen")) return 0.55
|
||||
if (model.api.id.toLowerCase().includes("claude")) return undefined
|
||||
if (model.api.id.toLowerCase().includes("gemini-3-pro")) return 1.0
|
||||
return 0
|
||||
}
|
||||
|
||||
export function topP(_providerID: string, modelID: string) {
|
||||
if (modelID.toLowerCase().includes("qwen")) return 1
|
||||
export function topP(model: Provider.Model) {
|
||||
if (model.api.id.toLowerCase().includes("qwen")) return 1
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function options(
|
||||
providerID: string,
|
||||
modelID: string,
|
||||
npm: string,
|
||||
model: Provider.Model,
|
||||
sessionID: string,
|
||||
providerOptions?: Record<string, any>,
|
||||
): Record<string, any> {
|
||||
const result: Record<string, any> = {}
|
||||
|
||||
// switch to providerID later, for now use this
|
||||
if (npm === "@openrouter/ai-sdk-provider") {
|
||||
if (model.api.npm === "@openrouter/ai-sdk-provider") {
|
||||
result["usage"] = {
|
||||
include: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (providerID === "openai" || providerOptions?.setCacheKey) {
|
||||
if (model.providerID === "openai" || providerOptions?.setCacheKey) {
|
||||
result["promptCacheKey"] = sessionID
|
||||
}
|
||||
|
||||
if (providerID === "google" || (providerID.startsWith("opencode") && modelID.includes("gemini-3"))) {
|
||||
if (
|
||||
model.providerID === "google" ||
|
||||
(model.providerID.startsWith("opencode") && model.api.id.includes("gemini-3"))
|
||||
) {
|
||||
result["thinkingConfig"] = {
|
||||
includeThoughts: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (modelID.includes("gpt-5") && !modelID.includes("gpt-5-chat")) {
|
||||
if (modelID.includes("codex")) {
|
||||
if (model.api.id.includes("gpt-5") && !model.api.id.includes("gpt-5-chat")) {
|
||||
if (model.providerID.includes("codex")) {
|
||||
result["store"] = false
|
||||
}
|
||||
|
||||
if (!modelID.includes("codex") && !modelID.includes("gpt-5-pro")) {
|
||||
if (!model.api.id.includes("codex") && !model.api.id.includes("gpt-5-pro")) {
|
||||
result["reasoningEffort"] = "medium"
|
||||
}
|
||||
|
||||
if (modelID.endsWith("gpt-5.1") && providerID !== "azure") {
|
||||
if (model.api.id.endsWith("gpt-5.1") && model.providerID !== "azure") {
|
||||
result["textVerbosity"] = "low"
|
||||
}
|
||||
|
||||
if (providerID.startsWith("opencode")) {
|
||||
if (model.providerID.startsWith("opencode")) {
|
||||
result["promptCacheKey"] = sessionID
|
||||
result["include"] = ["reasoning.encrypted_content"]
|
||||
result["reasoningSummary"] = "auto"
|
||||
@@ -176,17 +218,17 @@ export namespace ProviderTransform {
|
||||
return result
|
||||
}
|
||||
|
||||
export function smallOptions(input: { providerID: string; modelID: string }) {
|
||||
export function smallOptions(model: Provider.Model) {
|
||||
const options: Record<string, any> = {}
|
||||
|
||||
if (input.providerID === "openai" || input.modelID.includes("gpt-5")) {
|
||||
if (input.modelID.includes("5.1")) {
|
||||
if (model.providerID === "openai" || model.api.id.includes("gpt-5")) {
|
||||
if (model.api.id.includes("5.1")) {
|
||||
options["reasoningEffort"] = "low"
|
||||
} else {
|
||||
options["reasoningEffort"] = "minimal"
|
||||
}
|
||||
}
|
||||
if (input.providerID === "google") {
|
||||
if (model.providerID === "google") {
|
||||
options["thinkingConfig"] = {
|
||||
thinkingBudget: 0,
|
||||
}
|
||||
@@ -254,7 +296,7 @@ export namespace ProviderTransform {
|
||||
return standardLimit
|
||||
}
|
||||
|
||||
export function schema(providerID: string, modelID: string, schema: JSONSchema.BaseSchema) {
|
||||
export function schema(model: Provider.Model, schema: JSONSchema.BaseSchema) {
|
||||
/*
|
||||
if (["openai", "azure"].includes(providerID)) {
|
||||
if (schema.type === "object" && schema.properties) {
|
||||
@@ -274,7 +316,7 @@ export namespace ProviderTransform {
|
||||
*/
|
||||
|
||||
// Convert integer enums to string enums for Google/Gemini
|
||||
if (providerID === "google" || modelID.includes("gemini")) {
|
||||
if (model.providerID === "google" || model.api.id.includes("gemini")) {
|
||||
const sanitizeGemini = (obj: any): any => {
|
||||
if (obj === null || typeof obj !== "object") {
|
||||
return obj
|
||||
|
||||
226
packages/opencode/src/pty/index.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { type IPty } from "bun-pty"
|
||||
import z from "zod"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Log } from "../util/log"
|
||||
import { Bus } from "../bus"
|
||||
import type { WSContext } from "hono/ws"
|
||||
import { Instance } from "../project/instance"
|
||||
import { shell } from "@opencode-ai/util/shell"
|
||||
import { lazy } from "@opencode-ai/util/lazy"
|
||||
import {} from "process"
|
||||
import { Installation } from "@/installation"
|
||||
|
||||
export namespace Pty {
|
||||
const log = Log.create({ service: "pty" })
|
||||
|
||||
const pty = lazy(async () => {
|
||||
if (!Installation.isLocal()) {
|
||||
const path = require(
|
||||
`bun-pty/rust-pty/target/release/${
|
||||
process.platform === "win32"
|
||||
? "rust_pty.dll"
|
||||
: process.platform === "linux" && process.arch === "x64"
|
||||
? "librust_pty.so"
|
||||
: process.platform === "darwin" && process.arch === "x64"
|
||||
? "librust_pty.dylib"
|
||||
: process.platform === "darwin" && process.arch === "arm64"
|
||||
? "librust_pty_arm64.dylib"
|
||||
: process.platform === "linux" && process.arch === "arm64"
|
||||
? "librust_pty_arm64.so"
|
||||
: ""
|
||||
}`,
|
||||
)
|
||||
process.env.BUN_PTY_LIB = path
|
||||
}
|
||||
const { spawn } = await import("bun-pty")
|
||||
return spawn
|
||||
})
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
id: Identifier.schema("pty"),
|
||||
title: z.string(),
|
||||
command: z.string(),
|
||||
args: z.array(z.string()),
|
||||
cwd: z.string(),
|
||||
status: z.enum(["running", "exited"]),
|
||||
pid: z.number(),
|
||||
})
|
||||
.meta({ ref: "Pty" })
|
||||
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
export const CreateInput = z.object({
|
||||
command: z.string().optional(),
|
||||
args: z.array(z.string()).optional(),
|
||||
cwd: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
env: z.record(z.string(), z.string()).optional(),
|
||||
})
|
||||
|
||||
export type CreateInput = z.infer<typeof CreateInput>
|
||||
|
||||
export const UpdateInput = z.object({
|
||||
title: z.string().optional(),
|
||||
size: z
|
||||
.object({
|
||||
rows: z.number(),
|
||||
cols: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export type UpdateInput = z.infer<typeof UpdateInput>
|
||||
|
||||
export const Event = {
|
||||
Created: Bus.event("pty.created", z.object({ info: Info })),
|
||||
Updated: Bus.event("pty.updated", z.object({ info: Info })),
|
||||
Exited: Bus.event("pty.exited", z.object({ id: Identifier.schema("pty"), exitCode: z.number() })),
|
||||
Deleted: Bus.event("pty.deleted", z.object({ id: Identifier.schema("pty") })),
|
||||
}
|
||||
|
||||
interface ActiveSession {
|
||||
info: Info
|
||||
process: IPty
|
||||
buffer: string
|
||||
subscribers: Set<WSContext>
|
||||
}
|
||||
|
||||
const state = Instance.state(
|
||||
() => new Map<string, ActiveSession>(),
|
||||
async (sessions) => {
|
||||
for (const session of sessions.values()) {
|
||||
try {
|
||||
session.process.kill()
|
||||
} catch {}
|
||||
for (const ws of session.subscribers) {
|
||||
ws.close()
|
||||
}
|
||||
}
|
||||
sessions.clear()
|
||||
},
|
||||
)
|
||||
|
||||
export function list() {
|
||||
return Array.from(state().values()).map((s) => s.info)
|
||||
}
|
||||
|
||||
export function get(id: string) {
|
||||
return state().get(id)?.info
|
||||
}
|
||||
|
||||
export async function create(input: CreateInput) {
|
||||
const id = Identifier.create("pty", false)
|
||||
const command = input.command || shell()
|
||||
const args = input.args || []
|
||||
const cwd = input.cwd || Instance.directory
|
||||
const env = { ...process.env, ...input.env } as Record<string, string>
|
||||
log.info("creating session", { id, cmd: command, args, cwd })
|
||||
|
||||
const spawn = await pty()
|
||||
const ptyProcess = spawn(command, args, {
|
||||
name: "xterm-256color",
|
||||
cwd,
|
||||
env,
|
||||
})
|
||||
const info = {
|
||||
id,
|
||||
title: input.title || `Terminal ${id.slice(-4)}`,
|
||||
command,
|
||||
args,
|
||||
cwd,
|
||||
status: "running",
|
||||
pid: ptyProcess.pid,
|
||||
} as const
|
||||
const session: ActiveSession = {
|
||||
info,
|
||||
process: ptyProcess,
|
||||
buffer: "",
|
||||
subscribers: new Set(),
|
||||
}
|
||||
state().set(id, session)
|
||||
ptyProcess.onData((data) => {
|
||||
if (session.subscribers.size === 0) {
|
||||
session.buffer += data
|
||||
return
|
||||
}
|
||||
for (const ws of session.subscribers) {
|
||||
if (ws.readyState === 1) {
|
||||
ws.send(data)
|
||||
}
|
||||
}
|
||||
})
|
||||
ptyProcess.onExit(({ exitCode }) => {
|
||||
log.info("session exited", { id, exitCode })
|
||||
session.info.status = "exited"
|
||||
Bus.publish(Event.Exited, { id, exitCode })
|
||||
state().delete(id)
|
||||
})
|
||||
Bus.publish(Event.Created, { info })
|
||||
return info
|
||||
}
|
||||
|
||||
export async function update(id: string, input: UpdateInput) {
|
||||
const session = state().get(id)
|
||||
if (!session) return
|
||||
if (input.title) {
|
||||
session.info.title = input.title
|
||||
}
|
||||
if (input.size) {
|
||||
session.process.resize(input.size.cols, input.size.rows)
|
||||
}
|
||||
Bus.publish(Event.Updated, { info: session.info })
|
||||
return session.info
|
||||
}
|
||||
|
||||
export async function remove(id: string) {
|
||||
const session = state().get(id)
|
||||
if (!session) return
|
||||
log.info("removing session", { id })
|
||||
try {
|
||||
session.process.kill()
|
||||
} catch {}
|
||||
for (const ws of session.subscribers) {
|
||||
ws.close()
|
||||
}
|
||||
state().delete(id)
|
||||
Bus.publish(Event.Deleted, { id })
|
||||
}
|
||||
|
||||
export function resize(id: string, cols: number, rows: number) {
|
||||
const session = state().get(id)
|
||||
if (session && session.info.status === "running") {
|
||||
session.process.resize(cols, rows)
|
||||
}
|
||||
}
|
||||
|
||||
export function write(id: string, data: string) {
|
||||
const session = state().get(id)
|
||||
if (session && session.info.status === "running") {
|
||||
session.process.write(data)
|
||||
}
|
||||
}
|
||||
|
||||
export function connect(id: string, ws: WSContext) {
|
||||
const session = state().get(id)
|
||||
if (!session) {
|
||||
ws.close()
|
||||
return
|
||||
}
|
||||
log.info("client connected to session", { id })
|
||||
session.subscribers.add(ws)
|
||||
if (session.buffer) {
|
||||
ws.send(session.buffer)
|
||||
session.buffer = ""
|
||||
}
|
||||
return {
|
||||
onMessage: (message: string | ArrayBuffer) => {
|
||||
session.process.write(String(message))
|
||||
},
|
||||
onClose: () => {
|
||||
log.info("client disconnected from session", { id })
|
||||
session.subscribers.delete(ws)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
36
packages/opencode/src/server/error.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { resolver } from "hono-openapi"
|
||||
import z from "zod"
|
||||
import { Storage } from "../storage/storage"
|
||||
|
||||
export const ERRORS = {
|
||||
400: {
|
||||
description: "Bad request",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z
|
||||
.object({
|
||||
data: z.any(),
|
||||
errors: z.array(z.record(z.string(), z.any())),
|
||||
success: z.literal(false),
|
||||
})
|
||||
.meta({
|
||||
ref: "BadRequestError",
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "Not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Storage.NotFoundError.Schema),
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
export function errors(...codes: number[]) {
|
||||
return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]]))
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { proxy } from "hono/proxy"
|
||||
import { Session } from "../session"
|
||||
import z from "zod"
|
||||
import { Provider } from "../provider/provider"
|
||||
import { mapValues } from "remeda"
|
||||
import { mapValues, pipe } from "remeda"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { ModelsDev } from "../provider/models"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
@@ -43,43 +43,13 @@ import { Snapshot } from "@/snapshot"
|
||||
import { SessionSummary } from "@/session/summary"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { SessionStatus } from "@/session/status"
|
||||
import { upgradeWebSocket, websocket } from "hono/bun"
|
||||
import { errors } from "./error"
|
||||
import { Pty } from "@/pty"
|
||||
|
||||
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
|
||||
globalThis.AI_SDK_LOG_WARNINGS = false
|
||||
|
||||
const ERRORS = {
|
||||
400: {
|
||||
description: "Bad request",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z
|
||||
.object({
|
||||
data: z.any(),
|
||||
errors: z.array(z.record(z.string(), z.any())),
|
||||
success: z.literal(false),
|
||||
})
|
||||
.meta({
|
||||
ref: "BadRequestError",
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "Not found",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Storage.NotFoundError.Schema),
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
function errors(...codes: number[]) {
|
||||
return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]]))
|
||||
}
|
||||
|
||||
export namespace Server {
|
||||
const log = Log.create({ service: "server" })
|
||||
|
||||
@@ -192,7 +162,161 @@ export namespace Server {
|
||||
}),
|
||||
)
|
||||
.use(validator("query", z.object({ directory: z.string().optional() })))
|
||||
|
||||
.route("/project", ProjectRoute)
|
||||
|
||||
.get(
|
||||
"/pty",
|
||||
describeRoute({
|
||||
description: "List all PTY sessions",
|
||||
operationId: "pty.list",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of sessions",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Pty.Info.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json(Pty.list())
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/pty",
|
||||
describeRoute({
|
||||
description: "Create a new PTY session",
|
||||
operationId: "pty.create",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Pty.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
}),
|
||||
validator("json", Pty.CreateInput),
|
||||
async (c) => {
|
||||
const info = await Pty.create(c.req.valid("json"))
|
||||
return c.json(info)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/pty/:id",
|
||||
describeRoute({
|
||||
description: "Get PTY session info",
|
||||
operationId: "pty.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Session info",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Pty.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(404),
|
||||
},
|
||||
}),
|
||||
validator("param", z.object({ id: z.string() })),
|
||||
async (c) => {
|
||||
const info = Pty.get(c.req.valid("param").id)
|
||||
if (!info) {
|
||||
throw new Storage.NotFoundError({ message: "Session not found" })
|
||||
}
|
||||
return c.json(info)
|
||||
},
|
||||
)
|
||||
.put(
|
||||
"/pty/:id",
|
||||
describeRoute({
|
||||
description: "Update PTY session",
|
||||
operationId: "pty.update",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Pty.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
}),
|
||||
validator("param", z.object({ id: z.string() })),
|
||||
validator("json", Pty.UpdateInput),
|
||||
async (c) => {
|
||||
const info = await Pty.update(c.req.valid("param").id, c.req.valid("json"))
|
||||
return c.json(info)
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/pty/:id",
|
||||
describeRoute({
|
||||
description: "Remove a PTY session",
|
||||
operationId: "pty.remove",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Session removed",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(404),
|
||||
},
|
||||
}),
|
||||
validator("param", z.object({ id: z.string() })),
|
||||
async (c) => {
|
||||
await Pty.remove(c.req.valid("param").id)
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/pty/:id/connect",
|
||||
describeRoute({
|
||||
description: "Connect to a PTY session",
|
||||
operationId: "pty.connect",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Connected session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(404),
|
||||
},
|
||||
}),
|
||||
validator("param", z.object({ id: z.string() })),
|
||||
upgradeWebSocket((c) => {
|
||||
const id = c.req.param("id")
|
||||
let handler: ReturnType<typeof Pty.connect>
|
||||
if (!Pty.get(id)) throw new Error("Session not found")
|
||||
return {
|
||||
onOpen(_event, ws) {
|
||||
handler = Pty.connect(id, ws)
|
||||
},
|
||||
onMessage(event) {
|
||||
handler?.onMessage(String(event.data))
|
||||
},
|
||||
onClose() {
|
||||
handler?.onClose()
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
.get(
|
||||
"/config",
|
||||
describeRoute({
|
||||
@@ -296,8 +420,8 @@ export namespace Server {
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const { provider, model } = c.req.valid("query")
|
||||
const tools = await ToolRegistry.tools(provider, model)
|
||||
const { provider } = c.req.valid("query")
|
||||
const tools = await ToolRegistry.tools(provider)
|
||||
return c.json(
|
||||
tools.map((t) => ({
|
||||
id: t.id,
|
||||
@@ -1025,7 +1149,7 @@ export namespace Server {
|
||||
async (c) => {
|
||||
c.status(204)
|
||||
c.header("Content-Type", "application/json")
|
||||
return stream(c, async (stream) => {
|
||||
return stream(c, async () => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
SessionPrompt.prompt({ ...body, sessionID })
|
||||
@@ -1231,7 +1355,7 @@ export namespace Server {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
providers: ModelsDev.Provider.array(),
|
||||
providers: Provider.Info.array(),
|
||||
default: z.record(z.string(), z.string()),
|
||||
}),
|
||||
),
|
||||
@@ -1242,7 +1366,7 @@ export namespace Server {
|
||||
}),
|
||||
async (c) => {
|
||||
using _ = log.time("providers")
|
||||
const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info))
|
||||
const providers = await Provider.list().then((x) => mapValues(x, (item) => item))
|
||||
return c.json({
|
||||
providers: Object.values(providers),
|
||||
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
|
||||
@@ -1272,7 +1396,10 @@ export namespace Server {
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const providers = await ModelsDev.get()
|
||||
const providers = pipe(
|
||||
await ModelsDev.get(),
|
||||
mapValues((x) => Provider.fromModelsDevProvider(x)),
|
||||
)
|
||||
const connected = await Provider.list().then((x) => Object.keys(x))
|
||||
return c.json({
|
||||
all: Object.values(providers),
|
||||
@@ -2080,6 +2207,7 @@ export namespace Server {
|
||||
hostname: opts.hostname,
|
||||
idleTimeout: 0,
|
||||
fetch: App().fetch,
|
||||
websocket: websocket,
|
||||
})
|
||||
return server
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { streamText, wrapLanguageModel, type ModelMessage } from "ai"
|
||||
import { wrapLanguageModel, type ModelMessage } from "ai"
|
||||
import { Session } from "."
|
||||
import { Identifier } from "../id/id"
|
||||
import { Instance } from "../project/instance"
|
||||
@@ -7,10 +7,10 @@ import { MessageV2 } from "./message-v2"
|
||||
import { SystemPrompt } from "./system"
|
||||
import { Bus } from "../bus"
|
||||
import z from "zod"
|
||||
import type { ModelsDev } from "../provider/models"
|
||||
import { SessionPrompt } from "./prompt"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Token } from "../util/token"
|
||||
import { Config } from "../config/config"
|
||||
import { Log } from "../util/log"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
import { SessionProcessor } from "./processor"
|
||||
@@ -29,7 +29,7 @@ export namespace SessionCompaction {
|
||||
),
|
||||
}
|
||||
|
||||
export function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) {
|
||||
export function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) {
|
||||
if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) return false
|
||||
const context = input.model.limit.context
|
||||
if (context === 0) return false
|
||||
@@ -97,7 +97,9 @@ export namespace SessionCompaction {
|
||||
abort: AbortSignal
|
||||
auto: boolean
|
||||
}) {
|
||||
const cfg = await Config.get()
|
||||
const model = await Provider.getModel(input.model.providerID, input.model.modelID)
|
||||
const language = await Provider.getLanguage(model)
|
||||
const system = [...SystemPrompt.compaction(model.providerID)]
|
||||
const msg = (await Session.updateMessage({
|
||||
id: Identifier.ascending("message"),
|
||||
@@ -126,79 +128,73 @@ export namespace SessionCompaction {
|
||||
const processor = SessionProcessor.create({
|
||||
assistantMessage: msg,
|
||||
sessionID: input.sessionID,
|
||||
providerID: input.model.providerID,
|
||||
model: model.info,
|
||||
model: model,
|
||||
abort: input.abort,
|
||||
})
|
||||
const result = await processor.process(() =>
|
||||
streamText({
|
||||
onError(error) {
|
||||
log.error("stream error", {
|
||||
error,
|
||||
})
|
||||
},
|
||||
// set to 0, we handle loop
|
||||
maxRetries: 0,
|
||||
providerOptions: ProviderTransform.providerOptions(
|
||||
model.npm,
|
||||
model.providerID,
|
||||
pipe(
|
||||
{},
|
||||
mergeDeep(ProviderTransform.options(model.providerID, model.modelID, model.npm ?? "", input.sessionID)),
|
||||
mergeDeep(model.info.options),
|
||||
),
|
||||
const result = await processor.process({
|
||||
onError(error) {
|
||||
log.error("stream error", {
|
||||
error,
|
||||
})
|
||||
},
|
||||
// set to 0, we handle loop
|
||||
maxRetries: 0,
|
||||
providerOptions: ProviderTransform.providerOptions(
|
||||
model.api.npm,
|
||||
model.providerID,
|
||||
pipe({}, mergeDeep(ProviderTransform.options(model, input.sessionID)), mergeDeep(model.options)),
|
||||
),
|
||||
headers: model.headers,
|
||||
abortSignal: input.abort,
|
||||
tools: model.capabilities.toolcall ? {} : undefined,
|
||||
messages: [
|
||||
...system.map(
|
||||
(x): ModelMessage => ({
|
||||
role: "system",
|
||||
content: x,
|
||||
}),
|
||||
),
|
||||
headers: model.info.headers,
|
||||
abortSignal: input.abort,
|
||||
tools: model.info.tool_call ? {} : undefined,
|
||||
messages: [
|
||||
...system.map(
|
||||
(x): ModelMessage => ({
|
||||
role: "system",
|
||||
content: x,
|
||||
}),
|
||||
),
|
||||
...MessageV2.toModelMessage(
|
||||
input.messages.filter((m) => {
|
||||
if (m.info.role !== "assistant" || m.info.error === undefined) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
MessageV2.AbortedError.isInstance(m.info.error) &&
|
||||
m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
|
||||
) {
|
||||
return true
|
||||
}
|
||||
...MessageV2.toModelMessage(
|
||||
input.messages.filter((m) => {
|
||||
if (m.info.role !== "assistant" || m.info.error === undefined) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
MessageV2.AbortedError.isInstance(m.info.error) &&
|
||||
m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}),
|
||||
),
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Summarize our conversation above. This summary will be the only context available when the conversation continues, so preserve critical information including: what was accomplished, current work in progress, files involved, next steps, and any key user requests or constraints. Be concise but detailed enough that work can continue seamlessly.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
model: wrapLanguageModel({
|
||||
model: model.language,
|
||||
middleware: [
|
||||
return false
|
||||
}),
|
||||
),
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
async transformParams(args) {
|
||||
if (args.type === "stream") {
|
||||
// @ts-expect-error
|
||||
args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID)
|
||||
}
|
||||
return args.params
|
||||
},
|
||||
type: "text",
|
||||
text: "Summarize our conversation above. This summary will be the only context available when the conversation continues, so preserve critical information including: what was accomplished, current work in progress, files involved, next steps, and any key user requests or constraints. Be concise but detailed enough that work can continue seamlessly.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
model: wrapLanguageModel({
|
||||
model: language,
|
||||
middleware: [
|
||||
{
|
||||
async transformParams(args) {
|
||||
if (args.type === "stream") {
|
||||
// @ts-expect-error
|
||||
args.params.prompt = ProviderTransform.message(args.params.prompt, model)
|
||||
}
|
||||
return args.params
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
||||
})
|
||||
if (result === "continue" && input.auto) {
|
||||
const continueMsg = await Session.updateMessage({
|
||||
id: Identifier.ascending("message"),
|
||||
|
||||
@@ -6,8 +6,7 @@ import { Config } from "../config/config"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Installation } from "../installation"
|
||||
import type { ModelsDev } from "../provider/models"
|
||||
import { Share } from "../share/share"
|
||||
|
||||
import { Storage } from "../storage/storage"
|
||||
import { Log } from "../util/log"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
@@ -16,7 +15,8 @@ import { SessionPrompt } from "./prompt"
|
||||
import { fn } from "@/util/fn"
|
||||
import { Command } from "../command"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { ShareNext } from "@/share/share-next"
|
||||
|
||||
import type { Provider } from "@/provider/provider"
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
@@ -223,6 +223,7 @@ export namespace Session {
|
||||
}
|
||||
|
||||
if (cfg.enterprise?.url) {
|
||||
const { ShareNext } = await import("@/share/share-next")
|
||||
const share = await ShareNext.create(id)
|
||||
await update(id, (draft) => {
|
||||
draft.share = {
|
||||
@@ -233,6 +234,7 @@ export namespace Session {
|
||||
|
||||
const session = await get(id)
|
||||
if (session.share) return session.share
|
||||
const { Share } = await import("../share/share")
|
||||
const share = await Share.create(id)
|
||||
await update(id, (draft) => {
|
||||
draft.share = {
|
||||
@@ -253,6 +255,7 @@ export namespace Session {
|
||||
export const unshare = fn(Identifier.schema("session"), async (id) => {
|
||||
const cfg = await Config.get()
|
||||
if (cfg.enterprise?.url) {
|
||||
const { ShareNext } = await import("@/share/share-next")
|
||||
await ShareNext.remove(id)
|
||||
await update(id, (draft) => {
|
||||
draft.share = undefined
|
||||
@@ -264,6 +267,7 @@ export namespace Session {
|
||||
await update(id, (draft) => {
|
||||
draft.share = undefined
|
||||
})
|
||||
const { Share } = await import("../share/share")
|
||||
await Share.remove(id, share.secret)
|
||||
})
|
||||
|
||||
@@ -389,7 +393,7 @@ export namespace Session {
|
||||
|
||||
export const getUsage = fn(
|
||||
z.object({
|
||||
model: z.custom<ModelsDev.Model>(),
|
||||
model: z.custom<Provider.Model>(),
|
||||
usage: z.custom<LanguageModelUsage>(),
|
||||
metadata: z.custom<ProviderMetadata>().optional(),
|
||||
}),
|
||||
@@ -420,16 +424,16 @@ export namespace Session {
|
||||
}
|
||||
|
||||
const costInfo =
|
||||
input.model.cost?.context_over_200k && tokens.input + tokens.cache.read > 200_000
|
||||
? input.model.cost.context_over_200k
|
||||
input.model.cost?.experimentalOver200K && tokens.input + tokens.cache.read > 200_000
|
||||
? input.model.cost.experimentalOver200K
|
||||
: input.model.cost
|
||||
return {
|
||||
cost: safe(
|
||||
new Decimal(0)
|
||||
.add(new Decimal(tokens.input).mul(costInfo?.input ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.output).mul(costInfo?.output ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.read).mul(costInfo?.cache_read ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.write).mul(costInfo?.cache_write ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.read).mul(costInfo?.cache?.read ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.write).mul(costInfo?.cache?.write ?? 0).div(1_000_000))
|
||||
// TODO: update models.dev to have better pricing model, for now:
|
||||
// charge reasoning tokens at the same rate as output tokens
|
||||
.add(new Decimal(tokens.reasoning).mul(costInfo?.output ?? 0).div(1_000_000))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ModelsDev } from "@/provider/models"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { type StreamTextResult, type Tool as AITool, APICallError } from "ai"
|
||||
import { streamText } from "ai"
|
||||
import { Log } from "@/util/log"
|
||||
import { Identifier } from "@/id/id"
|
||||
import { Session } from "."
|
||||
@@ -11,6 +10,7 @@ import { SessionSummary } from "./summary"
|
||||
import { Bus } from "@/bus"
|
||||
import { SessionRetry } from "./retry"
|
||||
import { SessionStatus } from "./status"
|
||||
import type { Provider } from "@/provider/provider"
|
||||
|
||||
export namespace SessionProcessor {
|
||||
const DOOM_LOOP_THRESHOLD = 3
|
||||
@@ -19,11 +19,19 @@ export namespace SessionProcessor {
|
||||
export type Info = Awaited<ReturnType<typeof create>>
|
||||
export type Result = Awaited<ReturnType<Info["process"]>>
|
||||
|
||||
export type StreamInput = Parameters<typeof streamText>[0]
|
||||
|
||||
export type TBD = {
|
||||
model: {
|
||||
modelID: string
|
||||
providerID: string
|
||||
}
|
||||
}
|
||||
|
||||
export function create(input: {
|
||||
assistantMessage: MessageV2.Assistant
|
||||
sessionID: string
|
||||
providerID: string
|
||||
model: ModelsDev.Model
|
||||
model: Provider.Model
|
||||
abort: AbortSignal
|
||||
}) {
|
||||
const toolcalls: Record<string, MessageV2.ToolPart> = {}
|
||||
@@ -38,13 +46,13 @@ export namespace SessionProcessor {
|
||||
partFromToolCall(toolCallID: string) {
|
||||
return toolcalls[toolCallID]
|
||||
},
|
||||
async process(fn: () => StreamTextResult<Record<string, AITool>, never>) {
|
||||
async process(streamInput: StreamInput) {
|
||||
log.info("process")
|
||||
while (true) {
|
||||
try {
|
||||
let currentText: MessageV2.TextPart | undefined
|
||||
let reasoningMap: Record<string, MessageV2.ReasoningPart> = {}
|
||||
const stream = fn()
|
||||
const stream = streamText(streamInput)
|
||||
|
||||
for await (const value of stream.fullStream) {
|
||||
input.abort.throwIfAborted()
|
||||
@@ -328,11 +336,12 @@ export namespace SessionProcessor {
|
||||
continue
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log.error("process", {
|
||||
error: e,
|
||||
stack: JSON.stringify(e.stack),
|
||||
})
|
||||
const error = MessageV2.fromError(e, { providerID: input.providerID })
|
||||
const error = MessageV2.fromError(e, { providerID: input.model.providerID })
|
||||
const retry = SessionRetry.retryable(error)
|
||||
if (retry !== undefined) {
|
||||
attempt++
|
||||
|
||||
@@ -11,7 +11,6 @@ import { Agent } from "../agent/agent"
|
||||
import { Provider } from "../provider/provider"
|
||||
import {
|
||||
generateText,
|
||||
streamText,
|
||||
type ModelMessage,
|
||||
type Tool as AITool,
|
||||
tool,
|
||||
@@ -28,6 +27,7 @@ import { Plugin } from "../plugin"
|
||||
|
||||
import PROMPT_PLAN from "../session/prompt/plan.txt"
|
||||
import BUILD_SWITCH from "../session/prompt/build-switch.txt"
|
||||
import MAX_STEPS from "../session/prompt/max-steps.txt"
|
||||
import { defer } from "../util/defer"
|
||||
import { mergeDeep, pipe } from "remeda"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
@@ -43,6 +43,7 @@ import { Command } from "../command"
|
||||
import { $, fileURLToPath } from "bun"
|
||||
import { ConfigMarkdown } from "../config/markdown"
|
||||
import { SessionSummary } from "./summary"
|
||||
import { Config } from "../config/config"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { fn } from "@/util/fn"
|
||||
import { SessionProcessor } from "./processor"
|
||||
@@ -288,6 +289,7 @@ export namespace SessionPrompt {
|
||||
})
|
||||
|
||||
const model = await Provider.getModel(lastUser.model.providerID, lastUser.model.modelID)
|
||||
const language = await Provider.getLanguage(model)
|
||||
const task = tasks.pop()
|
||||
|
||||
// pending subtask
|
||||
@@ -311,7 +313,7 @@ export namespace SessionPrompt {
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: model.modelID,
|
||||
modelID: model.id,
|
||||
providerID: model.providerID,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
@@ -408,7 +410,7 @@ export namespace SessionPrompt {
|
||||
agent: lastUser.agent,
|
||||
model: {
|
||||
providerID: model.providerID,
|
||||
modelID: model.modelID,
|
||||
modelID: model.id,
|
||||
},
|
||||
sessionID,
|
||||
auto: task.auto,
|
||||
@@ -421,7 +423,7 @@ export namespace SessionPrompt {
|
||||
if (
|
||||
lastFinished &&
|
||||
lastFinished.summary !== true &&
|
||||
SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model: model.info })
|
||||
SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model })
|
||||
) {
|
||||
await SessionCompaction.create({
|
||||
sessionID,
|
||||
@@ -433,7 +435,10 @@ export namespace SessionPrompt {
|
||||
}
|
||||
|
||||
// normal processing
|
||||
const cfg = await Config.get()
|
||||
const agent = await Agent.get(lastUser.agent)
|
||||
const maxSteps = agent.maxSteps ?? Infinity
|
||||
const isLastStep = step >= maxSteps
|
||||
msgs = insertReminders({
|
||||
messages: msgs,
|
||||
agent,
|
||||
@@ -455,7 +460,7 @@ export namespace SessionPrompt {
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: model.modelID,
|
||||
modelID: model.id,
|
||||
providerID: model.providerID,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
@@ -463,20 +468,19 @@ export namespace SessionPrompt {
|
||||
sessionID,
|
||||
})) as MessageV2.Assistant,
|
||||
sessionID: sessionID,
|
||||
model: model.info,
|
||||
providerID: model.providerID,
|
||||
model,
|
||||
abort,
|
||||
})
|
||||
const system = await resolveSystemPrompt({
|
||||
providerID: model.providerID,
|
||||
modelID: model.info.id,
|
||||
model,
|
||||
agent,
|
||||
system: lastUser.system,
|
||||
isLastStep,
|
||||
})
|
||||
const tools = await resolveTools({
|
||||
agent,
|
||||
sessionID,
|
||||
model: lastUser.model,
|
||||
model,
|
||||
tools: lastUser.tools,
|
||||
processor,
|
||||
})
|
||||
@@ -486,21 +490,19 @@ export namespace SessionPrompt {
|
||||
{
|
||||
sessionID: sessionID,
|
||||
agent: lastUser.agent,
|
||||
model: model.info,
|
||||
model: model,
|
||||
provider,
|
||||
message: lastUser,
|
||||
},
|
||||
{
|
||||
temperature: model.info.temperature
|
||||
? (agent.temperature ?? ProviderTransform.temperature(model.providerID, model.modelID))
|
||||
temperature: model.capabilities.temperature
|
||||
? (agent.temperature ?? ProviderTransform.temperature(model))
|
||||
: undefined,
|
||||
topP: agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID),
|
||||
topP: agent.topP ?? ProviderTransform.topP(model),
|
||||
options: pipe(
|
||||
{},
|
||||
mergeDeep(
|
||||
ProviderTransform.options(model.providerID, model.modelID, model.npm ?? "", sessionID, provider?.options),
|
||||
),
|
||||
mergeDeep(model.info.options),
|
||||
mergeDeep(ProviderTransform.options(model, sessionID, provider?.options)),
|
||||
mergeDeep(model.options),
|
||||
mergeDeep(agent.options),
|
||||
),
|
||||
},
|
||||
@@ -513,113 +515,121 @@ export namespace SessionPrompt {
|
||||
})
|
||||
}
|
||||
|
||||
const result = await processor.process(() =>
|
||||
streamText({
|
||||
onError(error) {
|
||||
log.error("stream error", {
|
||||
error,
|
||||
const result = await processor.process({
|
||||
onError(error) {
|
||||
log.error("stream error", {
|
||||
error,
|
||||
})
|
||||
},
|
||||
async experimental_repairToolCall(input) {
|
||||
const lower = input.toolCall.toolName.toLowerCase()
|
||||
if (lower !== input.toolCall.toolName && tools[lower]) {
|
||||
log.info("repairing tool call", {
|
||||
tool: input.toolCall.toolName,
|
||||
repaired: lower,
|
||||
})
|
||||
},
|
||||
async experimental_repairToolCall(input) {
|
||||
const lower = input.toolCall.toolName.toLowerCase()
|
||||
if (lower !== input.toolCall.toolName && tools[lower]) {
|
||||
log.info("repairing tool call", {
|
||||
tool: input.toolCall.toolName,
|
||||
repaired: lower,
|
||||
})
|
||||
return {
|
||||
...input.toolCall,
|
||||
toolName: lower,
|
||||
}
|
||||
}
|
||||
return {
|
||||
...input.toolCall,
|
||||
input: JSON.stringify({
|
||||
tool: input.toolCall.toolName,
|
||||
error: input.error.message,
|
||||
}),
|
||||
toolName: "invalid",
|
||||
toolName: lower,
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
...(model.providerID.startsWith("opencode")
|
||||
? {
|
||||
"x-opencode-project": Instance.project.id,
|
||||
"x-opencode-session": sessionID,
|
||||
"x-opencode-request": lastUser.id,
|
||||
}
|
||||
: undefined),
|
||||
...model.info.headers,
|
||||
},
|
||||
// set to 0, we handle loop
|
||||
maxRetries: 0,
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||
maxOutputTokens: ProviderTransform.maxOutputTokens(
|
||||
model.providerID,
|
||||
params.options,
|
||||
model.info.limit.output,
|
||||
OUTPUT_TOKEN_MAX,
|
||||
}
|
||||
return {
|
||||
...input.toolCall,
|
||||
input: JSON.stringify({
|
||||
tool: input.toolCall.toolName,
|
||||
error: input.error.message,
|
||||
}),
|
||||
toolName: "invalid",
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
...(model.providerID.startsWith("opencode")
|
||||
? {
|
||||
"x-opencode-project": Instance.project.id,
|
||||
"x-opencode-session": sessionID,
|
||||
"x-opencode-request": lastUser.id,
|
||||
}
|
||||
: undefined),
|
||||
...model.headers,
|
||||
},
|
||||
// set to 0, we handle loop
|
||||
maxRetries: 0,
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||
maxOutputTokens: ProviderTransform.maxOutputTokens(
|
||||
model.api.npm,
|
||||
params.options,
|
||||
model.limit.output,
|
||||
OUTPUT_TOKEN_MAX,
|
||||
),
|
||||
abortSignal: abort,
|
||||
providerOptions: ProviderTransform.providerOptions(model.api.npm, model.providerID, params.options),
|
||||
stopWhen: stepCountIs(1),
|
||||
temperature: params.temperature,
|
||||
topP: params.topP,
|
||||
toolChoice: isLastStep ? "none" : undefined,
|
||||
messages: [
|
||||
...system.map(
|
||||
(x): ModelMessage => ({
|
||||
role: "system",
|
||||
content: x,
|
||||
}),
|
||||
),
|
||||
abortSignal: abort,
|
||||
providerOptions: ProviderTransform.providerOptions(model.npm, model.providerID, params.options),
|
||||
stopWhen: stepCountIs(1),
|
||||
temperature: params.temperature,
|
||||
topP: params.topP,
|
||||
messages: [
|
||||
...system.map(
|
||||
(x): ModelMessage => ({
|
||||
role: "system",
|
||||
content: x,
|
||||
}),
|
||||
),
|
||||
...MessageV2.toModelMessage(
|
||||
msgs.filter((m) => {
|
||||
if (m.info.role !== "assistant" || m.info.error === undefined) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
MessageV2.AbortedError.isInstance(m.info.error) &&
|
||||
m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
|
||||
) {
|
||||
return true
|
||||
}
|
||||
...MessageV2.toModelMessage(
|
||||
msgs.filter((m) => {
|
||||
if (m.info.role !== "assistant" || m.info.error === undefined) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
MessageV2.AbortedError.isInstance(m.info.error) &&
|
||||
m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}),
|
||||
),
|
||||
],
|
||||
tools: model.info.tool_call === false ? undefined : tools,
|
||||
model: wrapLanguageModel({
|
||||
model: model.language,
|
||||
middleware: [
|
||||
{
|
||||
async transformParams(args) {
|
||||
if (args.type === "stream") {
|
||||
// @ts-expect-error
|
||||
args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID)
|
||||
}
|
||||
// Transform tool schemas for provider compatibility
|
||||
if (args.params.tools && Array.isArray(args.params.tools)) {
|
||||
args.params.tools = args.params.tools.map((tool: any) => {
|
||||
// Tools at middleware level have inputSchema, not parameters
|
||||
if (tool.inputSchema && typeof tool.inputSchema === "object") {
|
||||
// Transform the inputSchema for provider compatibility
|
||||
return {
|
||||
...tool,
|
||||
inputSchema: ProviderTransform.schema(model.providerID, model.modelID, tool.inputSchema),
|
||||
}
|
||||
}
|
||||
// If no inputSchema, return tool unchanged
|
||||
return tool
|
||||
})
|
||||
}
|
||||
return args.params
|
||||
return false
|
||||
}),
|
||||
),
|
||||
...(isLastStep
|
||||
? [
|
||||
{
|
||||
role: "assistant" as const,
|
||||
content: MAX_STEPS,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
tools: model.capabilities.toolcall === false ? undefined : tools,
|
||||
model: wrapLanguageModel({
|
||||
model: language,
|
||||
middleware: [
|
||||
{
|
||||
async transformParams(args) {
|
||||
if (args.type === "stream") {
|
||||
// @ts-expect-error - prompt types are compatible at runtime
|
||||
args.params.prompt = ProviderTransform.message(args.params.prompt, model)
|
||||
}
|
||||
// Transform tool schemas for provider compatibility
|
||||
if (args.params.tools && Array.isArray(args.params.tools)) {
|
||||
args.params.tools = args.params.tools.map((tool: any) => {
|
||||
// Tools at middleware level have inputSchema, not parameters
|
||||
if (tool.inputSchema && typeof tool.inputSchema === "object") {
|
||||
// Transform the inputSchema for provider compatibility
|
||||
return {
|
||||
...tool,
|
||||
inputSchema: ProviderTransform.schema(model, tool.inputSchema),
|
||||
}
|
||||
}
|
||||
// If no inputSchema, return tool unchanged
|
||||
return tool
|
||||
})
|
||||
}
|
||||
return args.params
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
||||
})
|
||||
if (result === "stop") break
|
||||
continue
|
||||
}
|
||||
@@ -645,19 +655,24 @@ export namespace SessionPrompt {
|
||||
async function resolveSystemPrompt(input: {
|
||||
system?: string
|
||||
agent: Agent.Info
|
||||
providerID: string
|
||||
modelID: string
|
||||
model: Provider.Model
|
||||
isLastStep?: boolean
|
||||
}) {
|
||||
let system = SystemPrompt.header(input.providerID)
|
||||
let system = SystemPrompt.header(input.model.providerID)
|
||||
system.push(
|
||||
...(() => {
|
||||
if (input.system) return [input.system]
|
||||
if (input.agent.prompt) return [input.agent.prompt]
|
||||
return SystemPrompt.provider(input.modelID)
|
||||
return SystemPrompt.provider(input.model)
|
||||
})(),
|
||||
)
|
||||
system.push(...(await SystemPrompt.environment()))
|
||||
system.push(...(await SystemPrompt.custom()))
|
||||
|
||||
if (input.isLastStep) {
|
||||
system.push(MAX_STEPS)
|
||||
}
|
||||
|
||||
// max 2 system prompt messages for caching purposes
|
||||
const [first, ...rest] = system
|
||||
system = [first, rest.join("\n")]
|
||||
@@ -666,10 +681,7 @@ export namespace SessionPrompt {
|
||||
|
||||
async function resolveTools(input: {
|
||||
agent: Agent.Info
|
||||
model: {
|
||||
providerID: string
|
||||
modelID: string
|
||||
}
|
||||
model: Provider.Model
|
||||
sessionID: string
|
||||
tools?: Record<string, boolean>
|
||||
processor: SessionProcessor.Info
|
||||
@@ -677,16 +689,12 @@ export namespace SessionPrompt {
|
||||
const tools: Record<string, AITool> = {}
|
||||
const enabledTools = pipe(
|
||||
input.agent.tools,
|
||||
mergeDeep(await ToolRegistry.enabled(input.model.providerID, input.model.modelID, input.agent)),
|
||||
mergeDeep(await ToolRegistry.enabled(input.agent)),
|
||||
mergeDeep(input.tools ?? {}),
|
||||
)
|
||||
for (const item of await ToolRegistry.tools(input.model.providerID, input.model.modelID)) {
|
||||
for (const item of await ToolRegistry.tools(input.model.providerID)) {
|
||||
if (Wildcard.all(item.id, enabledTools) === false) continue
|
||||
const schema = ProviderTransform.schema(
|
||||
input.model.providerID,
|
||||
input.model.modelID,
|
||||
z.toJSONSchema(item.parameters),
|
||||
)
|
||||
const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters))
|
||||
tools[item.id] = tool({
|
||||
id: item.id as any,
|
||||
description: item.description,
|
||||
@@ -708,7 +716,7 @@ export namespace SessionPrompt {
|
||||
abort: options.abortSignal!,
|
||||
messageID: input.processor.message.id,
|
||||
callID: options.toolCallId,
|
||||
extra: input.model,
|
||||
extra: { model: input.model },
|
||||
agent: input.agent.name,
|
||||
metadata: async (val) => {
|
||||
const match = input.processor.partFromToolCall(options.toolCallId)
|
||||
@@ -925,12 +933,13 @@ export namespace SessionPrompt {
|
||||
|
||||
await ReadTool.init()
|
||||
.then(async (t) => {
|
||||
const model = await Provider.getModel(info.model.providerID, info.model.modelID)
|
||||
const result = await t.execute(args, {
|
||||
sessionID: input.sessionID,
|
||||
abort: new AbortController().signal,
|
||||
agent: input.agent!,
|
||||
messageID: info.id,
|
||||
extra: { bypassCwdCheck: true, ...info.model },
|
||||
extra: { bypassCwdCheck: true, model },
|
||||
metadata: async () => {},
|
||||
})
|
||||
pieces.push({
|
||||
@@ -1435,27 +1444,21 @@ export namespace SessionPrompt {
|
||||
input.history.filter((m) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic))
|
||||
.length === 1
|
||||
if (!isFirst) return
|
||||
const cfg = await Config.get()
|
||||
const small =
|
||||
(await Provider.getSmallModel(input.providerID)) ?? (await Provider.getModel(input.providerID, input.modelID))
|
||||
const language = await Provider.getLanguage(small)
|
||||
const provider = await Provider.getProvider(small.providerID)
|
||||
const options = pipe(
|
||||
{},
|
||||
mergeDeep(
|
||||
ProviderTransform.options(
|
||||
small.providerID,
|
||||
small.modelID,
|
||||
small.npm ?? "",
|
||||
input.session.id,
|
||||
provider?.options,
|
||||
),
|
||||
),
|
||||
mergeDeep(ProviderTransform.smallOptions({ providerID: small.providerID, modelID: small.modelID })),
|
||||
mergeDeep(small.info.options),
|
||||
mergeDeep(ProviderTransform.options(small, input.session.id, provider?.options)),
|
||||
mergeDeep(ProviderTransform.smallOptions(small)),
|
||||
mergeDeep(small.options),
|
||||
)
|
||||
await generateText({
|
||||
// use higher # for reasoning models since reasoning tokens eat up a lot of the budget
|
||||
maxOutputTokens: small.info.reasoning ? 3000 : 20,
|
||||
providerOptions: ProviderTransform.providerOptions(small.npm, small.providerID, options),
|
||||
maxOutputTokens: small.capabilities.reasoning ? 3000 : 20,
|
||||
providerOptions: ProviderTransform.providerOptions(small.api.npm, small.providerID, options),
|
||||
messages: [
|
||||
...SystemPrompt.title(small.providerID).map(
|
||||
(x): ModelMessage => ({
|
||||
@@ -1486,8 +1489,9 @@ export namespace SessionPrompt {
|
||||
},
|
||||
]),
|
||||
],
|
||||
headers: small.info.headers,
|
||||
model: small.language,
|
||||
headers: small.headers,
|
||||
model: language,
|
||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.text)
|
||||
@@ -1504,7 +1508,7 @@ export namespace SessionPrompt {
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error("failed to generate title", { error, model: small.info.id })
|
||||
log.error("failed to generate title", { error, model: small.id })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
16
packages/opencode/src/session/prompt/max-steps.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
CRITICAL - MAXIMUM STEPS REACHED
|
||||
|
||||
The maximum number of steps allowed for this task has been reached. Tools are disabled until next user input. Respond with text only.
|
||||
|
||||
STRICT REQUIREMENTS:
|
||||
1. Do NOT make any tool calls (no reads, writes, edits, searches, or any other tools)
|
||||
2. MUST provide a text response summarizing work done so far
|
||||
3. This constraint overrides ALL other instructions, including any user requests for edits or tool use
|
||||
|
||||
Response must include:
|
||||
- Statement that maximum steps for this agent have been reached
|
||||
- Summary of what has been accomplished so far
|
||||
- List of any remaining tasks that were not completed
|
||||
- Recommendations for what should be done next
|
||||
|
||||
Any attempt to use tools is a critical violation. Respond with text ONLY.
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { Config } from "@/config/config"
|
||||
import { fn } from "@/util/fn"
|
||||
import z from "zod"
|
||||
import { Session } from "."
|
||||
@@ -60,6 +61,7 @@ export namespace SessionSummary {
|
||||
}
|
||||
|
||||
async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) {
|
||||
const cfg = await Config.get()
|
||||
const messages = input.messages.filter(
|
||||
(m) => m.info.id === input.messageID || (m.info.role === "assistant" && m.info.parentID === input.messageID),
|
||||
)
|
||||
@@ -76,19 +78,20 @@ export namespace SessionSummary {
|
||||
const small =
|
||||
(await Provider.getSmallModel(assistantMsg.providerID)) ??
|
||||
(await Provider.getModel(assistantMsg.providerID, assistantMsg.modelID))
|
||||
const language = await Provider.getLanguage(small)
|
||||
|
||||
const options = pipe(
|
||||
{},
|
||||
mergeDeep(ProviderTransform.options(small.providerID, small.modelID, small.npm ?? "", assistantMsg.sessionID)),
|
||||
mergeDeep(ProviderTransform.smallOptions({ providerID: small.providerID, modelID: small.modelID })),
|
||||
mergeDeep(small.info.options),
|
||||
mergeDeep(ProviderTransform.options(small, assistantMsg.sessionID)),
|
||||
mergeDeep(ProviderTransform.smallOptions(small)),
|
||||
mergeDeep(small.options),
|
||||
)
|
||||
|
||||
const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart
|
||||
if (textPart && !userMsg.summary?.title) {
|
||||
const result = await generateText({
|
||||
maxOutputTokens: small.info.reasoning ? 1500 : 20,
|
||||
providerOptions: ProviderTransform.providerOptions(small.npm, small.providerID, options),
|
||||
maxOutputTokens: small.capabilities.reasoning ? 1500 : 20,
|
||||
providerOptions: ProviderTransform.providerOptions(small.api.npm, small.providerID, options),
|
||||
messages: [
|
||||
...SystemPrompt.title(small.providerID).map(
|
||||
(x): ModelMessage => ({
|
||||
@@ -106,8 +109,9 @@ export namespace SessionSummary {
|
||||
`,
|
||||
},
|
||||
],
|
||||
headers: small.info.headers,
|
||||
model: small.language,
|
||||
headers: small.headers,
|
||||
model: language,
|
||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
||||
})
|
||||
log.info("title", { title: result.text })
|
||||
userMsg.summary.title = result.text
|
||||
@@ -132,9 +136,9 @@ export namespace SessionSummary {
|
||||
}
|
||||
}
|
||||
const result = await generateText({
|
||||
model: small.language,
|
||||
model: language,
|
||||
maxOutputTokens: 100,
|
||||
providerOptions: ProviderTransform.providerOptions(small.npm, small.providerID, options),
|
||||
providerOptions: ProviderTransform.providerOptions(small.api.npm, small.providerID, options),
|
||||
messages: [
|
||||
...SystemPrompt.summarize(small.providerID).map(
|
||||
(x): ModelMessage => ({
|
||||
@@ -148,7 +152,8 @@ export namespace SessionSummary {
|
||||
content: `Summarize the above conversation according to your system prompts.`,
|
||||
},
|
||||
],
|
||||
headers: small.info.headers,
|
||||
headers: small.headers,
|
||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
||||
}).catch(() => {})
|
||||
if (result) summary = result.text
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
||||
import PROMPT_SUMMARIZE from "./prompt/summarize.txt"
|
||||
import PROMPT_TITLE from "./prompt/title.txt"
|
||||
import PROMPT_CODEX from "./prompt/codex.txt"
|
||||
import type { Provider } from "@/provider/provider"
|
||||
|
||||
export namespace SystemPrompt {
|
||||
export function header(providerID: string) {
|
||||
@@ -24,12 +25,13 @@ export namespace SystemPrompt {
|
||||
return []
|
||||
}
|
||||
|
||||
export function provider(modelID: string) {
|
||||
if (modelID.includes("gpt-5")) return [PROMPT_CODEX]
|
||||
if (modelID.includes("gpt-") || modelID.includes("o1") || modelID.includes("o3")) return [PROMPT_BEAST]
|
||||
if (modelID.includes("gemini-")) return [PROMPT_GEMINI]
|
||||
if (modelID.includes("claude")) return [PROMPT_ANTHROPIC]
|
||||
if (modelID.includes("polaris-alpha")) return [PROMPT_POLARIS]
|
||||
export function provider(model: Provider.Model) {
|
||||
if (model.api.id.includes("gpt-5")) return [PROMPT_CODEX]
|
||||
if (model.api.id.includes("gpt-") || model.api.id.includes("o1") || model.api.id.includes("o3"))
|
||||
return [PROMPT_BEAST]
|
||||
if (model.api.id.includes("gemini-")) return [PROMPT_GEMINI]
|
||||
if (model.api.id.includes("claude")) return [PROMPT_ANTHROPIC]
|
||||
if (model.api.id.includes("polaris-alpha")) return [PROMPT_POLARIS]
|
||||
return [PROMPT_ANTHROPIC_WITHOUT_TODO]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Bus } from "@/bus"
|
||||
import { Config } from "@/config/config"
|
||||
import { ulid } from "ulid"
|
||||
import type { ModelsDev } from "@/provider/models"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { Session } from "@/session"
|
||||
import { MessageV2 } from "@/session/message-v2"
|
||||
@@ -36,7 +35,7 @@ export namespace ShareNext {
|
||||
type: "model",
|
||||
data: [
|
||||
await Provider.getModel(evt.properties.info.model.providerID, evt.properties.info.model.modelID).then(
|
||||
(m) => m.info,
|
||||
(m) => m,
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -105,7 +104,7 @@ export namespace ShareNext {
|
||||
}
|
||||
| {
|
||||
type: "model"
|
||||
data: ModelsDev.Model[]
|
||||
data: SDK.Model[]
|
||||
}
|
||||
|
||||
const queue = new Map<string, { timeout: NodeJS.Timeout; data: Map<string, Data> }>()
|
||||
@@ -171,7 +170,7 @@ export namespace ShareNext {
|
||||
messages
|
||||
.filter((m) => m.info.role === "user")
|
||||
.map((m) => (m.info as SDK.UserMessage).model)
|
||||
.map((m) => Provider.getModel(m.providerID, m.modelID).then((m) => m.info)),
|
||||
.map((m) => Provider.getModel(m.providerID, m.modelID).then((m) => m)),
|
||||
)
|
||||
await sync(sessionID, [
|
||||
{
|
||||
|
||||
@@ -234,13 +234,15 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
})
|
||||
|
||||
const append = (chunk: Buffer) => {
|
||||
output += chunk.toString()
|
||||
ctx.metadata({
|
||||
metadata: {
|
||||
output,
|
||||
description: params.description,
|
||||
},
|
||||
})
|
||||
if (output.length <= MAX_OUTPUT_LENGTH) {
|
||||
output += chunk.toString()
|
||||
ctx.metadata({
|
||||
metadata: {
|
||||
output,
|
||||
description: params.description,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
proc.stdout?.on("data", append)
|
||||
@@ -295,7 +297,7 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
const timeoutTimer = setTimeout(() => {
|
||||
timedOut = true
|
||||
void killTree()
|
||||
}, timeout)
|
||||
}, timeout + 100)
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
@@ -316,17 +318,24 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
})
|
||||
})
|
||||
|
||||
let resultMetadata: String[] = ["<bash_metadata>"]
|
||||
|
||||
if (output.length > MAX_OUTPUT_LENGTH) {
|
||||
output = output.slice(0, MAX_OUTPUT_LENGTH)
|
||||
output += "\n\n(Output was truncated due to length limit)"
|
||||
resultMetadata.push(`bash tool truncated output as it exceeded ${MAX_OUTPUT_LENGTH} char limit`)
|
||||
}
|
||||
|
||||
if (timedOut) {
|
||||
output += `\n\n(Command timed out after ${timeout} ms)`
|
||||
resultMetadata.push(`bash tool terminated commmand after exceeding timeout ${timeout} ms`)
|
||||
}
|
||||
|
||||
if (aborted) {
|
||||
output += "\n\n(Command was aborted)"
|
||||
resultMetadata.push("User aborted the command")
|
||||
}
|
||||
|
||||
if (resultMetadata.length > 1) {
|
||||
resultMetadata.push("</bash_metadata>")
|
||||
output += "\n\n" + resultMetadata.join("\n")
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -35,6 +35,8 @@ Usage notes:
|
||||
|
||||
# Committing changes with git
|
||||
|
||||
IMPORTANT: ONLY COMMIT IF THE USER ASKS YOU TO.
|
||||
|
||||
If and only if the user asks you to create a new git commit, follow these steps carefully:
|
||||
|
||||
1. You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. ALWAYS run the following bash commands in parallel, each using the Bash tool:
|
||||
@@ -42,9 +44,8 @@ If and only if the user asks you to create a new git commit, follow these steps
|
||||
- Run a git diff command to see both staged and unstaged changes that will be committed.
|
||||
- Run a git log command to see recent commit messages, so that you can follow this repository's commit message style.
|
||||
|
||||
2. Analyze all staged changes (both previously staged and newly added) and draft a commit message. Wrap your analysis process in <commit_analysis> tags:
|
||||
2. Analyze all staged changes (both previously staged and newly added) and draft a commit message. When analyzing:
|
||||
|
||||
<commit_analysis>
|
||||
- List the files that have been changed or added
|
||||
- Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.)
|
||||
- Brainstorm the purpose or motivation behind these changes
|
||||
@@ -55,7 +56,6 @@ If and only if the user asks you to create a new git commit, follow these steps
|
||||
- Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.)
|
||||
- Ensure the message is not generic (avoid words like "Update" or "Fix" without context)
|
||||
- Review the draft message to ensure it accurately reflects the changes and their purpose
|
||||
</commit_analysis>
|
||||
|
||||
3. You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. ALWAYS run the following commands in parallel:
|
||||
- Add relevant untracked files to the staging area.
|
||||
|
||||
@@ -37,7 +37,7 @@ export const BatchTool = Tool.define("batch", async () => {
|
||||
const discardedCalls = params.tool_calls.slice(10)
|
||||
|
||||
const { ToolRegistry } = await import("./registry")
|
||||
const availableTools = await ToolRegistry.tools("", "")
|
||||
const availableTools = await ToolRegistry.tools("")
|
||||
const toolMap = new Map(availableTools.map((t) => [t.id, t]))
|
||||
|
||||
const executeCall = async (call: (typeof toolCalls)[0]) => {
|
||||
|
||||
@@ -95,14 +95,8 @@ export const ReadTool = Tool.define("read", {
|
||||
}
|
||||
|
||||
const isImage = isImageFile(filepath)
|
||||
const supportsImages = await (async () => {
|
||||
if (!ctx.extra?.["providerID"] || !ctx.extra?.["modelID"]) return false
|
||||
const providerID = ctx.extra["providerID"] as string
|
||||
const modelID = ctx.extra["modelID"] as string
|
||||
const model = await Provider.getModel(providerID, modelID).catch(() => undefined)
|
||||
if (!model) return false
|
||||
return model.info.modalities?.input?.includes("image") ?? false
|
||||
})()
|
||||
const model = ctx.extra?.model as Provider.Model | undefined
|
||||
const supportsImages = model?.capabilities.input.image ?? false
|
||||
if (isImage) {
|
||||
if (!supportsImages) {
|
||||
throw new Error(`Failed to read image: ${filepath}, model may not be able to read images`)
|
||||
|
||||
@@ -108,7 +108,7 @@ export namespace ToolRegistry {
|
||||
return all().then((x) => x.map((t) => t.id))
|
||||
}
|
||||
|
||||
export async function tools(providerID: string, _modelID: string) {
|
||||
export async function tools(providerID: string) {
|
||||
const tools = await all()
|
||||
const result = await Promise.all(
|
||||
tools
|
||||
@@ -124,11 +124,7 @@ export namespace ToolRegistry {
|
||||
return result
|
||||
}
|
||||
|
||||
export async function enabled(
|
||||
_providerID: string,
|
||||
_modelID: string,
|
||||
agent: Agent.Info,
|
||||
): Promise<Record<string, boolean>> {
|
||||
export async function enabled(agent: Agent.Info): Promise<Record<string, boolean>> {
|
||||
const result: Record<string, boolean> = {}
|
||||
|
||||
if (agent.permission.edit === "deny") {
|
||||
|
||||
@@ -17,3 +17,16 @@ export class AsyncQueue<T> implements AsyncIterable<T> {
|
||||
while (true) yield await this.next()
|
||||
}
|
||||
}
|
||||
|
||||
export async function work<T>(concurrency: number, items: T[], fn: (item: T) => Promise<void>) {
|
||||
const pending = [...items]
|
||||
await Promise.all(
|
||||
Array.from({ length: concurrency }, async () => {
|
||||
while (true) {
|
||||
const item = pending.pop()
|
||||
if (item === undefined) return
|
||||
await fn(item)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ describe("Project.fromDirectory", () => {
|
||||
|
||||
expect(project).toBeDefined()
|
||||
expect(project.id).toBe("global")
|
||||
expect(project.worktree).toBe("/")
|
||||
expect(project.vcs).toBe("git")
|
||||
expect(project.worktree).toBe(tmp.path)
|
||||
expect(project.vcsDir).toBe(path.join(tmp.path, ".git"))
|
||||
|
||||
const opencodeFile = path.join(tmp.path, ".git", "opencode")
|
||||
const fileExists = await Bun.file(opencodeFile).exists()
|
||||
|
||||
@@ -132,7 +132,7 @@ test("model whitelist filters models for provider", async () => {
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["anthropic"]).toBeDefined()
|
||||
const models = Object.keys(providers["anthropic"].info.models)
|
||||
const models = Object.keys(providers["anthropic"].models)
|
||||
expect(models).toContain("claude-sonnet-4-20250514")
|
||||
expect(models.length).toBe(1)
|
||||
},
|
||||
@@ -163,7 +163,7 @@ test("model blacklist excludes specific models", async () => {
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["anthropic"]).toBeDefined()
|
||||
const models = Object.keys(providers["anthropic"].info.models)
|
||||
const models = Object.keys(providers["anthropic"].models)
|
||||
expect(models).not.toContain("claude-sonnet-4-20250514")
|
||||
},
|
||||
})
|
||||
@@ -198,8 +198,8 @@ test("custom model alias via config", async () => {
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["anthropic"]).toBeDefined()
|
||||
expect(providers["anthropic"].info.models["my-alias"]).toBeDefined()
|
||||
expect(providers["anthropic"].info.models["my-alias"].name).toBe("My Custom Alias")
|
||||
expect(providers["anthropic"].models["my-alias"]).toBeDefined()
|
||||
expect(providers["anthropic"].models["my-alias"].name).toBe("My Custom Alias")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -241,8 +241,8 @@ test("custom provider with npm package", async () => {
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["custom-provider"]).toBeDefined()
|
||||
expect(providers["custom-provider"].info.name).toBe("Custom Provider")
|
||||
expect(providers["custom-provider"].info.models["custom-model"]).toBeDefined()
|
||||
expect(providers["custom-provider"].name).toBe("Custom Provider")
|
||||
expect(providers["custom-provider"].models["custom-model"]).toBeDefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -299,8 +299,9 @@ test("getModel returns model for valid provider/model", async () => {
|
||||
const model = await Provider.getModel("anthropic", "claude-sonnet-4-20250514")
|
||||
expect(model).toBeDefined()
|
||||
expect(model.providerID).toBe("anthropic")
|
||||
expect(model.modelID).toBe("claude-sonnet-4-20250514")
|
||||
expect(model.language).toBeDefined()
|
||||
expect(model.id).toBe("claude-sonnet-4-20250514")
|
||||
const language = await Provider.getLanguage(model)
|
||||
expect(language).toBeDefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -478,11 +479,11 @@ test("model cost defaults to zero when not specified", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["test-provider"].info.models["test-model"]
|
||||
const model = providers["test-provider"].models["test-model"]
|
||||
expect(model.cost.input).toBe(0)
|
||||
expect(model.cost.output).toBe(0)
|
||||
expect(model.cost.cache_read).toBe(0)
|
||||
expect(model.cost.cache_write).toBe(0)
|
||||
expect(model.cost.cache.read).toBe(0)
|
||||
expect(model.cost.cache.write).toBe(0)
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -516,7 +517,7 @@ test("model options are merged from existing model", async () => {
|
||||
},
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["anthropic"].info.models["claude-sonnet-4-20250514"]
|
||||
const model = providers["anthropic"].models["claude-sonnet-4-20250514"]
|
||||
expect(model.options.customOption).toBe("custom-value")
|
||||
},
|
||||
})
|
||||
@@ -623,17 +624,17 @@ test("getModel uses realIdByKey for aliased models", async () => {
|
||||
},
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["anthropic"].info.models["my-sonnet"]).toBeDefined()
|
||||
expect(providers["anthropic"].models["my-sonnet"]).toBeDefined()
|
||||
|
||||
const model = await Provider.getModel("anthropic", "my-sonnet")
|
||||
expect(model).toBeDefined()
|
||||
expect(model.modelID).toBe("my-sonnet")
|
||||
expect(model.info.name).toBe("My Sonnet Alias")
|
||||
expect(model.id).toBe("my-sonnet")
|
||||
expect(model.name).toBe("My Sonnet Alias")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("provider api field sets default baseURL", async () => {
|
||||
test("provider api field sets model api.url", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
@@ -666,7 +667,8 @@ test("provider api field sets default baseURL", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["custom-api"].options.baseURL).toBe("https://api.example.com/v1")
|
||||
// api field is stored on model.api.url, used by getSDK to set baseURL
|
||||
expect(providers["custom-api"].models["model-1"].api.url).toBe("https://api.example.com/v1")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -737,10 +739,10 @@ test("model inherits properties from existing database model", async () => {
|
||||
},
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["anthropic"].info.models["claude-sonnet-4-20250514"]
|
||||
const model = providers["anthropic"].models["claude-sonnet-4-20250514"]
|
||||
expect(model.name).toBe("Custom Name for Sonnet")
|
||||
expect(model.tool_call).toBe(true)
|
||||
expect(model.attachment).toBe(true)
|
||||
expect(model.capabilities.toolcall).toBe(true)
|
||||
expect(model.capabilities.attachment).toBe(true)
|
||||
expect(model.limit.context).toBeGreaterThan(0)
|
||||
},
|
||||
})
|
||||
@@ -820,7 +822,7 @@ test("whitelist and blacklist can be combined", async () => {
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["anthropic"]).toBeDefined()
|
||||
const models = Object.keys(providers["anthropic"].info.models)
|
||||
const models = Object.keys(providers["anthropic"].models)
|
||||
expect(models).toContain("claude-sonnet-4-20250514")
|
||||
expect(models).not.toContain("claude-opus-4-20250514")
|
||||
expect(models.length).toBe(1)
|
||||
@@ -858,11 +860,9 @@ test("model modalities default correctly", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["test-provider"].info.models["test-model"]
|
||||
expect(model.modalities).toEqual({
|
||||
input: ["text"],
|
||||
output: ["text"],
|
||||
})
|
||||
const model = providers["test-provider"].models["test-model"]
|
||||
expect(model.capabilities.input.text).toBe(true)
|
||||
expect(model.capabilities.output.text).toBe(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -903,11 +903,11 @@ test("model with custom cost values", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["test-provider"].info.models["test-model"]
|
||||
const model = providers["test-provider"].models["test-model"]
|
||||
expect(model.cost.input).toBe(5)
|
||||
expect(model.cost.output).toBe(15)
|
||||
expect(model.cost.cache_read).toBe(2.5)
|
||||
expect(model.cost.cache_write).toBe(7.5)
|
||||
expect(model.cost.cache.read).toBe(2.5)
|
||||
expect(model.cost.cache.write).toBe(7.5)
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -931,7 +931,7 @@ test("getSmallModel returns appropriate small model", async () => {
|
||||
fn: async () => {
|
||||
const model = await Provider.getSmallModel("anthropic")
|
||||
expect(model).toBeDefined()
|
||||
expect(model?.modelID).toContain("haiku")
|
||||
expect(model?.id).toContain("haiku")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -957,7 +957,7 @@ test("getSmallModel respects config small_model override", async () => {
|
||||
const model = await Provider.getSmallModel("anthropic")
|
||||
expect(model).toBeDefined()
|
||||
expect(model?.providerID).toBe("anthropic")
|
||||
expect(model?.modelID).toBe("claude-sonnet-4-20250514")
|
||||
expect(model?.id).toBe("claude-sonnet-4-20250514")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1046,7 +1046,7 @@ test("provider with custom npm package", async () => {
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["local-llm"]).toBeDefined()
|
||||
expect(providers["local-llm"].info.npm).toBe("@ai-sdk/openai-compatible")
|
||||
expect(providers["local-llm"].models["llama-3"].api.npm).toBe("@ai-sdk/openai-compatible")
|
||||
expect(providers["local-llm"].options.baseURL).toBe("http://localhost:11434/v1")
|
||||
},
|
||||
})
|
||||
@@ -1082,7 +1082,7 @@ test("model alias name defaults to alias key when id differs", async () => {
|
||||
},
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["anthropic"].info.models["sonnet"].name).toBe("sonnet")
|
||||
expect(providers["anthropic"].models["sonnet"].name).toBe("sonnet")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1123,8 +1123,8 @@ test("provider with multiple env var options only includes apiKey when single en
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["multi-env"]).toBeDefined()
|
||||
// When multiple env options exist, apiKey should NOT be auto-set
|
||||
expect(providers["multi-env"].options.apiKey).toBeUndefined()
|
||||
// When multiple env options exist, key should NOT be auto-set
|
||||
expect(providers["multi-env"].key).toBeUndefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1165,8 +1165,8 @@ test("provider with single env var includes apiKey automatically", async () => {
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["single-env"]).toBeDefined()
|
||||
// Single env option should auto-set apiKey
|
||||
expect(providers["single-env"].options.apiKey).toBe("my-api-key")
|
||||
// Single env option should auto-set key
|
||||
expect(providers["single-env"].key).toBe("my-api-key")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1201,7 +1201,7 @@ test("model cost overrides existing cost values", async () => {
|
||||
},
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["anthropic"].info.models["claude-sonnet-4-20250514"]
|
||||
const model = providers["anthropic"].models["claude-sonnet-4-20250514"]
|
||||
expect(model.cost.input).toBe(999)
|
||||
expect(model.cost.output).toBe(888)
|
||||
},
|
||||
@@ -1249,11 +1249,11 @@ test("completely new provider not in database can be configured", async () => {
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["brand-new-provider"]).toBeDefined()
|
||||
expect(providers["brand-new-provider"].info.name).toBe("Brand New")
|
||||
const model = providers["brand-new-provider"].info.models["new-model"]
|
||||
expect(model.reasoning).toBe(true)
|
||||
expect(model.attachment).toBe(true)
|
||||
expect(model.modalities?.input).toContain("image")
|
||||
expect(providers["brand-new-provider"].name).toBe("Brand New")
|
||||
const model = providers["brand-new-provider"].models["new-model"]
|
||||
expect(model.capabilities.reasoning).toBe(true)
|
||||
expect(model.capabilities.attachment).toBe(true)
|
||||
expect(model.capabilities.input.image).toBe(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1322,7 +1322,7 @@ test("model with tool_call false", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["no-tools"].info.models["basic-model"].tool_call).toBe(false)
|
||||
expect(providers["no-tools"].models["basic-model"].capabilities.toolcall).toBe(false)
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1357,7 +1357,7 @@ test("model defaults tool_call to true when not specified", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["default-tools"].info.models["model"].tool_call).toBe(true)
|
||||
expect(providers["default-tools"].models["model"].capabilities.toolcall).toBe(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1396,7 +1396,7 @@ test("model headers are preserved", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["headers-provider"].info.models["model"]
|
||||
const model = providers["headers-provider"].models["model"]
|
||||
expect(model.headers).toEqual({
|
||||
"X-Custom-Header": "custom-value",
|
||||
Authorization: "Bearer special-token",
|
||||
@@ -1465,8 +1465,8 @@ test("getModel returns consistent results", async () => {
|
||||
const model1 = await Provider.getModel("anthropic", "claude-sonnet-4-20250514")
|
||||
const model2 = await Provider.getModel("anthropic", "claude-sonnet-4-20250514")
|
||||
expect(model1.providerID).toEqual(model2.providerID)
|
||||
expect(model1.modelID).toEqual(model2.modelID)
|
||||
expect(model1.info).toEqual(model2.info)
|
||||
expect(model1.id).toEqual(model2.id)
|
||||
expect(model1).toEqual(model2)
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1501,7 +1501,7 @@ test("provider name defaults to id when not in database", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
expect(providers["my-custom-id"].info.name).toBe("my-custom-id")
|
||||
expect(providers["my-custom-id"].name).toBe("my-custom-id")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1601,7 +1601,7 @@ test("getProvider returns provider info", async () => {
|
||||
fn: async () => {
|
||||
const provider = await Provider.getProvider("anthropic")
|
||||
expect(provider).toBeDefined()
|
||||
expect(provider?.info.id).toBe("anthropic")
|
||||
expect(provider?.id).toBe("anthropic")
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1684,7 +1684,7 @@ test("model limit defaults to zero when not specified", async () => {
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["no-limit"].info.models["model"]
|
||||
const model = providers["no-limit"].models["model"]
|
||||
expect(model.limit.context).toBe(0)
|
||||
expect(model.limit.output).toBe(0)
|
||||
},
|
||||
@@ -1727,3 +1727,39 @@ test("provider options are deeply merged", async () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("custom model inherits npm package from models.dev provider config", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
provider: {
|
||||
openai: {
|
||||
models: {
|
||||
"my-custom-model": {
|
||||
name: "My Custom Model",
|
||||
tool_call: true,
|
||||
limit: { context: 8000, output: 2000 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
Env.set("OPENAI_API_KEY", "test-api-key")
|
||||
},
|
||||
fn: async () => {
|
||||
const providers = await Provider.list()
|
||||
const model = providers["openai"].models["my-custom-model"]
|
||||
expect(model).toBeDefined()
|
||||
expect(model.api.npm).toBe("@ai-sdk/openai")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -96,3 +96,210 @@ describe("ProviderTransform.maxOutputTokens", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
||||
test("DeepSeek with tool calls includes reasoning_content in providerOptions", () => {
|
||||
const msgs = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "reasoning", text: "Let me think about this..." },
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "test",
|
||||
toolName: "bash",
|
||||
input: { command: "echo hello" },
|
||||
},
|
||||
],
|
||||
},
|
||||
] as any[]
|
||||
|
||||
const result = ProviderTransform.message(msgs, {
|
||||
id: "deepseek/deepseek-chat",
|
||||
providerID: "deepseek",
|
||||
api: {
|
||||
id: "deepseek-chat",
|
||||
url: "https://api.deepseek.com",
|
||||
npm: "@ai-sdk/openai-compatible",
|
||||
},
|
||||
name: "DeepSeek Chat",
|
||||
capabilities: {
|
||||
temperature: true,
|
||||
reasoning: true,
|
||||
attachment: false,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
},
|
||||
cost: {
|
||||
input: 0.001,
|
||||
output: 0.002,
|
||||
cache: { read: 0.0001, write: 0.0002 },
|
||||
},
|
||||
limit: {
|
||||
context: 128000,
|
||||
output: 8192,
|
||||
},
|
||||
status: "active",
|
||||
options: {},
|
||||
headers: {},
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].content).toEqual([
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "test",
|
||||
toolName: "bash",
|
||||
input: { command: "echo hello" },
|
||||
},
|
||||
])
|
||||
expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBe("Let me think about this...")
|
||||
})
|
||||
|
||||
test("DeepSeek without tool calls strips reasoning from content", () => {
|
||||
const msgs = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "reasoning", text: "Let me think about this..." },
|
||||
{ type: "text", text: "Final answer" },
|
||||
],
|
||||
},
|
||||
] as any[]
|
||||
|
||||
const result = ProviderTransform.message(msgs, {
|
||||
id: "deepseek/deepseek-chat",
|
||||
providerID: "deepseek",
|
||||
api: {
|
||||
id: "deepseek-chat",
|
||||
url: "https://api.deepseek.com",
|
||||
npm: "@ai-sdk/openai-compatible",
|
||||
},
|
||||
name: "DeepSeek Chat",
|
||||
capabilities: {
|
||||
temperature: true,
|
||||
reasoning: true,
|
||||
attachment: false,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
},
|
||||
cost: {
|
||||
input: 0.001,
|
||||
output: 0.002,
|
||||
cache: { read: 0.0001, write: 0.0002 },
|
||||
},
|
||||
limit: {
|
||||
context: 128000,
|
||||
output: 8192,
|
||||
},
|
||||
status: "active",
|
||||
options: {},
|
||||
headers: {},
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].content).toEqual([{ type: "text", text: "Final answer" }])
|
||||
expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBeUndefined()
|
||||
})
|
||||
|
||||
test("DeepSeek model ID containing 'deepseek' matches (case insensitive)", () => {
|
||||
const msgs = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "reasoning", text: "Thinking..." },
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "test",
|
||||
toolName: "get_weather",
|
||||
input: { location: "Hangzhou" },
|
||||
},
|
||||
],
|
||||
},
|
||||
] as any[]
|
||||
|
||||
const result = ProviderTransform.message(msgs, {
|
||||
id: "someprovider/deepseek-reasoner",
|
||||
providerID: "someprovider",
|
||||
api: {
|
||||
id: "deepseek-reasoner",
|
||||
url: "https://api.someprovider.com",
|
||||
npm: "@ai-sdk/openai-compatible",
|
||||
},
|
||||
name: "SomeProvider DeepSeek Reasoner",
|
||||
capabilities: {
|
||||
temperature: true,
|
||||
reasoning: true,
|
||||
attachment: false,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
},
|
||||
cost: {
|
||||
input: 0.001,
|
||||
output: 0.002,
|
||||
cache: { read: 0.0001, write: 0.0002 },
|
||||
},
|
||||
limit: {
|
||||
context: 128000,
|
||||
output: 8192,
|
||||
},
|
||||
status: "active",
|
||||
options: {},
|
||||
headers: {},
|
||||
})
|
||||
|
||||
expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBe("Thinking...")
|
||||
})
|
||||
|
||||
test("Non-DeepSeek providers leave reasoning content unchanged", () => {
|
||||
const msgs = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "reasoning", text: "Should not be processed" },
|
||||
{ type: "text", text: "Answer" },
|
||||
],
|
||||
},
|
||||
] as any[]
|
||||
|
||||
const result = ProviderTransform.message(msgs, {
|
||||
id: "openai/gpt-4",
|
||||
providerID: "openai",
|
||||
api: {
|
||||
id: "gpt-4",
|
||||
url: "https://api.openai.com",
|
||||
npm: "@ai-sdk/openai",
|
||||
},
|
||||
name: "GPT-4",
|
||||
capabilities: {
|
||||
temperature: true,
|
||||
reasoning: false,
|
||||
attachment: true,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: true, video: false, pdf: false },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
},
|
||||
cost: {
|
||||
input: 0.03,
|
||||
output: 0.06,
|
||||
cache: { read: 0.001, write: 0.002 },
|
||||
},
|
||||
limit: {
|
||||
context: 128000,
|
||||
output: 4096,
|
||||
},
|
||||
status: "active",
|
||||
options: {},
|
||||
headers: {},
|
||||
})
|
||||
|
||||
expect(result[0].content).toEqual([
|
||||
{ type: "reasoning", text: "Should not be processed" },
|
||||
{ type: "text", text: "Answer" },
|
||||
])
|
||||
expect(result[0].providerOptions?.openaiCompatible?.reasoning_content).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
@@ -24,4 +24,4 @@
|
||||
"typescript": "catalog:",
|
||||
"@typescript/native-preview": "catalog:"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
@@ -26,4 +26,4 @@
|
||||
"publishConfig": {
|
||||
"directory": "dist"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,23 @@ import type {
|
||||
ProjectListResponses,
|
||||
ProjectCurrentData,
|
||||
ProjectCurrentResponses,
|
||||
PtyListData,
|
||||
PtyListResponses,
|
||||
PtyCreateData,
|
||||
PtyCreateResponses,
|
||||
PtyCreateErrors,
|
||||
PtyRemoveData,
|
||||
PtyRemoveResponses,
|
||||
PtyRemoveErrors,
|
||||
PtyGetData,
|
||||
PtyGetResponses,
|
||||
PtyGetErrors,
|
||||
PtyUpdateData,
|
||||
PtyUpdateResponses,
|
||||
PtyUpdateErrors,
|
||||
PtyConnectData,
|
||||
PtyConnectResponses,
|
||||
PtyConnectErrors,
|
||||
ConfigGetData,
|
||||
ConfigGetResponses,
|
||||
ConfigUpdateData,
|
||||
@@ -231,6 +248,76 @@ class Project extends _HeyApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
class Pty extends _HeyApiClient {
|
||||
/**
|
||||
* List all PTY sessions
|
||||
*/
|
||||
public list<ThrowOnError extends boolean = false>(options?: Options<PtyListData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get<PtyListResponses, unknown, ThrowOnError>({
|
||||
url: "/pty",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new PTY session
|
||||
*/
|
||||
public create<ThrowOnError extends boolean = false>(options?: Options<PtyCreateData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).post<PtyCreateResponses, PtyCreateErrors, ThrowOnError>({
|
||||
url: "/pty",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a PTY session
|
||||
*/
|
||||
public remove<ThrowOnError extends boolean = false>(options: Options<PtyRemoveData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).delete<PtyRemoveResponses, PtyRemoveErrors, ThrowOnError>({
|
||||
url: "/pty/{id}",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PTY session info
|
||||
*/
|
||||
public get<ThrowOnError extends boolean = false>(options: Options<PtyGetData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<PtyGetResponses, PtyGetErrors, ThrowOnError>({
|
||||
url: "/pty/{id}",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update PTY session
|
||||
*/
|
||||
public update<ThrowOnError extends boolean = false>(options: Options<PtyUpdateData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).put<PtyUpdateResponses, PtyUpdateErrors, ThrowOnError>({
|
||||
url: "/pty/{id}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a PTY session
|
||||
*/
|
||||
public connect<ThrowOnError extends boolean = false>(options: Options<PtyConnectData, ThrowOnError>) {
|
||||
return (options.client ?? this._client).get<PtyConnectResponses, PtyConnectErrors, ThrowOnError>({
|
||||
url: "/pty/{id}/connect",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Config extends _HeyApiClient {
|
||||
/**
|
||||
* Get config info
|
||||
@@ -1005,6 +1092,7 @@ export class OpencodeClient extends _HeyApiClient {
|
||||
}
|
||||
global = new Global({ client: this._client })
|
||||
project = new Project({ client: this._client })
|
||||
pty = new Pty({ client: this._client })
|
||||
config = new Config({ client: this._client })
|
||||
tool = new Tool({ client: this._client })
|
||||
instance = new Instance({ client: this._client })
|
||||
|
||||
@@ -655,6 +655,45 @@ export type EventTuiToastShow = {
|
||||
}
|
||||
}
|
||||
|
||||
export type Pty = {
|
||||
id: string
|
||||
title: string
|
||||
command: string
|
||||
args: Array<string>
|
||||
cwd: string
|
||||
status: "running" | "exited"
|
||||
pid: number
|
||||
}
|
||||
|
||||
export type EventPtyCreated = {
|
||||
type: "pty.created"
|
||||
properties: {
|
||||
info: Pty
|
||||
}
|
||||
}
|
||||
|
||||
export type EventPtyUpdated = {
|
||||
type: "pty.updated"
|
||||
properties: {
|
||||
info: Pty
|
||||
}
|
||||
}
|
||||
|
||||
export type EventPtyExited = {
|
||||
type: "pty.exited"
|
||||
properties: {
|
||||
id: string
|
||||
exitCode: number
|
||||
}
|
||||
}
|
||||
|
||||
export type EventPtyDeleted = {
|
||||
type: "pty.deleted"
|
||||
properties: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventServerConnected = {
|
||||
type: "server.connected"
|
||||
properties: {
|
||||
@@ -690,6 +729,10 @@ export type Event =
|
||||
| EventTuiPromptAppend
|
||||
| EventTuiCommandExecute
|
||||
| EventTuiToastShow
|
||||
| EventPtyCreated
|
||||
| EventPtyUpdated
|
||||
| EventPtyExited
|
||||
| EventPtyDeleted
|
||||
| EventServerConnected
|
||||
|
||||
export type GlobalEvent = {
|
||||
@@ -708,6 +751,21 @@ export type Project = {
|
||||
}
|
||||
}
|
||||
|
||||
export type BadRequestError = {
|
||||
data: unknown
|
||||
errors: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
success: false
|
||||
}
|
||||
|
||||
export type NotFoundError = {
|
||||
name: "NotFoundError"
|
||||
data: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom keybind configurations
|
||||
*/
|
||||
@@ -908,6 +966,10 @@ export type AgentConfig = {
|
||||
* Hex color code for the agent (e.g., #FF5733)
|
||||
*/
|
||||
color?: string
|
||||
/**
|
||||
* Maximum number of agentic iterations before forcing text-only response
|
||||
*/
|
||||
maxSteps?: number
|
||||
permission?: {
|
||||
edit?: "ask" | "allow" | "deny"
|
||||
bash?:
|
||||
@@ -928,6 +990,7 @@ export type AgentConfig = {
|
||||
}
|
||||
| boolean
|
||||
| ("subagent" | "primary" | "all")
|
||||
| number
|
||||
| {
|
||||
edit?: "ask" | "allow" | "deny"
|
||||
bash?:
|
||||
@@ -942,6 +1005,75 @@ export type AgentConfig = {
|
||||
| undefined
|
||||
}
|
||||
|
||||
export type ProviderConfig = {
|
||||
api?: string
|
||||
name?: string
|
||||
env?: Array<string>
|
||||
id?: string
|
||||
npm?: string
|
||||
models?: {
|
||||
[key: string]: {
|
||||
id?: string
|
||||
name?: string
|
||||
release_date?: string
|
||||
attachment?: boolean
|
||||
reasoning?: boolean
|
||||
temperature?: boolean
|
||||
tool_call?: boolean
|
||||
cost?: {
|
||||
input: number
|
||||
output: number
|
||||
cache_read?: number
|
||||
cache_write?: number
|
||||
context_over_200k?: {
|
||||
input: number
|
||||
output: number
|
||||
cache_read?: number
|
||||
cache_write?: number
|
||||
}
|
||||
}
|
||||
limit?: {
|
||||
context: number
|
||||
output: number
|
||||
}
|
||||
modalities?: {
|
||||
input: Array<"text" | "audio" | "image" | "video" | "pdf">
|
||||
output: Array<"text" | "audio" | "image" | "video" | "pdf">
|
||||
}
|
||||
experimental?: boolean
|
||||
status?: "alpha" | "beta" | "deprecated"
|
||||
options?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
headers?: {
|
||||
[key: string]: string
|
||||
}
|
||||
provider?: {
|
||||
npm: string
|
||||
}
|
||||
}
|
||||
}
|
||||
whitelist?: Array<string>
|
||||
blacklist?: Array<string>
|
||||
options?: {
|
||||
apiKey?: string
|
||||
baseURL?: string
|
||||
/**
|
||||
* GitHub Enterprise URL for copilot authentication
|
||||
*/
|
||||
enterpriseUrl?: string
|
||||
/**
|
||||
* Enable promptCacheKey for this provider (default false)
|
||||
*/
|
||||
setCacheKey?: boolean
|
||||
/**
|
||||
* Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.
|
||||
*/
|
||||
timeout?: number | false
|
||||
[key: string]: unknown | string | boolean | (number | false) | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export type McpLocalConfig = {
|
||||
/**
|
||||
* Type of MCP server connection
|
||||
@@ -1100,74 +1232,7 @@ export type Config = {
|
||||
* Custom provider configurations and model overrides
|
||||
*/
|
||||
provider?: {
|
||||
[key: string]: {
|
||||
api?: string
|
||||
name?: string
|
||||
env?: Array<string>
|
||||
id?: string
|
||||
npm?: string
|
||||
models?: {
|
||||
[key: string]: {
|
||||
id?: string
|
||||
name?: string
|
||||
release_date?: string
|
||||
attachment?: boolean
|
||||
reasoning?: boolean
|
||||
temperature?: boolean
|
||||
tool_call?: boolean
|
||||
cost?: {
|
||||
input: number
|
||||
output: number
|
||||
cache_read?: number
|
||||
cache_write?: number
|
||||
context_over_200k?: {
|
||||
input: number
|
||||
output: number
|
||||
cache_read?: number
|
||||
cache_write?: number
|
||||
}
|
||||
}
|
||||
limit?: {
|
||||
context: number
|
||||
output: number
|
||||
}
|
||||
modalities?: {
|
||||
input: Array<"text" | "audio" | "image" | "video" | "pdf">
|
||||
output: Array<"text" | "audio" | "image" | "video" | "pdf">
|
||||
}
|
||||
experimental?: boolean
|
||||
status?: "alpha" | "beta" | "deprecated"
|
||||
options?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
headers?: {
|
||||
[key: string]: string
|
||||
}
|
||||
provider?: {
|
||||
npm: string
|
||||
}
|
||||
}
|
||||
}
|
||||
whitelist?: Array<string>
|
||||
blacklist?: Array<string>
|
||||
options?: {
|
||||
apiKey?: string
|
||||
baseURL?: string
|
||||
/**
|
||||
* GitHub Enterprise URL for copilot authentication
|
||||
*/
|
||||
enterpriseUrl?: string
|
||||
/**
|
||||
* Enable promptCacheKey for this provider (default false)
|
||||
*/
|
||||
setCacheKey?: boolean
|
||||
/**
|
||||
* Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.
|
||||
*/
|
||||
timeout?: number | false
|
||||
[key: string]: unknown | string | boolean | (number | false) | undefined
|
||||
}
|
||||
}
|
||||
[key: string]: ProviderConfig
|
||||
}
|
||||
/**
|
||||
* MCP (Model Context Protocol) server configurations
|
||||
@@ -1257,6 +1322,10 @@ export type Config = {
|
||||
* Enable the batch tool
|
||||
*/
|
||||
batch_tool?: boolean
|
||||
/**
|
||||
* Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)
|
||||
*/
|
||||
openTelemetry?: boolean
|
||||
/**
|
||||
* Tools that should only be available to primary agents.
|
||||
*/
|
||||
@@ -1264,14 +1333,6 @@ export type Config = {
|
||||
}
|
||||
}
|
||||
|
||||
export type BadRequestError = {
|
||||
data: unknown
|
||||
errors: Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
success: false
|
||||
}
|
||||
|
||||
export type ToolIds = Array<string>
|
||||
|
||||
export type ToolListItem = {
|
||||
@@ -1293,13 +1354,6 @@ export type VcsInfo = {
|
||||
branch: string
|
||||
}
|
||||
|
||||
export type NotFoundError = {
|
||||
name: "NotFoundError"
|
||||
data: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TextPartInput = {
|
||||
id?: string
|
||||
type: "text"
|
||||
@@ -1354,51 +1408,71 @@ export type Command = {
|
||||
|
||||
export type Model = {
|
||||
id: string
|
||||
providerID: string
|
||||
api: {
|
||||
id: string
|
||||
url: string
|
||||
npm: string
|
||||
}
|
||||
name: string
|
||||
release_date: string
|
||||
attachment: boolean
|
||||
reasoning: boolean
|
||||
temperature: boolean
|
||||
tool_call: boolean
|
||||
capabilities: {
|
||||
temperature: boolean
|
||||
reasoning: boolean
|
||||
attachment: boolean
|
||||
toolcall: boolean
|
||||
input: {
|
||||
text: boolean
|
||||
audio: boolean
|
||||
image: boolean
|
||||
video: boolean
|
||||
pdf: boolean
|
||||
}
|
||||
output: {
|
||||
text: boolean
|
||||
audio: boolean
|
||||
image: boolean
|
||||
video: boolean
|
||||
pdf: boolean
|
||||
}
|
||||
}
|
||||
cost: {
|
||||
input: number
|
||||
output: number
|
||||
cache_read?: number
|
||||
cache_write?: number
|
||||
context_over_200k?: {
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
experimentalOver200K?: {
|
||||
input: number
|
||||
output: number
|
||||
cache_read?: number
|
||||
cache_write?: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
}
|
||||
limit: {
|
||||
context: number
|
||||
output: number
|
||||
}
|
||||
modalities?: {
|
||||
input: Array<"text" | "audio" | "image" | "video" | "pdf">
|
||||
output: Array<"text" | "audio" | "image" | "video" | "pdf">
|
||||
}
|
||||
experimental?: boolean
|
||||
status?: "alpha" | "beta" | "deprecated"
|
||||
status: "alpha" | "beta" | "deprecated" | "active"
|
||||
options: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
headers?: {
|
||||
headers: {
|
||||
[key: string]: string
|
||||
}
|
||||
provider?: {
|
||||
npm: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Provider = {
|
||||
api?: string
|
||||
name: string
|
||||
env: Array<string>
|
||||
id: string
|
||||
npm?: string
|
||||
name: string
|
||||
source: "env" | "config" | "custom" | "api"
|
||||
env: Array<string>
|
||||
key?: string
|
||||
options: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
models: {
|
||||
[key: string]: Model
|
||||
}
|
||||
@@ -1489,6 +1563,7 @@ export type Agent = {
|
||||
options: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
maxSteps?: number
|
||||
}
|
||||
|
||||
export type McpStatusConnected = {
|
||||
@@ -1592,6 +1667,181 @@ export type ProjectCurrentResponses = {
|
||||
|
||||
export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses]
|
||||
|
||||
export type PtyListData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/pty"
|
||||
}
|
||||
|
||||
export type PtyListResponses = {
|
||||
/**
|
||||
* List of sessions
|
||||
*/
|
||||
200: Array<Pty>
|
||||
}
|
||||
|
||||
export type PtyListResponse = PtyListResponses[keyof PtyListResponses]
|
||||
|
||||
export type PtyCreateData = {
|
||||
body?: {
|
||||
command?: string
|
||||
args?: Array<string>
|
||||
cwd?: string
|
||||
title?: string
|
||||
env?: {
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/pty"
|
||||
}
|
||||
|
||||
export type PtyCreateErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
}
|
||||
|
||||
export type PtyCreateError = PtyCreateErrors[keyof PtyCreateErrors]
|
||||
|
||||
export type PtyCreateResponses = {
|
||||
/**
|
||||
* Created session
|
||||
*/
|
||||
200: Pty
|
||||
}
|
||||
|
||||
export type PtyCreateResponse = PtyCreateResponses[keyof PtyCreateResponses]
|
||||
|
||||
export type PtyRemoveData = {
|
||||
body?: never
|
||||
path: {
|
||||
id: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/pty/{id}"
|
||||
}
|
||||
|
||||
export type PtyRemoveErrors = {
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type PtyRemoveError = PtyRemoveErrors[keyof PtyRemoveErrors]
|
||||
|
||||
export type PtyRemoveResponses = {
|
||||
/**
|
||||
* Session removed
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type PtyRemoveResponse = PtyRemoveResponses[keyof PtyRemoveResponses]
|
||||
|
||||
export type PtyGetData = {
|
||||
body?: never
|
||||
path: {
|
||||
id: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/pty/{id}"
|
||||
}
|
||||
|
||||
export type PtyGetErrors = {
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type PtyGetError = PtyGetErrors[keyof PtyGetErrors]
|
||||
|
||||
export type PtyGetResponses = {
|
||||
/**
|
||||
* Session info
|
||||
*/
|
||||
200: Pty
|
||||
}
|
||||
|
||||
export type PtyGetResponse = PtyGetResponses[keyof PtyGetResponses]
|
||||
|
||||
export type PtyUpdateData = {
|
||||
body?: {
|
||||
title?: string
|
||||
size?: {
|
||||
rows: number
|
||||
cols: number
|
||||
}
|
||||
}
|
||||
path: {
|
||||
id: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/pty/{id}"
|
||||
}
|
||||
|
||||
export type PtyUpdateErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
}
|
||||
|
||||
export type PtyUpdateError = PtyUpdateErrors[keyof PtyUpdateErrors]
|
||||
|
||||
export type PtyUpdateResponses = {
|
||||
/**
|
||||
* Updated session
|
||||
*/
|
||||
200: Pty
|
||||
}
|
||||
|
||||
export type PtyUpdateResponse = PtyUpdateResponses[keyof PtyUpdateResponses]
|
||||
|
||||
export type PtyConnectData = {
|
||||
body?: never
|
||||
path: {
|
||||
id: string
|
||||
}
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/pty/{id}/connect"
|
||||
}
|
||||
|
||||
export type PtyConnectErrors = {
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: NotFoundError
|
||||
}
|
||||
|
||||
export type PtyConnectError = PtyConnectErrors[keyof PtyConnectErrors]
|
||||
|
||||
export type PtyConnectResponses = {
|
||||
/**
|
||||
* Connected session
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type PtyConnectResponse = PtyConnectResponses[keyof PtyConnectResponses]
|
||||
|
||||
export type ConfigGetData = {
|
||||
body?: never
|
||||
path?: never
|
||||
@@ -2665,7 +2915,55 @@ export type ProviderListResponses = {
|
||||
* List of providers
|
||||
*/
|
||||
200: {
|
||||
all: Array<Provider>
|
||||
all: Array<{
|
||||
api?: string
|
||||
name: string
|
||||
env: Array<string>
|
||||
id: string
|
||||
npm?: string
|
||||
models: {
|
||||
[key: string]: {
|
||||
id: string
|
||||
name: string
|
||||
release_date: string
|
||||
attachment: boolean
|
||||
reasoning: boolean
|
||||
temperature: boolean
|
||||
tool_call: boolean
|
||||
cost?: {
|
||||
input: number
|
||||
output: number
|
||||
cache_read?: number
|
||||
cache_write?: number
|
||||
context_over_200k?: {
|
||||
input: number
|
||||
output: number
|
||||
cache_read?: number
|
||||
cache_write?: number
|
||||
}
|
||||
}
|
||||
limit: {
|
||||
context: number
|
||||
output: number
|
||||
}
|
||||
modalities?: {
|
||||
input: Array<"text" | "audio" | "image" | "video" | "pdf">
|
||||
output: Array<"text" | "audio" | "image" | "video" | "pdf">
|
||||
}
|
||||
experimental?: boolean
|
||||
status?: "alpha" | "beta" | "deprecated"
|
||||
options: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
headers?: {
|
||||
[key: string]: string
|
||||
}
|
||||
provider?: {
|
||||
npm: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}>
|
||||
default: {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun run src/index.ts",
|
||||
|
||||
@@ -1,35 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" href="/src/styles.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tauri App</title>
|
||||
<script type="module" src="/src/main.ts" defer></script>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OpenCode</title>
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<meta name="theme-color" content="#F8F7F7" />
|
||||
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
|
||||
<meta property="og:image" content="/social-share.png" />
|
||||
<meta property="twitter:image" content="/social-share.png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="container">
|
||||
<h1>Welcome to Tauri</h1>
|
||||
|
||||
<div class="row">
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img src="/src/assets/vite.svg" class="logo vite" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://tauri.app" target="_blank">
|
||||
<img src="/src/assets/tauri.svg" class="logo tauri" alt="Tauri logo" />
|
||||
</a>
|
||||
<a href="https://www.typescriptlang.org/docs" target="_blank">
|
||||
<img src="/src/assets/typescript.svg" class="logo typescript" alt="typescript logo" />
|
||||
</a>
|
||||
</div>
|
||||
<p>Click on the Tauri logo to learn more about the framework</p>
|
||||
|
||||
<form class="row" id="greet-form">
|
||||
<input id="greet-input" placeholder="Enter a name..." />
|
||||
<button type="submit">Greet</button>
|
||||
</form>
|
||||
<p id="greet-msg"></p>
|
||||
</main>
|
||||
<body class="antialiased overscroll-none select-none text-12-regular">
|
||||
<script>
|
||||
;(function () {
|
||||
const savedTheme = localStorage.getItem("theme") || "oc-1"
|
||||
document.documentElement.setAttribute("data-theme", savedTheme)
|
||||
})()
|
||||
</script>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script src="/src/index.ts" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
{
|
||||
"name": "@opencode-ai/tauri",
|
||||
"private": true,
|
||||
"version": "1.0.129",
|
||||
"version": "1.0.134",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"predev": "bun ./scripts/predev.ts",
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/desktop": "workspace:*",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2"
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-shell": "~2",
|
||||
"@tauri-apps/plugin-updater": "~2",
|
||||
"solid-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "4.0.0",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"vite": "^6.0.3",
|
||||
"typescript": "~5.6.2"
|
||||
"@types/bun": "catalog:",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
12
packages/tauri/scripts/copy-bundles.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { $ } from "bun"
|
||||
import * as path from "node:path"
|
||||
|
||||
import { RUST_TARGET } from "./utils"
|
||||
|
||||
if (!RUST_TARGET) throw new Error("RUST_TARGET not defined")
|
||||
|
||||
const BUNDLE_DIR = `src-tauri/target/${RUST_TARGET}/release/bundle`
|
||||
const BUNDLES_OUT_DIR = path.join(process.cwd(), `src-tauri/target/bundles`)
|
||||
|
||||
await $`mkdir -p ${BUNDLES_OUT_DIR}`
|
||||
await $`cp -r ${BUNDLE_DIR}/*/OpenCode* ${BUNDLES_OUT_DIR}`
|
||||
17
packages/tauri/scripts/predev.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as fs from "node:fs/promises"
|
||||
import { $ } from "bun"
|
||||
|
||||
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
|
||||
|
||||
const RUST_TARGET = Bun.env.TAURI_ENV_TARGET_TRIPLE
|
||||
|
||||
const sidecarConfig = getCurrentSidecar(RUST_TARGET)
|
||||
|
||||
const binaryPath = `../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode`
|
||||
|
||||
if (!(await fs.exists(binaryPath))) {
|
||||
console.log("opencode binary not found, building...")
|
||||
await $`cd ../opencode && bun run build --single`
|
||||
}
|
||||
|
||||
await copyBinaryToSidecarFolder(binaryPath, RUST_TARGET)
|
||||
20
packages/tauri/scripts/prepare.ts
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
|
||||
import { copyBinaryToSidecarFolder, getCurrentSidecar } from "./utils"
|
||||
|
||||
const sidecarConfig = getCurrentSidecar()
|
||||
|
||||
const dir = "src-tauri/target/opencode-binaries"
|
||||
|
||||
await $`mkdir -p ${dir}`
|
||||
await $`gh release download --pattern ${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} --repo sst/opencode --skip-existing --dir ${dir}`
|
||||
|
||||
if (sidecarConfig.assetExt === "tar.gz") {
|
||||
await $`tar -xvzf ${dir}/${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} -C ${dir}`
|
||||
} else {
|
||||
await $`unzip -o ${dir}/${sidecarConfig.ocBinary}.${sidecarConfig.assetExt} -d ${dir}`
|
||||
}
|
||||
|
||||
await copyBinaryToSidecarFolder(`${dir}/opencode${process.platform === "win32" ? ".exe" : ""}`)
|
||||
43
packages/tauri/scripts/utils.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { $ } from "bun"
|
||||
|
||||
export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; assetExt: string }> = [
|
||||
{
|
||||
rustTarget: "aarch64-apple-darwin",
|
||||
ocBinary: "opencode-darwin-arm64",
|
||||
assetExt: "zip",
|
||||
},
|
||||
{
|
||||
rustTarget: "x86_64-apple-darwin",
|
||||
ocBinary: "opencode-darwin-x64",
|
||||
assetExt: "zip",
|
||||
},
|
||||
{
|
||||
rustTarget: "x86_64-pc-windows-msvc",
|
||||
ocBinary: "opencode-windows-x64",
|
||||
assetExt: "zip",
|
||||
},
|
||||
{
|
||||
rustTarget: "x86_64-unknown-linux-gnu",
|
||||
ocBinary: "opencode-linux-x64",
|
||||
assetExt: "tar.gz",
|
||||
},
|
||||
]
|
||||
|
||||
export const RUST_TARGET = Bun.env.RUST_TARGET
|
||||
|
||||
export function getCurrentSidecar(target = RUST_TARGET) {
|
||||
if (!target && !RUST_TARGET) throw new Error("RUST_TARGET not set")
|
||||
|
||||
const binaryConfig = SIDECAR_BINARIES.find((b) => b.rustTarget === target)
|
||||
if (!binaryConfig) throw new Error(`Sidecar configuration not available for Rust target '${RUST_TARGET}'`)
|
||||
|
||||
return binaryConfig
|
||||
}
|
||||
|
||||
export async function copyBinaryToSidecarFolder(source: string, target = RUST_TARGET) {
|
||||
await $`mkdir -p src-tauri/sidecars`
|
||||
const dest = `src-tauri/sidecars/opencode-${target}${process.platform === "win32" ? ".exe" : ""}`
|
||||
await $`cp ${source} ${dest}`
|
||||
|
||||
console.log(`Copied ${source} to ${dest}`)
|
||||
}
|
||||
2
packages/tauri/src-tauri/.gitignore
vendored
@@ -5,3 +5,5 @@
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
|
||||
sidecars
|
||||
|
||||
649
packages/tauri/src-tauri/Cargo.lock
generated
@@ -47,6 +47,36 @@ version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"rand 0.9.2",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"url",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
@@ -656,6 +686,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
@@ -713,6 +754,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.6.2",
|
||||
"libc",
|
||||
"objc2 0.6.3",
|
||||
]
|
||||
|
||||
@@ -727,6 +770,15 @@ dependencies = [
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2"
|
||||
version = "0.8.0"
|
||||
@@ -750,6 +802,12 @@ dependencies = [
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dpi"
|
||||
version = "0.1.2"
|
||||
@@ -806,6 +864,15 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.0"
|
||||
@@ -906,6 +973,18 @@ dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"libredox",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.5"
|
||||
@@ -1195,8 +1274,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1206,9 +1287,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1468,6 +1551,23 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.18"
|
||||
@@ -1861,6 +1961,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1869,6 +1970,17 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "listeners"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "557f908c6cb431dd2496687aa9ea326507110ee4780517a42ade2df25d31126c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rustix",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.1"
|
||||
@@ -1890,6 +2002,12 @@ version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@@ -1948,6 +2066,12 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minisign-verify"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -2283,6 +2407,18 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-osa-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"objc2 0.6.3",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.2.2"
|
||||
@@ -2365,14 +2501,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opencode"
|
||||
version = "0.1.0"
|
||||
name = "opencode-desktop"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"listeners",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-updater",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2391,6 +2532,30 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "osakit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
||||
dependencies = [
|
||||
"objc2 0.6.3",
|
||||
"objc2-foundation 0.3.2",
|
||||
"objc2-osa-kit",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -2628,7 +2793,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.12.1",
|
||||
"quick-xml",
|
||||
"quick-xml 0.38.4",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
@@ -2758,6 +2923,15 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.4"
|
||||
@@ -2767,6 +2941,61 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
"rand 0.9.2",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.17",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
@@ -2807,6 +3036,16 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
@@ -2827,6 +3066,16 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
@@ -2845,6 +3094,15 @@ dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
@@ -2952,16 +3210,21 @@ dependencies = [
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
@@ -2971,8 +3234,54 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"block2 0.6.2",
|
||||
"dispatch2",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.6.3",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@@ -2995,6 +3304,41 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
@@ -3067,6 +3411,12 @@ dependencies = [
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -3282,12 +3632,44 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"sigchld",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "sigchld"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"os_pipe",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.6"
|
||||
@@ -3428,6 +3810,12 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
@@ -3545,6 +3933,17 @@ dependencies = [
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.16"
|
||||
@@ -3682,6 +4081,46 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.17",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"percent-encoding",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.17",
|
||||
"toml 0.9.8",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.2"
|
||||
@@ -3704,6 +4143,59 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c374b6db45f2a8a304f0273a15080d98c70cde86178855fc24653ba657a1144c"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"log",
|
||||
"open",
|
||||
"os_pipe",
|
||||
"regex",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shared_child",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"http",
|
||||
"infer",
|
||||
"log",
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tar",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tempfile",
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"windows-sys 0.60.2",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.9.1"
|
||||
@@ -3910,6 +4402,21 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.48.0"
|
||||
@@ -3920,10 +4427,22 @@ dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.17"
|
||||
@@ -4213,6 +4732,12 @@ version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.7"
|
||||
@@ -4404,6 +4929,66 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml 0.37.5",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.82"
|
||||
@@ -4414,6 +4999,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "2.0.1"
|
||||
@@ -4458,6 +5053,15 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.38.0"
|
||||
@@ -4688,6 +5292,15 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -5025,6 +5638,16 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
@@ -5072,6 +5695,7 @@ dependencies = [
|
||||
"ordered-stream",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"uuid",
|
||||
@@ -5150,6 +5774,12 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.3"
|
||||
@@ -5183,6 +5813,18 @@ dependencies = [
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"indexmap 2.12.1",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.8.0"
|
||||
@@ -5192,6 +5834,7 @@ dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"url",
|
||||
"winnow 0.7.13",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "opencode"
|
||||
version = "0.1.0"
|
||||
name = "opencode-desktop"
|
||||
version = "0.0.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
@@ -18,8 +18,13 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = ["macos-private-api", "devtools"] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
tokio = "1.48.0"
|
||||
listeners = "0.3"
|
||||
|
||||
@@ -3,5 +3,12 @@
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": ["core:default", "opener:default"]
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"core:window:allow-start-dragging",
|
||||
"shell:default",
|
||||
"updater:default",
|
||||
"dialog:default"
|
||||
]
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 486 B |
BIN
packages/tauri/src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 540 B |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 745 B |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 940 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 952 B |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 1.5 KiB |