Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23ff6dbba4 | ||
|
|
b457923970 | ||
|
|
66e4a5a64e | ||
|
|
6c25e64658 | ||
|
|
f2fd0f8f00 | ||
|
|
44cdde5422 | ||
|
|
88235dc618 | ||
|
|
4d2b671d7b | ||
|
|
8098031eac | ||
|
|
74c882d9d0 | ||
|
|
80636fec43 | ||
|
|
a8ad74aef3 | ||
|
|
e2e2b7934e | ||
|
|
28c802f399 | ||
|
|
bcfa63aa4e | ||
|
|
d40feafb01 | ||
|
|
2a8473891b | ||
|
|
a4e3451d5c | ||
|
|
53a7c2885b | ||
|
|
f354507d42 | ||
|
|
f17e1def32 | ||
|
|
3183e8b7d4 | ||
|
|
9d2b9ef2d4 | ||
|
|
733e5cd876 | ||
|
|
4ee4f7bcb3 | ||
|
|
da7ecda9ea | ||
|
|
1f11d4fb1a | ||
|
|
b308503ab5 | ||
|
|
3b10de4a28 | ||
|
|
6ce1de476a | ||
|
|
d9b0848a61 | ||
|
|
46dd3b8166 | ||
|
|
eca07be072 | ||
|
|
165d57b88e | ||
|
|
28c44f7e5a | ||
|
|
39d5bdff4b | ||
|
|
b9f8480b2f | ||
|
|
58b30d678a | ||
|
|
408cdaf5e0 | ||
|
|
cae23cde09 | ||
|
|
702fb2562c | ||
|
|
785d0b60b6 | ||
|
|
67ab9dc4d0 | ||
|
|
77494cb7df | ||
|
|
08f8856d6c | ||
|
|
79a4c1544d | ||
|
|
c0a35141e6 | ||
|
|
221bb64aeb | ||
|
|
2555dba188 | ||
|
|
6355ed6ae7 | ||
|
|
1864e8c863 | ||
|
|
61d0d66dae | ||
|
|
86522f1b3e | ||
|
|
dc32705bc9 | ||
|
|
1eaf5c31d3 | ||
|
|
677b19e22e | ||
|
|
8e248ae045 | ||
|
|
0acefd5c08 | ||
|
|
0331931f56 | ||
|
|
01e2c9cc21 | ||
|
|
4acb645f04 | ||
|
|
ef664039ac | ||
|
|
993422c6af | ||
|
|
c950d59047 | ||
|
|
cc726e0200 | ||
|
|
3d99dc78db | ||
|
|
95c3a8b805 | ||
|
|
f1bb5870ce | ||
|
|
540407e193 | ||
|
|
027d43b5ea | ||
|
|
729a6eda23 | ||
|
|
cbbd21e2e5 | ||
|
|
e7d45ca617 | ||
|
|
0ddfdb55d3 | ||
|
|
fc439455a7 | ||
|
|
c0fc02769b | ||
|
|
068ac68496 | ||
|
|
7da6a22df2 | ||
|
|
e37aeb6e6a | ||
|
|
eeb0d2b8e0 | ||
|
|
bf8c866bf7 | ||
|
|
75354b0002 |
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
@@ -11,7 +11,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
check-duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Send nicely-formatted embed to Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
startsWith(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
startsWith(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
@@ -14,7 +14,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -13,7 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -25,7 +25,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -14,7 +14,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
stats:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
zed:
|
||||
name: Release Zed Extension
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -19,7 +19,7 @@ on:
|
||||
jobs:
|
||||
update:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
env:
|
||||
SYSTEM: x86_64-linux
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: Git commit and push
|
||||
description: git commit and push
|
||||
---
|
||||
|
||||
commit and push
|
||||
@@ -21,3 +21,6 @@ WHAT was done.
|
||||
|
||||
do not do generic messages like "improved agent experience" be very specific
|
||||
about what user facing changes were made
|
||||
|
||||
if there are changes do a git pull --rebase
|
||||
if there are conflicts DO NOT FIX THEM. notify me and I will fix them
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: "Find issue(s) on github"
|
||||
description: "find issue(s) on github"
|
||||
model: opencode/claude-haiku-4-5
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: Spellcheck all markdown file changes
|
||||
description: spellcheck all markdown file changes
|
||||
---
|
||||
|
||||
Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
sst-env.d.ts
|
||||
@@ -156,3 +156,5 @@
|
||||
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
|
||||
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
|
||||
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
|
||||
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
|
||||
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"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.124",
|
||||
"version": "1.0.129",
|
||||
"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.124",
|
||||
"version": "1.0.129",
|
||||
"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.124",
|
||||
"version": "1.0.129",
|
||||
"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.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -164,7 +164,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -192,7 +192,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
@@ -208,7 +208,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -238,8 +238,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.2.8",
|
||||
"@opentui/core": "0.1.54",
|
||||
"@opentui/solid": "0.1.54",
|
||||
"@opentui/core": "0.1.55",
|
||||
"@opentui/solid": "0.1.55",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -297,7 +297,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -317,7 +317,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.81.0",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -328,7 +328,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -341,7 +341,7 @@
|
||||
},
|
||||
"packages/tauri": {
|
||||
"name": "@opencode-ai/tauri",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
@@ -354,7 +354,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -386,17 +386,18 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -442,7 +443,7 @@
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.5.7",
|
||||
"@pierre/precision-diffs": "0.6.0-beta.3",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
@@ -1087,21 +1088,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.54", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.54", "@opentui/core-darwin-x64": "0.1.54", "@opentui/core-linux-arm64": "0.1.54", "@opentui/core-linux-x64": "0.1.54", "@opentui/core-win32-arm64": "0.1.54", "@opentui/core-win32-x64": "0.1.54", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-NYBVOmAa3JB+bSTxFTUc3Ej3B8Gc364DMsoktVRCYdjQ+AyeMRNdOTj9sMWdDJbXqITK/atrsUXouoE5bL9sWA=="],
|
||||
"@opentui/core": ["@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-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.54", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LyJ2AI9XA3upckiXLaX/P6tB40pB6euZA5MCikyhwcDVew3Z7NpTI0xHp4gUWHwsSTwmx6u851ofgkf7nMBEJg=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.55", "", { "os": "darwin", "cpu": "arm64" }, "sha512-z2Prd/KKUbhPaSGBFv2q0nDtiLB/5oI3sGFDgf+YAfs6M6UfP9n0XkPUupbE1dx4lMyvwA9X8/QUnsQApd3E2g=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.54", "", { "os": "darwin", "cpu": "x64" }, "sha512-LZAjXR1OoUr8pewBTHqxupdPS4PC4qQCht0HHGEr1+zN7zu5c+kcp3Uop+NeQTYLjHJftiULukzAfLJMXFuDNA=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.55", "", { "os": "darwin", "cpu": "x64" }, "sha512-zjgGmIaTCWUvvQ9vIHJ0ypTkuFIA4ykKiZ16QxpG930bPr9fJ1xZ8MYj+6WSyuiao7tm6iWQfuYKT3tzA8+ItQ=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.54", "", { "os": "linux", "cpu": "arm64" }, "sha512-AC7qBYc4shY28/eR1BvguvfCjLzZJy0mdvadSdDF0XJJIe5vhWJ9UyKsoyLo7pwrd0iFBf5d7fg4kJcbLbZNWg=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.55", "", { "os": "linux", "cpu": "arm64" }, "sha512-77EZtLxH0VW/Kw+6kTs9FrFWfhjaIjsK/o39DAWM1ZNdFDTXAa/MQNOFDlBXbNHiNqPOyxd0tol1nUFLr8ZtZg=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.54", "", { "os": "linux", "cpu": "x64" }, "sha512-S7BAq4CUV6ZfoXDSClJT75XxVSKPOOuRVYvBSHMOgwkBwLHpJZdz0Z4mzcGBBP1nJQ5Gz92TWPYfYMbZvR1lpw=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.55", "", { "os": "linux", "cpu": "x64" }, "sha512-o4RB1jqKWx4TM9v2trGUij6H2ymJCxID8BK3HWvRIjd71tpKkaMY4SxaMWGzvK89X40u8v9qKE04dileKNa10w=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.54", "", { "os": "win32", "cpu": "arm64" }, "sha512-K+E9i8t6YfN0Ly9moHefRQfR5GSbQUAsqzfrW4TD4bpJBy5y7EKh1mz8ZdES/RwWOSGUCb+JN7/ZQm0OubbdvQ=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.55", "", { "os": "win32", "cpu": "arm64" }, "sha512-SYna371ZcQme6XjGI2ESHM2uMUZQRi9kgtJj5E22uH4wgBpPWFwf83EGWv78v+irvsypR+ZJgVfkwkz6JjgVTQ=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.54", "", { "os": "win32", "cpu": "x64" }, "sha512-0KsBPRtcqmPM1AoN2Ez9au6uf7y8631W9tXTWnVZRlI2/G3j1AfGReR6vuwrd8O4JfxFXiYWWPfSxYZvWLSRNg=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.55", "", { "os": "win32", "cpu": "x64" }, "sha512-ViPBCm+EtZ/4NmLqLAxcz31lVYGCe1ily+YmfAkoq1K/iuiXGhtD3mDrQuc3ayfTT8w5UwiYKyrUibJVJ/noVQ=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.54", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.54", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-rr9moI+W3meoD57t4Flyfw33mRSFIX0FOx+t4T3sLNA3Pz5FGU2n4qyxPc21OkAuc23S+YxuEuwbZebnIaLGWA=="],
|
||||
"@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=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -1217,7 +1218,7 @@
|
||||
|
||||
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
|
||||
|
||||
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.5.7", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-Y+e4kJ9pT2I4NS5fE39KdoiXtwMkVPRvrwLM6O2IqO7PDCRWLBS7CYxcSgSyngEndccUll2krx66I2QnfO0Ovg=="],
|
||||
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.0-beta.3", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-1FBm9jhLWZvs7BqN3yG2Wh9SpGuO1us2QsKZlQqSwyCctMr9DRGzYQJ9lF6yR03LHzXs3fuIzO++d9sCObYzrQ=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
|
||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1764527385,
|
||||
"narHash": "sha256-nA5ywiGKl76atrbdZ5Aucd8SjF/v8ew9b9QsC+MKL14=",
|
||||
"lastModified": 1764642553,
|
||||
"narHash": "sha256-mvbFFzVBhVK1FjyPHZGMAKpNiqkr7k++xIwy+p/NQvA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "23258e03aaa49b3a68597e3e50eb0cbce7e42e9d",
|
||||
"rev": "f720de59066162ee879adcc8c79e15c51fe6bfb4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
+1
-1
@@ -116,7 +116,7 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
|
||||
// CONSOLE
|
||||
////////////////
|
||||
|
||||
const bucket = new sst.cloudflare.Bucket("ConsoleData")
|
||||
const bucket = new sst.cloudflare.Bucket("ZenData")
|
||||
|
||||
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
|
||||
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
|
||||
|
||||
@@ -4,7 +4,7 @@ APP=opencode
|
||||
|
||||
MUTED='\033[0;2m'
|
||||
RED='\033[0;31m'
|
||||
ORANGE='\033[38;2;255;140;0m'
|
||||
ORANGE='\033[38;5;214m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
requested_version=${VERSION:-}
|
||||
@@ -353,10 +353,9 @@ echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░
|
||||
echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"
|
||||
echo -e ""
|
||||
echo -e ""
|
||||
echo -e "${MUTED}To get started, navigate to a project and run:${NC}"
|
||||
echo -e "opencode ${MUTED}Use free models${NC}"
|
||||
echo -e "opencode auth login ${MUTED}Add paid provider API keys${NC}"
|
||||
echo -e "opencode help ${MUTED}List commands and options${NC}"
|
||||
echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
|
||||
echo -e "cd <project> ${MUTED}# Open directory${NC}"
|
||||
echo -e "opencode ${MUTED}# Run command${NC}"
|
||||
echo -e ""
|
||||
echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
|
||||
echo -e ""
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"nodeModules": "sha256-CrewwFMa3zxKTM/XpnRVQm75FB7Jk1tXX40D+kYt0YE="
|
||||
"nodeModules": "sha256-QhqAa47P3Y2aoMGnr8l1nLq0EQb4qEm75dGfNjyzbpU="
|
||||
}
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.5.7",
|
||||
"@pierre/precision-diffs": "0.6.0-beta.3",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"ai": "5.0.97",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -9,8 +9,8 @@ export const config = {
|
||||
github: {
|
||||
repoUrl: "https://github.com/sst/opencode",
|
||||
starsFormatted: {
|
||||
compact: "30K",
|
||||
full: "30,000",
|
||||
compact: "35K",
|
||||
full: "35,000",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,8 +22,8 @@ export const config = {
|
||||
|
||||
// Static stats (used on landing page)
|
||||
stats: {
|
||||
contributors: "300",
|
||||
commits: "4,000",
|
||||
monthlyUsers: "300,000",
|
||||
contributors: "350",
|
||||
commits: "5,000",
|
||||
monthlyUsers: "400,000",
|
||||
},
|
||||
} as const
|
||||
|
||||
@@ -338,6 +338,11 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="installation-instructions"] {
|
||||
color: var(--color-text-strong);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
[data-slot="installation"] {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
@@ -348,6 +353,11 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="installation-options"] {
|
||||
font-size: 13px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
[data-component="tabs"] {
|
||||
[data-slot="tablist"] {
|
||||
display: flex;
|
||||
@@ -480,10 +490,10 @@ body {
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
font-size: 38px;
|
||||
color: var(--color-text-strong);
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
font-size: 22px;
|
||||
@@ -492,7 +502,7 @@ body {
|
||||
|
||||
p {
|
||||
color: var(--color-text);
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 40px;
|
||||
max-width: 82%;
|
||||
|
||||
@media (max-width: 50rem) {
|
||||
@@ -607,6 +617,25 @@ body {
|
||||
padding: var(--vertical-padding) var(--padding);
|
||||
color: var(--color-text);
|
||||
|
||||
a {
|
||||
background: var(--color-background-strong);
|
||||
padding: 8px 12px 8px 20px;
|
||||
color: var(--color-text-inverted);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
gap: 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: var(--color-background-strong-hover);
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
li {
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function Home() {
|
||||
return (
|
||||
<main data-page="opencode">
|
||||
{/*<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />*/}
|
||||
<Title>OpenCode | The AI coding agent built for the terminal</Title>
|
||||
<Title>OpenCode | The open source AI coding agent</Title>
|
||||
<Link rel="canonical" href={config.baseUrl} />
|
||||
<Meta property="og:image" content="/social-share.png" />
|
||||
<Meta name="twitter:image" content="/social-share.png" />
|
||||
@@ -56,23 +56,13 @@ export default function Home() {
|
||||
<a data-slot="releases" href={release()?.url ?? `${config.github.repoUrl}/releases`} target="_blank">
|
||||
What’s new in {release()?.name ?? "the latest release"}
|
||||
</a>
|
||||
<h1>The AI coding agent built for the terminal</h1>
|
||||
<h1>The open source coding agent</h1>
|
||||
<p>
|
||||
OpenCode is fully open source, giving you control and freedom to use any provider, any model, and any
|
||||
editor.
|
||||
OpenCode includes free models or connect from any provider to <br />
|
||||
use other models, including Claude, GPT, Gemini and more.
|
||||
</p>
|
||||
<a href="/docs">
|
||||
<span>Read docs </span>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<p data-slot="installation-instructions">Install and use. No account, no email, and no credit card.</p>
|
||||
<div data-slot="installation">
|
||||
<Tabs
|
||||
as="section"
|
||||
@@ -151,6 +141,11 @@ export default function Home() {
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
<p data-slot="installation-options">
|
||||
Available in terminal, web, and desktop (coming soon).
|
||||
<br />
|
||||
Extensions for VS Code, Cursor, Windsurf, and more.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section data-component="video">
|
||||
@@ -208,6 +203,17 @@ export default function Home() {
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<a href="/docs">
|
||||
<span>Read docs </span>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section data-component="growth">
|
||||
|
||||
@@ -14,7 +14,7 @@ import "./workspace-picker.css"
|
||||
const getWorkspaces = query(async () => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return Database.transaction((tx) =>
|
||||
return Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
id: WorkspaceTable.id,
|
||||
|
||||
@@ -1,25 +1,37 @@
|
||||
import { Resource, waitUntil } from "@opencode-ai/console-resource"
|
||||
|
||||
export function createDataDumper(sessionId: string, requestId: string) {
|
||||
export function createDataDumper(sessionId: string, requestId: string, projectId: string) {
|
||||
if (Resource.App.stage !== "production") return
|
||||
if (sessionId === "") return
|
||||
|
||||
let data: Record<string, any> = {}
|
||||
let modelName: string | undefined
|
||||
let data: Record<string, any> = { sessionId, requestId, projectId }
|
||||
let metadata: Record<string, any> = { sessionId, requestId, projectId }
|
||||
|
||||
return {
|
||||
provideModel: (model?: string) => (modelName = model),
|
||||
provideModel: (model?: string) => {
|
||||
data.modelName = model
|
||||
metadata.modelName = model
|
||||
},
|
||||
provideRequest: (request: string) => (data.request = request),
|
||||
provideResponse: (response: string) => (data.response = response),
|
||||
provideStream: (chunk: string) => (data.response = (data.response ?? "") + chunk),
|
||||
flush: () => {
|
||||
if (!modelName) return
|
||||
if (!data.modelName) return
|
||||
|
||||
const str = new Date().toISOString().replace(/[^0-9]/g, "")
|
||||
const yyyymmdd = str.substring(0, 8)
|
||||
const hh = str.substring(8, 10)
|
||||
const timestamp = new Date().toISOString().replace(/[^0-9]/g, "")
|
||||
|
||||
waitUntil(
|
||||
Resource.ConsoleData.put(`${yyyymmdd}/${hh}/${modelName}/${sessionId}/${requestId}.json`, JSON.stringify(data)),
|
||||
Resource.ZenData.put(
|
||||
`data/${data.modelName}/${sessionId}/${requestId}.json`,
|
||||
JSON.stringify({ timestamp, ...data }),
|
||||
),
|
||||
)
|
||||
|
||||
waitUntil(
|
||||
Resource.ZenData.put(
|
||||
`meta/${data.modelName}/${timestamp}/${requestId}.json`,
|
||||
JSON.stringify({ timestamp, ...metadata }),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,13 +13,7 @@ import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
|
||||
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
|
||||
import { logger } from "./logger"
|
||||
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
|
||||
import {
|
||||
createBodyConverter,
|
||||
createStreamPartConverter,
|
||||
createResponseConverter,
|
||||
ProviderHelper,
|
||||
UsageInfo,
|
||||
} from "./provider/provider"
|
||||
import { createBodyConverter, createStreamPartConverter, createResponseConverter, UsageInfo } from "./provider/provider"
|
||||
import { anthropicHelper } from "./provider/anthropic"
|
||||
import { googleHelper } from "./provider/google"
|
||||
import { openaiHelper } from "./provider/openai"
|
||||
@@ -61,6 +55,7 @@ export async function handler(
|
||||
const ip = input.request.headers.get("x-real-ip") ?? ""
|
||||
const sessionId = input.request.headers.get("x-opencode-session") ?? ""
|
||||
const requestId = input.request.headers.get("x-opencode-request") ?? ""
|
||||
const projectId = input.request.headers.get("x-opencode-project") ?? ""
|
||||
logger.metric({
|
||||
is_tream: isStream,
|
||||
session: sessionId,
|
||||
@@ -68,7 +63,7 @@ export async function handler(
|
||||
})
|
||||
const zenData = ZenData.list()
|
||||
const modelInfo = validateModel(zenData, model)
|
||||
const dataDumper = createDataDumper(sessionId, requestId)
|
||||
const dataDumper = createDataDumper(sessionId, requestId, projectId)
|
||||
const trialLimiter = createTrialLimiter(modelInfo.trial?.limit, ip)
|
||||
const isTrial = await trialLimiter?.isTrial()
|
||||
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
|
||||
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -11,7 +11,7 @@ export namespace Account {
|
||||
id: z.string().optional(),
|
||||
}),
|
||||
async (input) =>
|
||||
Database.transaction(async (tx) => {
|
||||
Database.use(async (tx) => {
|
||||
const id = input.id ?? Identifier.create("account")
|
||||
await tx.insert(AccountTable).values({
|
||||
id,
|
||||
@@ -21,13 +21,12 @@ export namespace Account {
|
||||
)
|
||||
|
||||
export const fromID = fn(z.string(), async (id) =>
|
||||
Database.transaction(async (tx) => {
|
||||
return tx
|
||||
Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(AccountTable)
|
||||
.where(eq(AccountTable.id, id))
|
||||
.execute()
|
||||
.then((rows) => rows[0])
|
||||
}),
|
||||
.then((rows) => rows[0]),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export namespace Provider {
|
||||
}),
|
||||
async ({ provider }) => {
|
||||
Actor.assertAdmin()
|
||||
return Database.transaction((tx) =>
|
||||
return Database.use((tx) =>
|
||||
tx
|
||||
.delete(ProviderTable)
|
||||
.where(and(eq(ProviderTable.provider, provider), eq(ProviderTable.workspaceID, Actor.workspace()))),
|
||||
|
||||
Vendored
+94
-90
@@ -6,126 +6,130 @@
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
ADMIN_SECRET: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AUTH_API_URL: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_ACCESS_KEY_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_SECRET_ACCESS_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
Database: {
|
||||
database: string
|
||||
host: string
|
||||
password: string
|
||||
port: number
|
||||
type: "sst.sst.Linkable"
|
||||
username: string
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
Desktop: {
|
||||
type: "sst.cloudflare.StaticSite"
|
||||
url: string
|
||||
"Desktop": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
EMAILOCTOPUS_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_APP_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Enterprise": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
GITHUB_APP_PRIVATE_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_ID_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_SECRET_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GOOGLE_CLIENT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
HONEYCOMB_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2AccessKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2SecretKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_SECRET_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_WEBHOOK_SECRET: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Web: {
|
||||
type: "sst.cloudflare.Astro"
|
||||
url: string
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS1: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
ZEN_MODELS2: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS3: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS4: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types"
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
Api: cloudflare.Service
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
ConsoleData: cloudflare.R2Bucket
|
||||
EnterpriseStorage: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"GatewayKv": cloudflare.KVNamespace
|
||||
"LogProcessor": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -194,7 +194,7 @@ export default {
|
||||
// Get workspace
|
||||
await Actor.provide("account", { accountID, email }, async () => {
|
||||
await User.joinInvitedWorkspaces()
|
||||
const workspaces = await Database.transaction(async (tx) =>
|
||||
const workspaces = await Database.use((tx) =>
|
||||
tx
|
||||
.select({ id: WorkspaceTable.id })
|
||||
.from(WorkspaceTable)
|
||||
|
||||
+94
-90
@@ -6,126 +6,130 @@
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
ADMIN_SECRET: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AUTH_API_URL: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_ACCESS_KEY_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_SECRET_ACCESS_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
Database: {
|
||||
database: string
|
||||
host: string
|
||||
password: string
|
||||
port: number
|
||||
type: "sst.sst.Linkable"
|
||||
username: string
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
Desktop: {
|
||||
type: "sst.cloudflare.StaticSite"
|
||||
url: string
|
||||
"Desktop": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
EMAILOCTOPUS_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_APP_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Enterprise": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
GITHUB_APP_PRIVATE_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_ID_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_SECRET_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GOOGLE_CLIENT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
HONEYCOMB_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2AccessKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2SecretKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_SECRET_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_WEBHOOK_SECRET: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Web: {
|
||||
type: "sst.cloudflare.Astro"
|
||||
url: string
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS1: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
ZEN_MODELS2: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS3: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS4: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types"
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
Api: cloudflare.Service
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
ConsoleData: cloudflare.R2Bucket
|
||||
EnterpriseStorage: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"GatewayKv": cloudflare.KVNamespace
|
||||
"LogProcessor": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -2,8 +2,8 @@ import type { KVNamespaceListOptions, KVNamespaceListResult, KVNamespacePutOptio
|
||||
import { Resource as ResourceBase } from "sst"
|
||||
import Cloudflare from "cloudflare"
|
||||
|
||||
export const waitUntil = async (fn: () => Promise<void>) => {
|
||||
await fn()
|
||||
export const waitUntil = async (promise: Promise<any>) => {
|
||||
await promise
|
||||
}
|
||||
|
||||
export const Resource = new Proxy(
|
||||
|
||||
+94
-90
@@ -6,126 +6,130 @@
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
ADMIN_SECRET: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AUTH_API_URL: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_ACCESS_KEY_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_SECRET_ACCESS_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
Database: {
|
||||
database: string
|
||||
host: string
|
||||
password: string
|
||||
port: number
|
||||
type: "sst.sst.Linkable"
|
||||
username: string
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
Desktop: {
|
||||
type: "sst.cloudflare.StaticSite"
|
||||
url: string
|
||||
"Desktop": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
EMAILOCTOPUS_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_APP_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Enterprise": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
GITHUB_APP_PRIVATE_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_ID_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_SECRET_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GOOGLE_CLIENT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
HONEYCOMB_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2AccessKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2SecretKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_SECRET_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_WEBHOOK_SECRET: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Web: {
|
||||
type: "sst.cloudflare.Astro"
|
||||
url: string
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS1: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
ZEN_MODELS2: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS3: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS4: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types"
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
Api: cloudflare.Service
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
ConsoleData: cloudflare.R2Bucket
|
||||
EnterpriseStorage: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"GatewayKv": cloudflare.KVNamespace
|
||||
"LogProcessor": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -30,6 +30,7 @@ import { useSync } from "@/context/sync"
|
||||
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"
|
||||
|
||||
export default function Page() {
|
||||
const layout = useLayout()
|
||||
@@ -123,7 +124,6 @@ export default function Page() {
|
||||
const handleTabClick = async (tab: string) => {
|
||||
if (store.clickTimer) {
|
||||
resetClickTimer()
|
||||
// local.file.update(file.path, { ...file, pinned: true })
|
||||
} else {
|
||||
if (tab.startsWith("file://")) {
|
||||
local.file.open(tab.replace("file://", ""))
|
||||
@@ -355,8 +355,15 @@ export default function Page() {
|
||||
classes={{
|
||||
root: "pb-20 flex-1 min-w-0",
|
||||
content: "pb-20",
|
||||
container: "w-full " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
|
||||
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>
|
||||
@@ -405,6 +412,7 @@ export default function Page() {
|
||||
container: "px-6",
|
||||
}}
|
||||
diffs={session.diffs()}
|
||||
diffComponent={Diff}
|
||||
actions={
|
||||
<Tooltip value="Open in tab">
|
||||
<IconButton
|
||||
@@ -436,6 +444,7 @@ export default function Page() {
|
||||
container: "px-6",
|
||||
}}
|
||||
diffs={session.diffs()}
|
||||
diffComponent={Diff}
|
||||
split
|
||||
/>
|
||||
</div>
|
||||
|
||||
Vendored
+4
-2
@@ -2,7 +2,9 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/// <reference types="vite/client" />
|
||||
interface ImportMetaEnv {}
|
||||
interface ImportMetaEnv {
|
||||
|
||||
}
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -18,4 +18,7 @@ export default defineConfig({
|
||||
build: {
|
||||
target: "esnext",
|
||||
},
|
||||
worker: {
|
||||
format: "es",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { FileDiff, Message, Model, Part, Session, SessionStatus } from "@opencode-ai/sdk"
|
||||
import { fn } from "@opencode-ai/util/fn"
|
||||
import { iife } from "@opencode-ai/util/iife"
|
||||
import { Identifier } from "@opencode-ai/util/identifier"
|
||||
import z from "zod"
|
||||
import { Storage } from "./storage"
|
||||
import { Binary } from "@opencode-ai/util/binary"
|
||||
|
||||
export namespace Share {
|
||||
export const Info = z.object({
|
||||
@@ -37,15 +39,15 @@ export namespace Share {
|
||||
export type Data = z.infer<typeof Data>
|
||||
|
||||
export const create = fn(z.object({ sessionID: z.string() }), async (body) => {
|
||||
const isTest = process.env.NODE_ENV === "test" || body.sessionID.startsWith("test_")
|
||||
const info: Info = {
|
||||
id: body.sessionID.slice(-8),
|
||||
id: (isTest ? "test_" : "") + body.sessionID.slice(-8),
|
||||
sessionID: body.sessionID,
|
||||
secret: crypto.randomUUID(),
|
||||
}
|
||||
const exists = await get(info.id)
|
||||
if (exists) throw new Errors.AlreadyExists(info.id)
|
||||
await Storage.write(["share", info.id], info)
|
||||
console.log("created share", info.id)
|
||||
return info
|
||||
})
|
||||
|
||||
@@ -58,30 +60,77 @@ export namespace Share {
|
||||
if (!share) throw new Errors.NotFound(body.id)
|
||||
if (share.secret !== body.secret) throw new Errors.InvalidSecret(body.id)
|
||||
await Storage.remove(["share", body.id])
|
||||
const list = await Storage.list(["share_data", body.id])
|
||||
const list = await Storage.list({ prefix: ["share_data", body.id] })
|
||||
for (const item of list) {
|
||||
await Storage.remove(item)
|
||||
}
|
||||
})
|
||||
|
||||
export async function data(id: string) {
|
||||
const list = await Storage.list(["share_data", id])
|
||||
const promises = []
|
||||
for (const item of list) {
|
||||
promises.push(
|
||||
iife(async () => {
|
||||
const [, , type] = item
|
||||
return {
|
||||
type: type as any,
|
||||
data: await Storage.read<any>(item),
|
||||
} as Data
|
||||
}),
|
||||
)
|
||||
}
|
||||
return await Promise.all(promises)
|
||||
export const sync = fn(
|
||||
z.object({
|
||||
share: Info.pick({ id: true, secret: true }),
|
||||
data: Data.array(),
|
||||
}),
|
||||
async (input) => {
|
||||
const share = await get(input.share.id)
|
||||
if (!share) throw new Errors.NotFound(input.share.id)
|
||||
if (share.secret !== input.share.secret) throw new Errors.InvalidSecret(input.share.id)
|
||||
await Storage.write(["share_event", input.share.id, Identifier.descending()], input.data)
|
||||
},
|
||||
)
|
||||
|
||||
type Compaction = {
|
||||
event?: string
|
||||
data: Data[]
|
||||
}
|
||||
|
||||
export const sync = fn(
|
||||
export async function data(shareID: string) {
|
||||
console.log("reading compaction")
|
||||
const compaction: Compaction = (await Storage.read<Compaction>(["share_compaction", shareID])) ?? {
|
||||
data: [],
|
||||
event: undefined,
|
||||
}
|
||||
console.log("reading pending events")
|
||||
const list = await Storage.list({
|
||||
prefix: ["share_event", shareID],
|
||||
before: compaction.event,
|
||||
}).then((x) => x.toReversed())
|
||||
|
||||
console.log("compacting", list.length)
|
||||
|
||||
if (list.length > 0) {
|
||||
const data = await Promise.all(list.map(async (event) => await Storage.read<Data[]>(event))).then((x) => x.flat())
|
||||
for (const item of data) {
|
||||
if (!item) continue
|
||||
const key = (item: Data) => {
|
||||
switch (item.type) {
|
||||
case "session":
|
||||
return "session"
|
||||
case "message":
|
||||
return `message/${item.data.id}`
|
||||
case "part":
|
||||
return `${item.data.messageID}/${item.data.id}`
|
||||
case "session_diff":
|
||||
return "session_diff"
|
||||
case "model":
|
||||
return "model"
|
||||
}
|
||||
}
|
||||
const id = key(item)
|
||||
const result = Binary.search(compaction.data, id, key)
|
||||
if (result.found) {
|
||||
compaction.data[result.index] = item
|
||||
} else {
|
||||
compaction.data.splice(result.index, 0, item)
|
||||
}
|
||||
}
|
||||
compaction.event = list.at(-1)?.at(-1)
|
||||
await Storage.write(["share_compaction", shareID], compaction)
|
||||
}
|
||||
return compaction.data
|
||||
}
|
||||
|
||||
export const syncOld = fn(
|
||||
z.object({
|
||||
share: Info.pick({ id: true, secret: true }),
|
||||
data: Data.array(),
|
||||
@@ -98,15 +147,16 @@ export namespace Share {
|
||||
case "session":
|
||||
await Storage.write(["share_data", input.share.id, "session"], item.data)
|
||||
break
|
||||
case "message":
|
||||
await Storage.write(["share_data", input.share.id, "message", item.data.id], item.data)
|
||||
case "message": {
|
||||
const data = item.data as Message
|
||||
await Storage.write(["share_data", input.share.id, "message", data.id], item.data)
|
||||
break
|
||||
case "part":
|
||||
await Storage.write(
|
||||
["share_data", input.share.id, "part", item.data.messageID, item.data.id],
|
||||
item.data,
|
||||
)
|
||||
}
|
||||
case "part": {
|
||||
const data = item.data as Part
|
||||
await Storage.write(["share_data", input.share.id, "part", data.messageID, data.id], item.data)
|
||||
break
|
||||
}
|
||||
case "session_diff":
|
||||
await Storage.write(["share_data", input.share.id, "session_diff"], item.data)
|
||||
break
|
||||
|
||||
@@ -6,7 +6,7 @@ export namespace Storage {
|
||||
read(path: string): Promise<string | undefined>
|
||||
write(path: string, value: string): Promise<void>
|
||||
remove(path: string): Promise<void>
|
||||
list(prefix: string): Promise<string[]>
|
||||
list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise<string[]>
|
||||
}
|
||||
|
||||
function createAdapter(client: AwsClient, endpoint: string, bucket: string): Adapter {
|
||||
@@ -37,8 +37,14 @@ export namespace Storage {
|
||||
if (!response.ok) throw new Error(`Failed to remove ${path}: ${response.status}`)
|
||||
},
|
||||
|
||||
async list(prefix: string): Promise<string[]> {
|
||||
async list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise<string[]> {
|
||||
const prefix = options?.prefix || ""
|
||||
const params = new URLSearchParams({ "list-type": "2", prefix })
|
||||
if (options?.limit) params.set("max-keys", options.limit.toString())
|
||||
if (options?.after) {
|
||||
const afterPath = prefix + options.after + ".json"
|
||||
params.set("start-after", afterPath)
|
||||
}
|
||||
const response = await client.fetch(`${base}?${params}`)
|
||||
if (!response.ok) throw new Error(`Failed to list ${prefix}: ${response.status}`)
|
||||
const xml = await response.text()
|
||||
@@ -48,6 +54,10 @@ export namespace Storage {
|
||||
while ((match = regex.exec(xml)) !== null) {
|
||||
keys.push(match[1])
|
||||
}
|
||||
if (options?.before) {
|
||||
const beforePath = prefix + options.before + ".json"
|
||||
return keys.filter((key) => key < beforePath)
|
||||
}
|
||||
return keys
|
||||
},
|
||||
}
|
||||
@@ -98,9 +108,14 @@ export namespace Storage {
|
||||
return adapter().remove(resolve(key))
|
||||
}
|
||||
|
||||
export async function list(prefix: string[]) {
|
||||
const p = prefix.join("/") + (prefix.length ? "/" : "")
|
||||
const result = await adapter().list(p)
|
||||
export async function list(options?: { prefix?: string[]; limit?: number; after?: string; before?: string }) {
|
||||
const p = options?.prefix ? options.prefix.join("/") + (options.prefix.length ? "/" : "") : ""
|
||||
const result = await adapter().list({
|
||||
prefix: p,
|
||||
limit: options?.limit,
|
||||
after: options?.after,
|
||||
before: options?.before,
|
||||
})
|
||||
return result.map((x) => x.replace(/\.json$/, "").split("/"))
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ import z from "zod"
|
||||
import NotFound from "../[...404]"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
import { Diff } from "@opencode-ai/ui/diff-ssr"
|
||||
import { clientOnly } from "@solidjs/start"
|
||||
|
||||
const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
|
||||
|
||||
const SessionDataMissingError = NamedError.create(
|
||||
"SessionDataMissingError",
|
||||
@@ -230,6 +234,7 @@ export default function () {
|
||||
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
|
||||
container: "px-4",
|
||||
}}
|
||||
diffComponent={ClientOnlyDiff}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
@@ -279,7 +284,8 @@ export default function () {
|
||||
classList={{
|
||||
"w-full flex justify-start items-start min-w-0": true,
|
||||
"max-w-146 mx-auto px-6": wide(),
|
||||
"pr-6 pl-18": !wide(),
|
||||
"pr-6 pl-18": !wide() && messages().length > 1,
|
||||
"px-6": !wide() && messages().length === 1,
|
||||
}}
|
||||
>
|
||||
{title()}
|
||||
@@ -297,8 +303,11 @@ export default function () {
|
||||
classes={{
|
||||
root: "grow",
|
||||
content: "flex flex-col justify-between items-start",
|
||||
container: "w-full pb-20 " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
|
||||
container:
|
||||
"w-full pb-20 " +
|
||||
(wide() ? "max-w-146 mx-auto px-6" : messages().length > 1 ? "pr-6 pl-18" : "px-6"),
|
||||
}}
|
||||
diffComponent={ClientOnlyDiff}
|
||||
>
|
||||
<div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
|
||||
<Logo class="w-58.5 opacity-12" />
|
||||
@@ -311,6 +320,7 @@ export default function () {
|
||||
<SessionReview
|
||||
class="@4xl:hidden"
|
||||
diffs={diffs()}
|
||||
diffComponent={Diff}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
@@ -318,9 +328,10 @@ export default function () {
|
||||
}}
|
||||
/>
|
||||
<SessionReview
|
||||
class="hidden @4xl:flex"
|
||||
split
|
||||
class="hidden @4xl:flex"
|
||||
diffs={splitDiffs()}
|
||||
diffComponent={Diff}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
@@ -352,6 +363,7 @@ export default function () {
|
||||
<div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
|
||||
<SessionReview
|
||||
diffs={diffs()}
|
||||
diffComponent={Diff}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-4",
|
||||
|
||||
Vendored
+94
-90
@@ -6,126 +6,130 @@
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
ADMIN_SECRET: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AUTH_API_URL: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_ACCESS_KEY_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_SECRET_ACCESS_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
Database: {
|
||||
database: string
|
||||
host: string
|
||||
password: string
|
||||
port: number
|
||||
type: "sst.sst.Linkable"
|
||||
username: string
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
Desktop: {
|
||||
type: "sst.cloudflare.StaticSite"
|
||||
url: string
|
||||
"Desktop": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
EMAILOCTOPUS_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_APP_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Enterprise": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
GITHUB_APP_PRIVATE_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_ID_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_SECRET_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GOOGLE_CLIENT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
HONEYCOMB_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2AccessKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2SecretKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_SECRET_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_WEBHOOK_SECRET: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Web: {
|
||||
type: "sst.cloudflare.Astro"
|
||||
url: string
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS1: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
ZEN_MODELS2: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS3: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS4: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types"
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
Api: cloudflare.Service
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
ConsoleData: cloudflare.R2Bucket
|
||||
EnterpriseStorage: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"GatewayKv": cloudflare.KVNamespace
|
||||
"LogProcessor": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Share } from "./src/core/share"
|
||||
import { Storage } from "./src/core/storage"
|
||||
|
||||
async function test() {
|
||||
const shareInfo = await Share.create({ sessionID: "test-debug-" + Date.now() })
|
||||
|
||||
const batch1: Share.Data[] = [
|
||||
{ type: "part", data: { id: "part1", sessionID: "session1", messageID: "msg1", type: "text", text: "Hello" } },
|
||||
]
|
||||
|
||||
const batch2: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID: "session1", messageID: "msg1", type: "text", text: "Hello Updated" },
|
||||
},
|
||||
]
|
||||
|
||||
await Share.sync({
|
||||
share: { id: shareInfo.id, secret: shareInfo.secret },
|
||||
data: batch1,
|
||||
})
|
||||
|
||||
await Share.sync({
|
||||
share: { id: shareInfo.id, secret: shareInfo.secret },
|
||||
data: batch2,
|
||||
})
|
||||
|
||||
const events = await Storage.list({ prefix: ["share_event", shareInfo.id] })
|
||||
console.log("Events (raw):", events)
|
||||
console.log("Events (reversed):", events.toReversed())
|
||||
|
||||
for (const event of events.toReversed()) {
|
||||
const data = await Storage.read(event)
|
||||
console.log("Event data (reversed order):", event, data)
|
||||
}
|
||||
|
||||
await Share.remove({ id: shareInfo.id, secret: shareInfo.secret })
|
||||
}
|
||||
|
||||
test()
|
||||
@@ -0,0 +1,262 @@
|
||||
import { describe, expect, test, afterAll } from "bun:test"
|
||||
import { Share } from "../../src/core/share"
|
||||
import { Storage } from "../../src/core/storage"
|
||||
import { Identifier } from "@opencode-ai/util/identifier"
|
||||
|
||||
describe.concurrent("core.share", () => {
|
||||
test("should create a share", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
expect(share.sessionID).toBe(sessionID)
|
||||
expect(share.secret).toBeDefined()
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
|
||||
test("should sync data to a share", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
|
||||
},
|
||||
]
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data,
|
||||
})
|
||||
|
||||
const events = await Storage.list({ prefix: ["share_event", share.id] })
|
||||
expect(events.length).toBe(1)
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
|
||||
test("should sync multiple batches of data", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data1: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
|
||||
},
|
||||
]
|
||||
|
||||
const data2: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part2", sessionID, messageID: "msg1", type: "text", text: "World" },
|
||||
},
|
||||
]
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data: data1,
|
||||
})
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data: data2,
|
||||
})
|
||||
|
||||
const events = await Storage.list({ prefix: ["share_event", share.id] })
|
||||
expect(events.length).toBe(2)
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
|
||||
test("should retrieve synced data", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
|
||||
},
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part2", sessionID, messageID: "msg1", type: "text", text: "World" },
|
||||
},
|
||||
]
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data,
|
||||
})
|
||||
|
||||
const result = await Share.data(share.id)
|
||||
|
||||
expect(result.length).toBe(2)
|
||||
expect(result[0].type).toBe("part")
|
||||
expect(result[1].type).toBe("part")
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
|
||||
test("should retrieve data from multiple syncs", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data1: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
|
||||
},
|
||||
]
|
||||
|
||||
const data2: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part2", sessionID, messageID: "msg2", type: "text", text: "World" },
|
||||
},
|
||||
]
|
||||
|
||||
const data3: Share.Data[] = [
|
||||
{ type: "part", data: { id: "part3", sessionID, messageID: "msg3", type: "text", text: "!" } },
|
||||
]
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data: data1,
|
||||
})
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data: data2,
|
||||
})
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data: data3,
|
||||
})
|
||||
|
||||
const result = await Share.data(share.id)
|
||||
|
||||
expect(result.length).toBe(3)
|
||||
const parts = result.filter((d) => d.type === "part")
|
||||
expect(parts.length).toBe(3)
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
|
||||
test("should return latest data when syncing duplicate parts", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data1: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
|
||||
},
|
||||
]
|
||||
|
||||
const data2: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello Updated" },
|
||||
},
|
||||
]
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data: data1,
|
||||
})
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data: data2,
|
||||
})
|
||||
|
||||
const result = await Share.data(share.id)
|
||||
|
||||
expect(result.length).toBe(1)
|
||||
const [first] = result
|
||||
expect(first.type).toBe("part")
|
||||
expect(first.type === "part" && first.data.type === "text" && first.data.text).toBe("Hello Updated")
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
|
||||
test("should return empty array for share with no data", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const result = await Share.data(share.id)
|
||||
|
||||
expect(result).toEqual([])
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
|
||||
test("should throw error for invalid secret", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Test" },
|
||||
},
|
||||
]
|
||||
|
||||
expect(async () => {
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: "invalid-secret" },
|
||||
data,
|
||||
})
|
||||
}).toThrow()
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
|
||||
test("should throw error for non-existent share", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const data: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Test" },
|
||||
},
|
||||
]
|
||||
|
||||
expect(async () => {
|
||||
await Share.sync({
|
||||
share: { id: "non-existent-id", secret: "some-secret" },
|
||||
data,
|
||||
})
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
test("should handle different data types", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data: Share.Data[] = [
|
||||
{ type: "session", data: { id: sessionID, status: "running" } as any },
|
||||
{ type: "message", data: { id: "msg1", sessionID } as any },
|
||||
{
|
||||
type: "part",
|
||||
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
|
||||
},
|
||||
]
|
||||
|
||||
await Share.sync({
|
||||
share: { id: share.id, secret: share.secret },
|
||||
data,
|
||||
})
|
||||
|
||||
const result = await Share.data(share.id)
|
||||
|
||||
expect(result.length).toBe(3)
|
||||
expect(result.some((d) => d.type === "session")).toBe(true)
|
||||
expect(result.some((d) => d.type === "message")).toBe(true)
|
||||
expect(result.some((d) => d.type === "part")).toBe(true)
|
||||
|
||||
await Share.remove({ id: share.id, secret: share.secret })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, test, afterAll } from "bun:test"
|
||||
import { Storage } from "../../src/core/storage"
|
||||
|
||||
describe("core.storage", () => {
|
||||
test("should list files with after and before range", async () => {
|
||||
await Storage.write(["test", "users", "user1"], { name: "user1" })
|
||||
await Storage.write(["test", "users", "user2"], { name: "user2" })
|
||||
await Storage.write(["test", "users", "user3"], { name: "user3" })
|
||||
await Storage.write(["test", "users", "user4"], { name: "user4" })
|
||||
await Storage.write(["test", "users", "user5"], { name: "user5" })
|
||||
|
||||
const result = await Storage.list({ prefix: ["test", "users"], after: "user2", before: "user4" })
|
||||
|
||||
expect(result).toEqual([["test", "users", "user3"]])
|
||||
})
|
||||
|
||||
test("should list files with after only", async () => {
|
||||
const result = await Storage.list({ prefix: ["test", "users"], after: "user3" })
|
||||
|
||||
expect(result).toEqual([
|
||||
["test", "users", "user4"],
|
||||
["test", "users", "user5"],
|
||||
])
|
||||
})
|
||||
|
||||
test("should list files with limit", async () => {
|
||||
const result = await Storage.list({ prefix: ["test", "users"], limit: 3 })
|
||||
|
||||
expect(result).toEqual([
|
||||
["test", "users", "user1"],
|
||||
["test", "users", "user2"],
|
||||
["test", "users", "user3"],
|
||||
])
|
||||
})
|
||||
|
||||
test("should list all files without prefix", async () => {
|
||||
const result = await Storage.list()
|
||||
|
||||
expect(result.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test("should list all files with prefix", async () => {
|
||||
const result = await Storage.list({ prefix: ["test", "users"] })
|
||||
|
||||
expect(result).toEqual([
|
||||
["test", "users", "user1"],
|
||||
["test", "users", "user2"],
|
||||
["test", "users", "user3"],
|
||||
["test", "users", "user4"],
|
||||
["test", "users", "user5"],
|
||||
])
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const testFiles = await Storage.list({ prefix: ["test"] })
|
||||
|
||||
for (const file of testFiles) {
|
||||
await Storage.remove(file)
|
||||
}
|
||||
|
||||
const remainingFiles = await Storage.list({ prefix: ["test"] })
|
||||
expect(remainingFiles).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -23,4 +23,7 @@ export default defineConfig({
|
||||
host: "0.0.0.0",
|
||||
allowedHosts: true,
|
||||
},
|
||||
worker: {
|
||||
format: "es",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The AI coding agent built for the terminal"
|
||||
version = "1.0.124"
|
||||
version = "1.0.129"
|
||||
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.124/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/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.124/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.124/opencode-linux-arm64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/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.124/opencode-linux-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/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.124/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.129/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
Vendored
+94
-90
@@ -6,126 +6,130 @@
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
ADMIN_SECRET: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AUTH_API_URL: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_ACCESS_KEY_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
AWS_SES_SECRET_ACCESS_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_API_TOKEN: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Console: {
|
||||
type: "sst.cloudflare.SolidStart"
|
||||
url: string
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
Database: {
|
||||
database: string
|
||||
host: string
|
||||
password: string
|
||||
port: number
|
||||
type: "sst.sst.Linkable"
|
||||
username: string
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
Desktop: {
|
||||
type: "sst.cloudflare.StaticSite"
|
||||
url: string
|
||||
"Desktop": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
EMAILOCTOPUS_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_APP_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Enterprise": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
GITHUB_APP_PRIVATE_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_ID_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GITHUB_CLIENT_SECRET_CONSOLE: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
GOOGLE_CLIENT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
HONEYCOMB_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2AccessKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
R2SecretKey: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_SECRET_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
STRIPE_WEBHOOK_SECRET: {
|
||||
type: "sst.sst.Linkable"
|
||||
value: string
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
Web: {
|
||||
type: "sst.cloudflare.Astro"
|
||||
url: string
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS1: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
ZEN_MODELS2: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS3: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
ZEN_MODELS4: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types"
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
Api: cloudflare.Service
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
ConsoleData: cloudflare.R2Bucket
|
||||
EnterpriseStorage: cloudflare.R2Bucket
|
||||
GatewayKv: cloudflare.KVNamespace
|
||||
LogProcessor: cloudflare.Service
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"GatewayKv": cloudflare.KVNamespace
|
||||
"LogProcessor": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -64,8 +64,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.2.8",
|
||||
"@opentui/core": "0.1.54",
|
||||
"@opentui/solid": "0.1.54",
|
||||
"@opentui/core": "0.1.55",
|
||||
"@opentui/solid": "0.1.55",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -7,19 +7,35 @@ import { GlobalBus } from "./global"
|
||||
export namespace Bus {
|
||||
const log = Log.create({ service: "bus" })
|
||||
type Subscription = (event: any) => void
|
||||
|
||||
const state = Instance.state(() => {
|
||||
const subscriptions = new Map<any, Subscription[]>()
|
||||
|
||||
return {
|
||||
subscriptions,
|
||||
}
|
||||
})
|
||||
const disposedEventType = "server.instance.disposed"
|
||||
|
||||
export type EventDefinition = ReturnType<typeof event>
|
||||
|
||||
const registry = new Map<string, EventDefinition>()
|
||||
|
||||
const state = Instance.state(
|
||||
() => {
|
||||
const subscriptions = new Map<any, Subscription[]>()
|
||||
|
||||
return {
|
||||
subscriptions,
|
||||
}
|
||||
},
|
||||
async (entry) => {
|
||||
const wildcard = entry.subscriptions.get("*")
|
||||
if (!wildcard) return
|
||||
const event = {
|
||||
type: disposedEventType,
|
||||
properties: {
|
||||
directory: Instance.directory,
|
||||
},
|
||||
}
|
||||
for (const sub of [...wildcard]) {
|
||||
sub(event)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export function event<Type extends string, Properties extends ZodType>(type: Type, properties: Properties) {
|
||||
const result = {
|
||||
type,
|
||||
@@ -29,6 +45,13 @@ export namespace Bus {
|
||||
return result
|
||||
}
|
||||
|
||||
export const InstanceDisposed = event(
|
||||
disposedEventType,
|
||||
z.object({
|
||||
directory: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
export function payloads() {
|
||||
return z
|
||||
.discriminatedUnion(
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ModelsDev } from "../../provider/models"
|
||||
import { map, pipe, sortBy, values } from "remeda"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { Config } from "../../config/config"
|
||||
import { Global } from "../../global"
|
||||
import { Plugin } from "../../plugin"
|
||||
import { Instance } from "../../project/instance"
|
||||
@@ -103,7 +104,22 @@ export const AuthLoginCommand = cmd({
|
||||
return
|
||||
}
|
||||
await ModelsDev.refresh().catch(() => {})
|
||||
const providers = await ModelsDev.get()
|
||||
|
||||
const config = await Config.get()
|
||||
|
||||
const disabled = new Set(config.disabled_providers ?? [])
|
||||
const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
|
||||
|
||||
const providers = await ModelsDev.get().then((x) => {
|
||||
const filtered: Record<string, (typeof x)[string]> = {}
|
||||
for (const [key, value] of Object.entries(x)) {
|
||||
if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
|
||||
filtered[key] = value
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
})
|
||||
|
||||
const priority: Record<string, number> = {
|
||||
opencode: 0,
|
||||
anthropic: 1,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { CommandModule } from "yargs"
|
||||
|
||||
export function cmd<T, U>(input: CommandModule<T, U>) {
|
||||
type WithDoubleDash<T> = T & { "--"?: string[] }
|
||||
|
||||
export function cmd<T, U>(input: CommandModule<T, WithDoubleDash<U>>) {
|
||||
return input
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ export const RunCommand = cmd({
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
let message = args.message.join(" ")
|
||||
let message = [...args.message, ...(args["--"] || [])].join(" ")
|
||||
|
||||
const fileParts: any[] = []
|
||||
if (args.file) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { cmd } from "./cmd"
|
||||
import { Session } from "../../session"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { UI } from "../ui"
|
||||
import { Locale } from "../../util/locale"
|
||||
import { EOL } from "os"
|
||||
|
||||
export const SessionCommand = cmd({
|
||||
command: "session",
|
||||
describe: "manage sessions",
|
||||
builder: (yargs: Argv) => yargs.command(SessionListCommand).demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
export const SessionListCommand = cmd({
|
||||
command: "list",
|
||||
describe: "list sessions",
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
.option("max-count", {
|
||||
alias: "n",
|
||||
describe: "limit to N most recent sessions",
|
||||
type: "number",
|
||||
})
|
||||
.option("format", {
|
||||
describe: "output format",
|
||||
type: "string",
|
||||
choices: ["table", "json"],
|
||||
default: "table",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const sessions = []
|
||||
for await (const session of Session.list()) {
|
||||
if (!session.parentID) {
|
||||
sessions.push(session)
|
||||
}
|
||||
}
|
||||
|
||||
sessions.sort((a, b) => b.time.updated - a.time.updated)
|
||||
|
||||
const limitedSessions = args.maxCount ? sessions.slice(0, args.maxCount) : sessions
|
||||
|
||||
if (limitedSessions.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let output: string
|
||||
if (args.format === "json") {
|
||||
output = formatSessionJSON(limitedSessions)
|
||||
} else {
|
||||
output = formatSessionTable(limitedSessions)
|
||||
}
|
||||
|
||||
const shouldPaginate = process.stdout.isTTY && !args.maxCount && args.format === "table"
|
||||
|
||||
if (shouldPaginate) {
|
||||
const proc = Bun.spawn({
|
||||
cmd: ["less", "-R", "-S"],
|
||||
stdin: "pipe",
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
|
||||
proc.stdin.write(output)
|
||||
proc.stdin.end()
|
||||
await proc.exited
|
||||
} else {
|
||||
console.log(output)
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
function formatSessionTable(sessions: Session.Info[]): string {
|
||||
const lines: string[] = []
|
||||
|
||||
const maxIdWidth = Math.max(20, ...sessions.map((s) => s.id.length))
|
||||
const maxTitleWidth = Math.max(25, ...sessions.map((s) => s.title.length))
|
||||
|
||||
const header = `Session ID${" ".repeat(maxIdWidth - 10)} Title${" ".repeat(maxTitleWidth - 5)} Updated`
|
||||
lines.push(header)
|
||||
lines.push("─".repeat(header.length))
|
||||
for (const session of sessions) {
|
||||
const truncatedTitle = Locale.truncate(session.title, maxTitleWidth)
|
||||
const timeStr = Locale.todayTimeOrDateTime(session.time.updated)
|
||||
const line = `${session.id.padEnd(maxIdWidth)} ${truncatedTitle.padEnd(maxTitleWidth)} ${timeStr}`
|
||||
lines.push(line)
|
||||
}
|
||||
|
||||
return lines.join(EOL)
|
||||
}
|
||||
|
||||
function formatSessionJSON(sessions: Session.Info[]): string {
|
||||
const jsonData = sessions.map((session) => ({
|
||||
id: session.id,
|
||||
title: session.title,
|
||||
updated: session.time.updated,
|
||||
created: session.time.created,
|
||||
projectId: session.projectID,
|
||||
directory: session.directory,
|
||||
}))
|
||||
return JSON.stringify(jsonData, null, 2)
|
||||
}
|
||||
@@ -2,9 +2,10 @@ import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentu
|
||||
import { Clipboard } from "@tui/util/clipboard"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show } from "solid-js"
|
||||
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
|
||||
import { Installation } from "@/installation"
|
||||
import { Global } from "@/global"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { DialogProvider, useDialog } from "@tui/ui/dialog"
|
||||
import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
|
||||
import { SDKProvider, useSDK } from "@tui/context/sdk"
|
||||
@@ -197,6 +198,17 @@ function App() {
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => sync.status === "complete" && sync.data.provider.length === 0,
|
||||
(isEmpty, wasEmpty) => {
|
||||
// only trigger when we transition into an empty-provider state
|
||||
if (!isEmpty || wasEmpty) return
|
||||
dialog.replace(() => <DialogProviderList />)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
command.register(() => [
|
||||
{
|
||||
title: "Switch session",
|
||||
@@ -301,10 +313,11 @@ function App() {
|
||||
category: "System",
|
||||
},
|
||||
{
|
||||
title: `Switch to ${mode() === "dark" ? "light" : "dark"} mode`,
|
||||
title: "Toggle appearance",
|
||||
value: "theme.switch_mode",
|
||||
onSelect: () => {
|
||||
onSelect: (dialog) => {
|
||||
setMode(mode() === "dark" ? "light" : "dark")
|
||||
dialog.clear()
|
||||
},
|
||||
category: "System",
|
||||
},
|
||||
@@ -367,8 +380,9 @@ function App() {
|
||||
])
|
||||
|
||||
createEffect(() => {
|
||||
const providerID = local.model.current().providerID
|
||||
if (providerID === "openrouter" && !kv.get("openrouter_warning", false)) {
|
||||
const currentModel = local.model.current()
|
||||
if (!currentModel) return
|
||||
if (currentModel.providerID === "openrouter" && !kv.get("openrouter_warning", false)) {
|
||||
untrack(() => {
|
||||
DialogAlert.show(
|
||||
dialog,
|
||||
@@ -448,6 +462,10 @@ function App() {
|
||||
height={dimensions().height}
|
||||
backgroundColor={theme.background}
|
||||
onMouseUp={async () => {
|
||||
if (Flag.OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT) {
|
||||
renderer.clearSelection()
|
||||
return
|
||||
}
|
||||
const text = renderer.getSelection()?.getSelectedText()
|
||||
if (text && text.length > 0) {
|
||||
const base64 = Buffer.from(text).toString("base64")
|
||||
|
||||
@@ -22,6 +22,9 @@ import { TuiEvent } from "../../event"
|
||||
import { iife } from "@/util/iife"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { createColors, createFrames } from "../../ui/spinner.ts"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
|
||||
import { useToast } from "../../ui/toast"
|
||||
|
||||
export type PromptProps = {
|
||||
sessionID?: string
|
||||
@@ -50,12 +53,25 @@ export function Prompt(props: PromptProps) {
|
||||
const sdk = useSDK()
|
||||
const route = useRoute()
|
||||
const sync = useSync()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const status = createMemo(() => sync.data.session_status[props.sessionID ?? ""] ?? { type: "idle" })
|
||||
const history = usePromptHistory()
|
||||
const command = useCommandDialog()
|
||||
const renderer = useRenderer()
|
||||
const { theme, syntax } = useTheme()
|
||||
|
||||
function promptModelWarning() {
|
||||
toast.show({
|
||||
variant: "warning",
|
||||
message: "Connect a provider to send prompts",
|
||||
duration: 3000,
|
||||
})
|
||||
if (sync.data.provider.length === 0) {
|
||||
dialog.replace(() => <DialogProviderConnect />)
|
||||
}
|
||||
}
|
||||
|
||||
const textareaKeybindings = createMemo(() => {
|
||||
const newlineBindings = keybind.all.input_newline || []
|
||||
const submitBindings = keybind.all.input_submit || []
|
||||
@@ -388,6 +404,11 @@ export function Prompt(props: PromptProps) {
|
||||
if (props.disabled) return
|
||||
if (autocomplete.visible) return
|
||||
if (!store.prompt.input) return
|
||||
const selectedModel = local.model.current()
|
||||
if (!selectedModel) {
|
||||
promptModelWarning()
|
||||
return
|
||||
}
|
||||
const sessionID = props.sessionID
|
||||
? props.sessionID
|
||||
: await (async () => {
|
||||
@@ -424,8 +445,8 @@ export function Prompt(props: PromptProps) {
|
||||
body: {
|
||||
agent: local.agent.current().name,
|
||||
model: {
|
||||
providerID: local.model.current().providerID,
|
||||
modelID: local.model.current().modelID,
|
||||
providerID: selectedModel.providerID,
|
||||
modelID: selectedModel.modelID,
|
||||
},
|
||||
command: inputText,
|
||||
},
|
||||
@@ -448,7 +469,7 @@ export function Prompt(props: PromptProps) {
|
||||
command: command.slice(1),
|
||||
arguments: args.join(" "),
|
||||
agent: local.agent.current().name,
|
||||
model: `${local.model.current().providerID}/${local.model.current().modelID}`,
|
||||
model: `${selectedModel.providerID}/${selectedModel.modelID}`,
|
||||
messageID,
|
||||
},
|
||||
})
|
||||
@@ -458,10 +479,10 @@ export function Prompt(props: PromptProps) {
|
||||
id: sessionID,
|
||||
},
|
||||
body: {
|
||||
...local.model.current(),
|
||||
...selectedModel,
|
||||
messageID,
|
||||
agent: local.agent.current().name,
|
||||
model: local.model.current(),
|
||||
model: selectedModel,
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
|
||||
@@ -175,8 +175,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
const provider = sync.data.provider[0]
|
||||
const model = sync.data.provider_default[provider.id] ?? Object.values(provider.models)[0].id
|
||||
if (!provider) return undefined
|
||||
const defaultModel = sync.data.provider_default[provider.id]
|
||||
const firstModel = Object.values(provider.models)[0]
|
||||
const model = defaultModel ?? firstModel?.id
|
||||
if (!model) return undefined
|
||||
return {
|
||||
providerID: provider.id,
|
||||
modelID: model,
|
||||
@@ -185,11 +190,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
|
||||
const currentModel = createMemo(() => {
|
||||
const a = agent.current()
|
||||
return getFirstValidModel(
|
||||
() => modelStore.model[a.name],
|
||||
() => a.model,
|
||||
fallbackModel,
|
||||
)!
|
||||
return (
|
||||
getFirstValidModel(
|
||||
() => modelStore.model[a.name],
|
||||
() => a.model,
|
||||
fallbackModel,
|
||||
) ?? undefined
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -205,11 +212,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
},
|
||||
parsed: createMemo(() => {
|
||||
const value = currentModel()
|
||||
const provider = sync.data.provider.find((x) => x.id === value.providerID)!
|
||||
const model = provider.models[value.modelID]
|
||||
if (!value) {
|
||||
return {
|
||||
provider: "Connect a provider",
|
||||
model: "No provider selected",
|
||||
}
|
||||
}
|
||||
const provider = sync.data.provider.find((x) => x.id === value.providerID)
|
||||
const info = provider?.models[value.modelID]
|
||||
return {
|
||||
provider: provider.name ?? value.providerID,
|
||||
model: model.name ?? value.modelID,
|
||||
provider: provider?.name ?? value.providerID,
|
||||
model: info?.name ?? value.modelID,
|
||||
}
|
||||
}),
|
||||
cycle(direction: 1 | -1) {
|
||||
@@ -236,7 +249,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
return
|
||||
}
|
||||
const current = currentModel()
|
||||
let index = favorites.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID)
|
||||
let index = -1
|
||||
if (current) {
|
||||
index = favorites.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID)
|
||||
}
|
||||
if (index === -1) {
|
||||
index = direction === 1 ? 0 : favorites.length - 1
|
||||
} else {
|
||||
|
||||
@@ -161,7 +161,7 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
|
||||
|
||||
if (c.startsWith("#")) return RGBA.fromHex(c)
|
||||
|
||||
if (defs[c]) {
|
||||
if (defs[c] != null) {
|
||||
return resolveColor(defs[c])
|
||||
} else if (theme.theme[c as keyof ThemeColors] !== undefined) {
|
||||
return resolveColor(theme.theme[c as keyof ThemeColors]!)
|
||||
@@ -169,6 +169,9 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
|
||||
throw new Error(`Color reference "${c}" not found in defs or theme`)
|
||||
}
|
||||
}
|
||||
if (typeof c === "number") {
|
||||
return ansiToRgba(c)
|
||||
}
|
||||
return resolveColor(c[mode])
|
||||
}
|
||||
|
||||
@@ -203,6 +206,51 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
|
||||
} as Theme
|
||||
}
|
||||
|
||||
function ansiToRgba(code: number): RGBA {
|
||||
// Standard ANSI colors (0-15)
|
||||
if (code < 16) {
|
||||
const ansiColors = [
|
||||
"#000000", // Black
|
||||
"#800000", // Red
|
||||
"#008000", // Green
|
||||
"#808000", // Yellow
|
||||
"#000080", // Blue
|
||||
"#800080", // Magenta
|
||||
"#008080", // Cyan
|
||||
"#c0c0c0", // White
|
||||
"#808080", // Bright Black
|
||||
"#ff0000", // Bright Red
|
||||
"#00ff00", // Bright Green
|
||||
"#ffff00", // Bright Yellow
|
||||
"#0000ff", // Bright Blue
|
||||
"#ff00ff", // Bright Magenta
|
||||
"#00ffff", // Bright Cyan
|
||||
"#ffffff", // Bright White
|
||||
]
|
||||
return RGBA.fromHex(ansiColors[code] ?? "#000000")
|
||||
}
|
||||
|
||||
// 6x6x6 Color Cube (16-231)
|
||||
if (code < 232) {
|
||||
const index = code - 16
|
||||
const b = index % 6
|
||||
const g = Math.floor(index / 6) % 6
|
||||
const r = Math.floor(index / 36)
|
||||
|
||||
const val = (x: number) => (x === 0 ? 0 : x * 40 + 55)
|
||||
return RGBA.fromInts(val(r), val(g), val(b))
|
||||
}
|
||||
|
||||
// Grayscale Ramp (232-255)
|
||||
if (code < 256) {
|
||||
const gray = (code - 232) * 10 + 8
|
||||
return RGBA.fromInts(gray, gray, gray)
|
||||
}
|
||||
|
||||
// Fallback for invalid codes
|
||||
return RGBA.fromInts(0, 0, 0)
|
||||
}
|
||||
|
||||
export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||
name: "Theme",
|
||||
init: (props: { mode: "dark" | "light" }) => {
|
||||
|
||||
@@ -81,6 +81,8 @@ const context = createContext<{
|
||||
conceal: () => boolean
|
||||
showThinking: () => boolean
|
||||
showTimestamps: () => boolean
|
||||
usernameVisible: () => boolean
|
||||
showDetails: () => boolean
|
||||
diffWrapMode: () => "word" | "none"
|
||||
sync: ReturnType<typeof useSync>
|
||||
}>()
|
||||
@@ -114,6 +116,8 @@ export function Session() {
|
||||
const [conceal, setConceal] = createSignal(true)
|
||||
const [showThinking, setShowThinking] = createSignal(kv.get("thinking_visibility", true))
|
||||
const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
|
||||
const [usernameVisible, setUsernameVisible] = createSignal(kv.get("username_visible", true))
|
||||
const [showDetails, setShowDetails] = createSignal(kv.get("tool_details_visibility", true))
|
||||
const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word")
|
||||
|
||||
const wide = createMemo(() => dimensions().width > 120)
|
||||
@@ -269,13 +273,22 @@ export function Session() {
|
||||
keybind: "session_compact",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
const selectedModel = local.model.current()
|
||||
if (!selectedModel) {
|
||||
toast.show({
|
||||
variant: "warning",
|
||||
message: "Connect a provider to summarize this session",
|
||||
duration: 3000,
|
||||
})
|
||||
return
|
||||
}
|
||||
sdk.client.session.summarize({
|
||||
path: {
|
||||
id: route.sessionID,
|
||||
},
|
||||
body: {
|
||||
modelID: local.model.current().modelID,
|
||||
providerID: local.model.current().providerID,
|
||||
modelID: selectedModel.modelID,
|
||||
providerID: selectedModel.providerID,
|
||||
},
|
||||
})
|
||||
dialog.clear()
|
||||
@@ -408,6 +421,20 @@ export function Session() {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: usernameVisible() ? "Hide username" : "Show username",
|
||||
value: "session.username_visible.toggle",
|
||||
keybind: "username_toggle",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setUsernameVisible((prev) => {
|
||||
const next = !prev
|
||||
kv.set("username_visible", next)
|
||||
return next
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Toggle code concealment",
|
||||
value: "session.toggle.conceal",
|
||||
@@ -453,6 +480,17 @@ export function Session() {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: showDetails() ? "Hide tool details" : "Show tool details",
|
||||
value: "session.toggle.actions",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
const newValue = !showDetails()
|
||||
setShowDetails(newValue)
|
||||
kv.set("tool_details_visibility", newValue)
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Page up",
|
||||
value: "session.page.up",
|
||||
@@ -754,6 +792,8 @@ export function Session() {
|
||||
conceal,
|
||||
showThinking,
|
||||
showTimestamps,
|
||||
usernameVisible,
|
||||
showDetails,
|
||||
diffWrapMode,
|
||||
sync,
|
||||
}}
|
||||
@@ -917,13 +957,14 @@ function UserMessage(props: {
|
||||
pending?: string
|
||||
}) {
|
||||
const ctx = use()
|
||||
const local = useLocal()
|
||||
const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0])
|
||||
const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : [])))
|
||||
const sync = useSync()
|
||||
const { theme } = useTheme()
|
||||
const [hover, setHover] = createSignal(false)
|
||||
const queued = createMemo(() => props.pending && props.message.id > props.pending)
|
||||
const color = createMemo(() => (queued() ? theme.accent : theme.secondary))
|
||||
const color = createMemo(() => (queued() ? theme.accent : local.agent.color(props.message.agent)))
|
||||
|
||||
const compaction = createMemo(() => props.parts.find((x) => x.type === "compaction"))
|
||||
|
||||
@@ -972,7 +1013,7 @@ function UserMessage(props: {
|
||||
</box>
|
||||
</Show>
|
||||
<text fg={theme.textMuted}>
|
||||
{sync.data.config.username ?? "You"}{" "}
|
||||
{ctx.usernameVisible() ? `${sync.data.config.username ?? "You"} ` : "You"}{" "}
|
||||
<Show
|
||||
when={queued()}
|
||||
fallback={
|
||||
@@ -1078,7 +1119,11 @@ const PART_MAPPING = {
|
||||
function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: AssistantMessage }) {
|
||||
const { theme, subtleSyntax } = useTheme()
|
||||
const ctx = use()
|
||||
const content = createMemo(() => props.part.text.trim())
|
||||
const content = createMemo(() => {
|
||||
// Filter out redacted reasoning chunks from OpenRouter
|
||||
// OpenRouter sends encrypted reasoning data that appears as [REDACTED]
|
||||
return props.part.text.replace("[REDACTED]", "").trim()
|
||||
})
|
||||
return (
|
||||
<Show when={content() && ctx.showThinking()}>
|
||||
<box
|
||||
@@ -1128,9 +1173,21 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess
|
||||
|
||||
function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMessage }) {
|
||||
const { theme } = useTheme()
|
||||
const { showDetails } = use()
|
||||
const sync = useSync()
|
||||
const [margin, setMargin] = createSignal(0)
|
||||
const component = createMemo(() => {
|
||||
// Hide tool if showDetails is false and tool completed successfully
|
||||
// But always show if there's an error or permission is required
|
||||
const shouldHide =
|
||||
!showDetails() &&
|
||||
props.part.state.status === "completed" &&
|
||||
!sync.data.permission[props.message.sessionID]?.some((x) => x.callID === props.part.callID)
|
||||
|
||||
if (shouldHide) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const render = ToolRegistry.render(props.part.tool) ?? GenericTool
|
||||
|
||||
const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
|
||||
@@ -1327,7 +1384,13 @@ ToolRegistry.register<typeof WriteTool>({
|
||||
Wrote {props.input.filePath}
|
||||
</ToolTitle>
|
||||
<line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
|
||||
<code fg={theme.text} filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
|
||||
<code
|
||||
conceal={false}
|
||||
fg={theme.text}
|
||||
filetype={filetype(props.input.filePath!)}
|
||||
syntaxStyle={syntax()}
|
||||
content={code()}
|
||||
/>
|
||||
</line_number>
|
||||
<Show when={diagnostics().length}>
|
||||
<For each={diagnostics()}>
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Installation } from "@/installation"
|
||||
|
||||
export async function upgrade() {
|
||||
const config = await Config.global()
|
||||
const latest = await Installation.latest().catch(() => {})
|
||||
const method = await Installation.method()
|
||||
const latest = await Installation.latest(method).catch(() => {})
|
||||
if (!latest) return
|
||||
if (Installation.VERSION === latest) return
|
||||
|
||||
@@ -17,7 +18,6 @@ export async function upgrade() {
|
||||
return
|
||||
}
|
||||
|
||||
const method = await Installation.method()
|
||||
if (method === "unknown") return
|
||||
await Installation.upgrade(method, latest)
|
||||
.then(() => Bus.publish(Installation.Event.Updated, { version: latest }))
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import z from "zod"
|
||||
import { Config } from "../config/config"
|
||||
import { Instance } from "../project/instance"
|
||||
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
||||
import { Bus } from "../bus"
|
||||
import { Identifier } from "../id/id"
|
||||
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
||||
import PROMPT_REVIEW from "./template/review.txt"
|
||||
|
||||
export namespace Command {
|
||||
export const Default = {
|
||||
INIT: "init",
|
||||
} as const
|
||||
|
||||
export const Event = {
|
||||
Executed: Bus.event(
|
||||
"command.executed",
|
||||
@@ -36,10 +33,27 @@ export namespace Command {
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
export const Default = {
|
||||
INIT: "init",
|
||||
REVIEW: "review",
|
||||
} as const
|
||||
|
||||
const state = Instance.state(async () => {
|
||||
const cfg = await Config.get()
|
||||
|
||||
const result: Record<string, Info> = {}
|
||||
const result: Record<string, Info> = {
|
||||
[Default.INIT]: {
|
||||
name: Default.INIT,
|
||||
description: "create/update AGENTS.md",
|
||||
template: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
|
||||
},
|
||||
[Default.REVIEW]: {
|
||||
name: Default.REVIEW,
|
||||
description: "review changes [commit|branch|pr], defaults to uncommitted",
|
||||
template: PROMPT_REVIEW.replace("${path}", Instance.worktree),
|
||||
subtask: true,
|
||||
},
|
||||
}
|
||||
|
||||
for (const [name, command] of Object.entries(cfg.command ?? {})) {
|
||||
result[name] = {
|
||||
@@ -52,14 +66,6 @@ export namespace Command {
|
||||
}
|
||||
}
|
||||
|
||||
if (result[Default.INIT] === undefined) {
|
||||
result[Default.INIT] = {
|
||||
name: Default.INIT,
|
||||
description: "create/update AGENTS.md",
|
||||
template: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
You are a code reviewer. Your job is to review code changes and provide actionable feedback.
|
||||
|
||||
---
|
||||
|
||||
Input: $ARGUMENTS
|
||||
|
||||
---
|
||||
|
||||
## Determining What to Review
|
||||
|
||||
Based on the input provided, determine which type of review to perform:
|
||||
|
||||
1. **No arguments (default)**: Review all uncommitted changes
|
||||
- Run: `git diff` for unstaged changes
|
||||
- Run: `git diff --cached` for staged changes
|
||||
|
||||
2. **Commit hash** (40-char SHA or short hash): Review that specific commit
|
||||
- Run: `git show $ARGUMENTS`
|
||||
|
||||
3. **Branch name**: Compare current branch to the specified branch
|
||||
- Run: `git diff $ARGUMENTS...HEAD`
|
||||
|
||||
4. **PR URL or number** (contains "github.com" or "pull" or looks like a PR number): Review the pull request
|
||||
- Run: `gh pr view $ARGUMENTS` to get PR context
|
||||
- Run: `gh pr diff $ARGUMENTS` to get the diff
|
||||
|
||||
Use best judgement when processing input.
|
||||
|
||||
---
|
||||
|
||||
## What to Look For
|
||||
|
||||
**Bugs** - Your primary focus.
|
||||
- Logic errors, off-by-one mistakes, incorrect conditionals
|
||||
- Edge cases: null/empty inputs, error conditions, race conditions
|
||||
- Security issues: injection, auth bypass, data exposure
|
||||
- Broken error handling that swallows failures
|
||||
|
||||
**Structure** - Does the code fit the codebase?
|
||||
- Does it follow existing patterns and conventions?
|
||||
- Are there established abstractions it should use but doesn't?
|
||||
|
||||
**Performance** - Only flag if obviously problematic.
|
||||
- O(n²) on unbounded data, N+1 queries, blocking I/O on hot paths
|
||||
|
||||
## Before You Flag Something
|
||||
|
||||
Be certain. If you're going to call something a bug, you need to be confident it actually is one.
|
||||
|
||||
- Only review the changes - do not review pre-existing code that wasn't modified
|
||||
- Don't flag something as a bug if you're unsure - investigate first
|
||||
- Don't flag style preferences as issues
|
||||
- Don't invent hypothetical problems - if an edge case matters, explain the realistic scenario where it breaks
|
||||
- If you need more context to be sure, use the tools below to get it
|
||||
|
||||
## Tools
|
||||
|
||||
Use these to inform your review:
|
||||
|
||||
- **Explore agent** - Find how existing code handles similar problems. Check patterns, conventions, and prior art before claiming something doesn't fit.
|
||||
- **Exa Code Context** - Verify correct usage of libraries/APIs before flagging something as wrong.
|
||||
- **Exa Web Search** - Research best practices if you're unsure about a pattern.
|
||||
|
||||
If you're uncertain about something and can't verify it with these tools, say "I'm not sure about X" rather than flagging it as a definite issue.
|
||||
|
||||
## Tone and Approach
|
||||
|
||||
1. If there is a bug, be direct and clear about why it is a bug.
|
||||
2. You should clearly communicate severity of issues, do not claim issues are more severe than they actually are.
|
||||
3. Critiques should clearly and explicitly communicate the scenarios, environments, or inputs that are necessary for the bug to arise. The comment should immediately indicate that the issue's severity depends on these factors.
|
||||
4. Your tone should be matter-of-fact and not accusatory or overly positive. It should read as a helpful AI assistant suggestion without sounding too much like a human reviewer.
|
||||
5. Write in a manner that allows reader to quickly understand issue without reading too closely.
|
||||
6. AVOID flattery, do not give any comments that are not helpful to the reader. Avoid phrasing like "Great job ...", "Thanks for ...".
|
||||
@@ -398,6 +398,7 @@ export namespace Config {
|
||||
editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
|
||||
theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
|
||||
sidebar_toggle: z.string().optional().default("<leader>b").describe("Toggle sidebar"),
|
||||
username_toggle: z.string().optional().default("none").describe("Toggle username visibility"),
|
||||
status_view: z.string().optional().default("<leader>s").describe("View status"),
|
||||
session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
|
||||
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
|
||||
|
||||
@@ -276,11 +276,16 @@ export namespace File {
|
||||
const project = Instance.project
|
||||
let ignored = (_: string) => false
|
||||
if (project.vcs === "git") {
|
||||
const ig = ignore()
|
||||
const gitignore = Bun.file(path.join(Instance.worktree, ".gitignore"))
|
||||
if (await gitignore.exists()) {
|
||||
const ig = ignore().add(await gitignore.text())
|
||||
ignored = ig.ignores.bind(ig)
|
||||
ig.add(await gitignore.text())
|
||||
}
|
||||
const ignoreFile = Bun.file(path.join(Instance.worktree, ".ignore"))
|
||||
if (await ignoreFile.exists()) {
|
||||
ig.add(await ignoreFile.text())
|
||||
}
|
||||
ignored = ig.ignores.bind(ig)
|
||||
}
|
||||
const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory
|
||||
const nodes: Node[] = []
|
||||
|
||||
@@ -17,6 +17,7 @@ export namespace Flag {
|
||||
// Experimental
|
||||
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
|
||||
export const OPENCODE_EXPERIMENTAL_WATCHER = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WATCHER")
|
||||
export const OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT = truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT")
|
||||
|
||||
function truthy(key: string) {
|
||||
const value = process.env[key]?.toLowerCase()
|
||||
|
||||
@@ -25,6 +25,7 @@ import { AcpCommand } from "./cli/cmd/acp"
|
||||
import { EOL } from "os"
|
||||
import { WebCommand } from "./cli/cmd/web"
|
||||
import { PrCommand } from "./cli/cmd/pr"
|
||||
import { SessionCommand } from "./cli/cmd/session"
|
||||
|
||||
process.on("unhandledRejection", (e) => {
|
||||
Log.Default.error("rejection", {
|
||||
@@ -39,6 +40,7 @@ process.on("uncaughtException", (e) => {
|
||||
})
|
||||
|
||||
const cli = yargs(hideBin(process.argv))
|
||||
.parserConfiguration({ "populate--": true })
|
||||
.scriptName("opencode")
|
||||
.help("help", "show help")
|
||||
.alias("help", "h")
|
||||
@@ -92,6 +94,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
.command(ImportCommand)
|
||||
.command(GithubCommand)
|
||||
.command(PrCommand)
|
||||
.command(SessionCommand)
|
||||
.fail((msg) => {
|
||||
if (
|
||||
msg.startsWith("Unknown argument") ||
|
||||
|
||||
@@ -4,6 +4,7 @@ import z from "zod"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { Bus } from "../bus"
|
||||
import { Log } from "../util/log"
|
||||
import { iife } from "@/util/iife"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_VERSION: string
|
||||
@@ -162,10 +163,28 @@ export namespace Installation {
|
||||
export const CHANNEL = typeof OPENCODE_CHANNEL === "string" ? OPENCODE_CHANNEL : "local"
|
||||
export const USER_AGENT = `opencode/${CHANNEL}/${VERSION}`
|
||||
|
||||
export async function latest() {
|
||||
export async function latest(installMethod?: Method) {
|
||||
const detectedMethod = installMethod || (await method())
|
||||
if (detectedMethod === "brew") {
|
||||
const formula = await getBrewFormula()
|
||||
if (formula === "opencode") {
|
||||
return fetch("https://formulae.brew.sh/api/formula/opencode.json")
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
return res.json()
|
||||
})
|
||||
.then((data: any) => data.versions.stable)
|
||||
}
|
||||
}
|
||||
|
||||
const registry = await iife(async () => {
|
||||
const r = (await $`npm config get registry`.quiet().nothrow().text()).trim()
|
||||
const reg = r || "https://registry.npmjs.org"
|
||||
return reg.endsWith("/") ? reg.slice(0, -1) : reg
|
||||
})
|
||||
const [major] = VERSION.split(".").map((x) => Number(x))
|
||||
const channel = CHANNEL === "latest" ? `latest-${major}` : CHANNEL
|
||||
return fetch(`https://registry.npmjs.org/opencode-ai/${channel}`)
|
||||
return fetch(`${registry}/opencode-ai/${channel}`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
return res.json()
|
||||
|
||||
@@ -104,7 +104,7 @@ export namespace LSPClient {
|
||||
},
|
||||
},
|
||||
}),
|
||||
5_000,
|
||||
45_000,
|
||||
).catch((err) => {
|
||||
l.error("initialize error", { error: err })
|
||||
throw new InitializeError(
|
||||
|
||||
@@ -94,7 +94,7 @@ export namespace Provider {
|
||||
return {
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||
if (modelID.includes("gpt-5")) {
|
||||
if (modelID.includes("codex")) {
|
||||
return sdk.responses(modelID)
|
||||
}
|
||||
return sdk.chat(modelID)
|
||||
@@ -106,7 +106,7 @@ export namespace Provider {
|
||||
return {
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||
if (modelID.includes("gpt-5")) {
|
||||
if (modelID.includes("codex")) {
|
||||
return sdk.responses(modelID)
|
||||
}
|
||||
return sdk.chat(modelID)
|
||||
@@ -515,7 +515,7 @@ export namespace Provider {
|
||||
continue
|
||||
}
|
||||
|
||||
if (providerID === "github-copilot") {
|
||||
if (providerID === "github-copilot" || providerID === "github-copilot-enterprise") {
|
||||
provider.info.npm = "@ai-sdk/github-copilot"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { APICallError, ModelMessage } from "ai"
|
||||
import { STATUS_CODES } from "http"
|
||||
import { unique } from "remeda"
|
||||
import type { JSONSchema } from "zod/v4/core"
|
||||
|
||||
@@ -276,13 +275,13 @@ export namespace ProviderTransform {
|
||||
|
||||
// Convert integer enums to string enums for Google/Gemini
|
||||
if (providerID === "google" || modelID.includes("gemini")) {
|
||||
const convertIntEnumsToStrings = (obj: any): any => {
|
||||
const sanitizeGemini = (obj: any): any => {
|
||||
if (obj === null || typeof obj !== "object") {
|
||||
return obj
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(convertIntEnumsToStrings)
|
||||
return obj.map(sanitizeGemini)
|
||||
}
|
||||
|
||||
const result: any = {}
|
||||
@@ -295,15 +294,21 @@ export namespace ProviderTransform {
|
||||
result.type = "string"
|
||||
}
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
result[key] = convertIntEnumsToStrings(value)
|
||||
result[key] = sanitizeGemini(value)
|
||||
} else {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Filter required array to only include fields that exist in properties
|
||||
if (result.type === "object" && result.properties && Array.isArray(result.required)) {
|
||||
result.required = result.required.filter((field: any) => field in result.properties)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
schema = convertIntEnumsToStrings(schema)
|
||||
schema = sanitizeGemini(schema)
|
||||
}
|
||||
|
||||
return schema
|
||||
@@ -318,19 +323,6 @@ export namespace ProviderTransform {
|
||||
)
|
||||
}
|
||||
|
||||
if (!error.responseBody || (error.statusCode && message !== STATUS_CODES[error.statusCode])) {
|
||||
return message
|
||||
}
|
||||
|
||||
try {
|
||||
const body = JSON.parse(error.responseBody)
|
||||
// try to extract common error message fields
|
||||
const errMsg = body.message || body.error
|
||||
if (errMsg && typeof errMsg === "string") {
|
||||
return `${message}: ${errMsg}`
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return `${message}: ${error.responseBody}`
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ import { Snapshot } from "@/snapshot"
|
||||
import { SessionSummary } from "@/session/summary"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { SessionStatus } from "@/session/status"
|
||||
import { ShareNext } from "@/share/share-next"
|
||||
|
||||
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
|
||||
globalThis.AI_SDK_LOG_WARNINGS = false
|
||||
@@ -2037,6 +2036,9 @@ export namespace Server {
|
||||
await stream.writeSSE({
|
||||
data: JSON.stringify(event),
|
||||
})
|
||||
if (event.type === Bus.InstanceDisposed.type) {
|
||||
stream.close()
|
||||
}
|
||||
})
|
||||
await new Promise<void>((resolve) => {
|
||||
stream.onAbort(() => {
|
||||
|
||||
@@ -9,6 +9,8 @@ import { Snapshot } from "@/snapshot"
|
||||
import { fn } from "@/util/fn"
|
||||
import { Storage } from "@/storage/storage"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
import { STATUS_CODES } from "http"
|
||||
import { iife } from "@/util/iife"
|
||||
|
||||
export namespace MessageV2 {
|
||||
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
|
||||
@@ -739,7 +741,28 @@ export namespace MessageV2 {
|
||||
{ cause: e },
|
||||
).toObject()
|
||||
case APICallError.isInstance(e):
|
||||
const message = ProviderTransform.error(ctx.providerID, e)
|
||||
const message = iife(() => {
|
||||
let msg = e.message
|
||||
const transformed = ProviderTransform.error(ctx.providerID, e)
|
||||
if (transformed !== msg) {
|
||||
return transformed
|
||||
}
|
||||
if (!e.responseBody || (e.statusCode && msg !== STATUS_CODES[e.statusCode])) {
|
||||
return msg
|
||||
}
|
||||
|
||||
try {
|
||||
const body = JSON.parse(e.responseBody)
|
||||
// try to extract common error message fields
|
||||
const errMsg = body.message || body.error
|
||||
if (errMsg && typeof errMsg === "string") {
|
||||
return `${msg}: ${errMsg}`
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return `${msg}: ${e.responseBody}`
|
||||
})
|
||||
|
||||
return new MessageV2.APIError(
|
||||
{
|
||||
message,
|
||||
|
||||
@@ -333,13 +333,14 @@ export namespace SessionProcessor {
|
||||
error: e,
|
||||
})
|
||||
const error = MessageV2.fromError(e, { providerID: input.providerID })
|
||||
if (error?.name === "APIError" && error.data.isRetryable) {
|
||||
const retry = SessionRetry.retryable(error)
|
||||
if (retry !== undefined) {
|
||||
attempt++
|
||||
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
|
||||
SessionStatus.set(input.sessionID, {
|
||||
type: "retry",
|
||||
attempt,
|
||||
message: error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message,
|
||||
message: retry,
|
||||
next: Date.now() + delay,
|
||||
})
|
||||
await SessionRetry.sleep(delay, input.abort).catch(() => {})
|
||||
|
||||
@@ -544,6 +544,7 @@ export namespace SessionPrompt {
|
||||
headers: {
|
||||
...(model.providerID.startsWith("opencode")
|
||||
? {
|
||||
"x-opencode-project": Instance.project.id,
|
||||
"x-opencode-session": sessionID,
|
||||
"x-opencode-request": lastUser.id,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { NamedError } from "@opencode-ai/util/error"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
|
||||
export namespace SessionRetry {
|
||||
@@ -51,4 +52,22 @@ export namespace SessionRetry {
|
||||
|
||||
return Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)
|
||||
}
|
||||
|
||||
export function retryable(error: ReturnType<NamedError["toObject"]>) {
|
||||
if (MessageV2.APIError.isInstance(error)) {
|
||||
if (!error.data.isRetryable) return undefined
|
||||
return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
|
||||
}
|
||||
|
||||
if (typeof error.data?.message === "string") {
|
||||
try {
|
||||
const json = JSON.parse(error.data.message)
|
||||
if (json.type === "error" && json.error?.type === "too_many_requests") {
|
||||
return "Too Many Requests"
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { Ripgrep } from "../file/ripgrep"
|
||||
import DESCRIPTION from "./grep.txt"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
const MAX_LINE_LENGTH = 2000
|
||||
|
||||
export const GrepTool = Tool.define("grep", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
@@ -96,7 +98,9 @@ export const GrepTool = Tool.define("grep", {
|
||||
currentFile = match.path
|
||||
outputLines.push(`${match.path}:`)
|
||||
}
|
||||
outputLines.push(` Line ${match.lineNum}: ${match.lineText}`)
|
||||
const truncatedLineText =
|
||||
match.lineText.length > MAX_LINE_LENGTH ? match.lineText.substring(0, MAX_LINE_LENGTH) + "..." : match.lineText
|
||||
outputLines.push(` Line ${match.lineNum}: ${truncatedLineText}`)
|
||||
}
|
||||
|
||||
if (truncated) {
|
||||
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -16,6 +16,12 @@ import { type ToolDefinition } from "./tool"
|
||||
|
||||
export * from "./tool"
|
||||
|
||||
export type ProviderContext = {
|
||||
source: "env" | "config" | "custom" | "api"
|
||||
info: Provider
|
||||
options: Record<string, any>
|
||||
}
|
||||
|
||||
export type PluginInput = {
|
||||
client: ReturnType<typeof createOpencodeClient>
|
||||
project: Project
|
||||
@@ -153,7 +159,7 @@ export interface Hooks {
|
||||
* Modify parameters sent to LLM
|
||||
*/
|
||||
"chat.params"?: (
|
||||
input: { sessionID: string; agent: string; model: Model; provider: Provider; message: UserMessage },
|
||||
input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
|
||||
output: { temperature: number; topP: number; options: Record<string, any> },
|
||||
) => Promise<void>
|
||||
"permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>
|
||||
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.0.124",
|
||||
"version": "1.0.129",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export type EventServerInstanceDisposed = {
|
||||
type: "server.instance.disposed"
|
||||
properties: {
|
||||
directory: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventInstallationUpdated = {
|
||||
type: "installation.updated"
|
||||
properties: {
|
||||
@@ -656,6 +663,7 @@ export type EventServerConnected = {
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| EventServerInstanceDisposed
|
||||
| EventInstallationUpdated
|
||||
| EventInstallationUpdateAvailable
|
||||
| EventLspClientDiagnostics
|
||||
@@ -724,6 +732,10 @@ export type KeybindsConfig = {
|
||||
* Toggle sidebar
|
||||
*/
|
||||
sidebar_toggle?: string
|
||||
/**
|
||||
* Toggle username visibility
|
||||
*/
|
||||
username_toggle?: string
|
||||
/**
|
||||
* View status
|
||||
*/
|
||||
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
@@ -39,9 +39,6 @@ class Resource:
|
||||
class Console:
|
||||
type: str
|
||||
url: str
|
||||
class ConsoleData:
|
||||
name: str
|
||||
type: str
|
||||
class Database:
|
||||
database: str
|
||||
host: str
|
||||
@@ -111,4 +108,7 @@ class Resource:
|
||||
class ZEN_MODELS4:
|
||||
type: str
|
||||
value: str
|
||||
class ZenData:
|
||||
name: str
|
||||
type: str
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user