mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-16 01:19:19 -04:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 090f638318 | |||
| 8f3f87943a | |||
| 7ac3f9182b | |||
| cda2138f24 | |||
| c4f8c6c005 | |||
| 8304bd418f | |||
| 8a9c7a4ef3 | |||
| 2dad56c9a2 | |||
| 366b6e4fbb | |||
| 41d78c1ecc | |||
| 16c4b02b69 | |||
| 35c04d9283 | |||
| 1fbd7a7f9a | |||
| d7563d1694 | |||
| b9fa7d9163 | |||
| f736751ab2 | |||
| dbcc779f0b | |||
| c33a90320c | |||
| 802b862aae | |||
| b0cd171c1b | |||
| 13755f4680 | |||
| b242659cc3 | |||
| 5f6b2fdc6f | |||
| e34f18991e | |||
| 209b0a06f7 | |||
| a2e460bc4b | |||
| fc9081afe4 | |||
| 1a3f7c3d84 | |||
| 06aa1f49b8 | |||
| dfd67cd922 | |||
| 70f7287ca1 | |||
| f1955b4d05 | |||
| c5e5627cbd | |||
| 93378526b9 | |||
| abf176a335 | |||
| 84a0868e66 | |||
| 75a9c42789 | |||
| 204fa54625 | |||
| 365584048f | |||
| edffcc32cf | |||
| 238f441bcb | |||
| 0571a8302c | |||
| 8c07382382 | |||
| fa32fbd187 | |||
| 0fd2ecd0ba | |||
| 7439a40b00 | |||
| 2ad99713f3 | |||
| 19ec970701 | |||
| b48caec218 | |||
| 380c34af53 | |||
| 553d9013eb | |||
| 8bff3cdae8 | |||
| 0b40c3d37d | |||
| 1e3bdcc71c | |||
| de577e17da | |||
| 8a9e258ad7 | |||
| 9a34965432 | |||
| c944d19c3b | |||
| fb1b6c5e6b | |||
| ad0c4c5d89 | |||
| a54b663a39 | |||
| ae4993f39a | |||
| aa638cec48 | |||
| 4db4a90559 | |||
| e23a81097c | |||
| 76f4803d8d | |||
| 22e4649318 | |||
| 0ac70ff261 | |||
| 1bc1e56da3 | |||
| 0d0c20e673 | |||
| a964824b22 | |||
| 2cf0d578fe | |||
| 13e8fb382f | |||
| 4090bc9dea | |||
| cec1caf99e | |||
| c74da97d52 | |||
| 1f2497ce69 | |||
| 986f14cb15 | |||
| 34f639d510 | |||
| defe51c825 | |||
| 5a16acef8c | |||
| 2ce249dbc0 | |||
| 7ba6b18945 | |||
| b8c0b393bf | |||
| 5442adb517 | |||
| 6b2ac20abc | |||
| 3efc95b157 | |||
| cd9db8a81d | |||
| 036f5d4eef | |||
| c4401290db | |||
| 4a6deb6420 | |||
| 87a03e1e30 | |||
| 01dc9d7ec6 | |||
| e78e0f9841 | |||
| 8326640670 | |||
| d079af4be2 | |||
| 82c9584382 | |||
| d3b6de855b | |||
| 5ad000fd99 | |||
| fe196da430 | |||
| 20662e2101 | |||
| 0a357be160 | |||
| d29205e677 | |||
| 9d0630f094 | |||
| b6844565e8 | |||
| 17d1b24def | |||
| 3d279edf44 | |||
| 0a47a3cea0 | |||
| 306d57fcde | |||
| ff6f1abf61 | |||
| 331278a5be | |||
| 78547f3c59 | |||
| d32671224f | |||
| 9ade416ad4 | |||
| f8bd4ff705 | |||
| 2206e10d92 | |||
| e282d5dc42 | |||
| 2b4a5aede1 | |||
| 654a2cd6a4 |
@@ -8,9 +8,14 @@ on:
|
|||||||
branches-ignore:
|
branches-ignore:
|
||||||
- production
|
- production
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["sdk"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
jobs:
|
jobs:
|
||||||
format:
|
format:
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
if: github.event.workflow_run.conclusion == 'success'
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||||
|
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: ./.github/actions/setup-bun
|
uses: ./.github/actions/setup-bun
|
||||||
@@ -26,6 +28,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
bun ./packages/sdk/js/script/build.ts
|
bun ./packages/sdk/js/script/build.ts
|
||||||
(cd packages/opencode && bun dev generate > ../sdk/openapi.json)
|
(cd packages/opencode && bun dev generate > ../sdk/openapi.json)
|
||||||
|
bun x prettier --write packages/sdk/js/src/openapi.ts
|
||||||
if [ -z "$(git status --porcelain)" ]; then
|
if [ -z "$(git status --porcelain)" ]; then
|
||||||
echo "No changes to commit"
|
echo "No changes to commit"
|
||||||
exit 0
|
exit 0
|
||||||
@@ -34,6 +37,7 @@ jobs:
|
|||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "chore: regen sdk"
|
git commit -m "chore: regen sdk"
|
||||||
git push --no-verify
|
git push origin HEAD:${PUSH_BRANCH} --no-verify
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
PUSH_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ node_modules
|
|||||||
playground
|
playground
|
||||||
tmp
|
tmp
|
||||||
dist
|
dist
|
||||||
|
ts-dist
|
||||||
.turbo
|
.turbo
|
||||||
**/.serena
|
**/.serena
|
||||||
.serena/
|
.serena/
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
description: git commit and push
|
description: git commit and push
|
||||||
|
model: opencode/glm-4.6
|
||||||
---
|
---
|
||||||
|
|
||||||
commit and push
|
commit and push
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
"instructions": ["STYLE_GUIDE.md"],
|
"instructions": ["STYLE_GUIDE.md"],
|
||||||
"provider": {
|
"provider": {
|
||||||
"opencode": {
|
"opencode": {
|
||||||
"options": {
|
"options": {},
|
||||||
// "baseURL": "http://localhost:8080",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"mcp": {
|
"mcp": {
|
||||||
|
|||||||
@@ -1,166 +1,167 @@
|
|||||||
# Download Stats
|
# Download Stats
|
||||||
|
|
||||||
| Date | GitHub Downloads | npm Downloads | Total |
|
| Date | GitHub Downloads | npm Downloads | Total |
|
||||||
| ---------- | ------------------ | ----------------- | ------------------- |
|
| ---------- | ------------------- | ----------------- | ------------------- |
|
||||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||||
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
||||||
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
||||||
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
||||||
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
||||||
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
||||||
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
||||||
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
||||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||||
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||||
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||||
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||||
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||||
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||||
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||||
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||||
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||||
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
||||||
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
||||||
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
||||||
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||||
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
||||||
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
||||||
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
||||||
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
||||||
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
||||||
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
||||||
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
|
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
|
||||||
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
|
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
|
||||||
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
|
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
|
||||||
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
|
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
|
||||||
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
|
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
|
||||||
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
|
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
|
||||||
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
|
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
|
||||||
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
|
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
|
||||||
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
|
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
|
||||||
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
|
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
|
||||||
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
|
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
|
||||||
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
|
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
|
||||||
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
|
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
|
||||||
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
|
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
|
||||||
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
|
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
|
||||||
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
|
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
|
||||||
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
|
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
|
||||||
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
|
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
|
||||||
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
|
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
|
||||||
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
|
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
|
||||||
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
|
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
|
||||||
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
|
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
|
||||||
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
|
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
|
||||||
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
|
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
|
||||||
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
|
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
|
||||||
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
|
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
|
||||||
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
|
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
|
||||||
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
|
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
|
||||||
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
|
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
|
||||||
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
|
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
|
||||||
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
|
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
|
||||||
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
|
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
|
||||||
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
|
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
|
||||||
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
|
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
|
||||||
| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
|
| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
|
||||||
| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
|
| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
|
||||||
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
|
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
|
||||||
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
|
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
|
||||||
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
|
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
|
||||||
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
|
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
|
||||||
| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
|
| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
|
||||||
| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
|
| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
|
||||||
| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
|
| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
|
||||||
| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
|
| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
|
||||||
| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
|
| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
|
||||||
| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
|
| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
|
||||||
| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
|
| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
|
||||||
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
|
| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
|
||||||
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
|
| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
|
||||||
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
|
| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
|
||||||
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
|
| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
|
||||||
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
|
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
|
||||||
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
|
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
|
||||||
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
|
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
|
||||||
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
|
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
|
||||||
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
|
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
|
||||||
| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
|
| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
|
||||||
| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
|
| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
|
||||||
| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
|
| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
|
||||||
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
|
| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
|
||||||
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
|
| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
|
||||||
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
|
| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
|
||||||
| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |
|
| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |
|
||||||
| 2025-11-14 | 759,928 (+10,023) | 705,237 (+9,080) | 1,465,165 (+19,103) |
|
| 2025-11-14 | 759,928 (+10,023) | 705,237 (+9,080) | 1,465,165 (+19,103) |
|
||||||
| 2025-11-15 | 765,955 (+6,027) | 712,870 (+7,633) | 1,478,825 (+13,660) |
|
| 2025-11-15 | 765,955 (+6,027) | 712,870 (+7,633) | 1,478,825 (+13,660) |
|
||||||
| 2025-11-16 | 771,069 (+5,114) | 716,596 (+3,726) | 1,487,665 (+8,840) |
|
| 2025-11-16 | 771,069 (+5,114) | 716,596 (+3,726) | 1,487,665 (+8,840) |
|
||||||
| 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) |
|
| 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) |
|
||||||
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
|
| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
|
||||||
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
|
| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
|
||||||
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
|
| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
|
||||||
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
|
| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
|
||||||
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
|
| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
|
||||||
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
|
| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
|
||||||
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
|
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
|
||||||
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
|
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
|
||||||
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
|
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
|
||||||
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
|
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
|
||||||
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
|
| 2025-11-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-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-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-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
|
||||||
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
|
| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
|
||||||
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
|
| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
|
||||||
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
|
| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
|
||||||
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
|
| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
|
||||||
| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
|
| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
|
||||||
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
|
| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
|
||||||
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
|
| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
|
||||||
|
| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
@@ -212,7 +212,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -324,7 +324,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.88.1",
|
"@hey-api/openapi-ts": "0.88.1",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -335,7 +335,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -348,12 +348,13 @@
|
|||||||
},
|
},
|
||||||
"packages/tauri": {
|
"packages/tauri": {
|
||||||
"name": "@opencode-ai/tauri",
|
"name": "@opencode-ai/tauri",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/desktop": "workspace:*",
|
"@opencode-ai/desktop": "workspace:*",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-dialog": "~2",
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
|
"@tauri-apps/plugin-process": "~2",
|
||||||
"@tauri-apps/plugin-shell": "~2",
|
"@tauri-apps/plugin-shell": "~2",
|
||||||
"@tauri-apps/plugin-updater": "~2",
|
"@tauri-apps/plugin-updater": "~2",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
@@ -362,13 +363,14 @@
|
|||||||
"@actions/artifact": "4.0.0",
|
"@actions/artifact": "4.0.0",
|
||||||
"@tauri-apps/cli": "^2",
|
"@tauri-apps/cli": "^2",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
|
"@typescript/native-preview": "catalog:",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -400,7 +402,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -411,7 +413,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -1655,6 +1657,8 @@
|
|||||||
|
|
||||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="],
|
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="],
|
||||||
|
|
||||||
|
"@tauri-apps/plugin-process": ["@tauri-apps/plugin-process@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ=="],
|
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="],
|
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="],
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": "sha256-IzF5XDY09Z1p/8jgYIHhE/jpKPub15KKUpV+a/aKpuc="
|
"nodeModules": "sha256-lM/7mkrPHz5E6YOMjWspfRhKjwav9ANrLt9kYlpPkEI="
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
{ lib, stdenvNoCC, bun, fzf, ripgrep, makeBinaryWrapper }:
|
{ lib, stdenvNoCC, bun, ripgrep, makeBinaryWrapper }:
|
||||||
args:
|
args:
|
||||||
let
|
let
|
||||||
scripts = args.scripts;
|
scripts = args.scripts;
|
||||||
@@ -97,7 +97,7 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
|||||||
makeWrapper ${bun}/bin/bun $out/bin/opencode \
|
makeWrapper ${bun}/bin/bun $out/bin/opencode \
|
||||||
--add-flags "run" \
|
--add-flags "run" \
|
||||||
--add-flags "$out/lib/opencode/dist/src/index.js" \
|
--add-flags "$out/lib/opencode/dist/src/index.js" \
|
||||||
--prefix PATH : ${lib.makeBinPath [ fzf ripgrep ]} \
|
--prefix PATH : ${lib.makeBinPath [ ripgrep ]} \
|
||||||
--argv0 opencode
|
--argv0 opencode
|
||||||
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { createContext } from "solid-js"
|
|
||||||
import { useContext } from "solid-js"
|
|
||||||
|
|
||||||
export interface Platform {}
|
|
||||||
|
|
||||||
const PlatformContext = createContext<Platform>()
|
|
||||||
|
|
||||||
export const PlatformProvider = PlatformContext.Provider
|
|
||||||
|
|
||||||
export function usePlatform() {
|
|
||||||
const ctx = useContext(PlatformContext)
|
|
||||||
if (!ctx) throw new Error("usePlatform must be used within a PlatformProvider")
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
@@ -2,19 +2,18 @@ import "@/index.css"
|
|||||||
import { Router, Route, Navigate } from "@solidjs/router"
|
import { Router, Route, Navigate } from "@solidjs/router"
|
||||||
import { MetaProvider } from "@solidjs/meta"
|
import { MetaProvider } from "@solidjs/meta"
|
||||||
import { Font } from "@opencode-ai/ui/font"
|
import { Font } from "@opencode-ai/ui/font"
|
||||||
import { Favicon } from "@opencode-ai/ui/favicon"
|
|
||||||
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||||
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
|
import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
|
||||||
import { Diff } from "@opencode-ai/ui/diff"
|
import { Diff } from "@opencode-ai/ui/diff"
|
||||||
import { GlobalSyncProvider, useGlobalSync } from "./context/global-sync"
|
import { GlobalSyncProvider } from "./context/global-sync"
|
||||||
import Layout from "@/pages/layout"
|
import Layout from "@/pages/layout"
|
||||||
|
import Home from "@/pages/home"
|
||||||
import DirectoryLayout from "@/pages/directory-layout"
|
import DirectoryLayout from "@/pages/directory-layout"
|
||||||
import Session from "@/pages/session"
|
import Session from "@/pages/session"
|
||||||
import { LayoutProvider } from "./context/layout"
|
import { LayoutProvider } from "./context/layout"
|
||||||
import { GlobalSDKProvider } from "./context/global-sdk"
|
import { GlobalSDKProvider } from "./context/global-sdk"
|
||||||
import { SessionProvider } from "./context/session"
|
import { SessionProvider } from "./context/session"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { Show } from "solid-js"
|
||||||
import { createMemo, Show } from "solid-js"
|
|
||||||
|
|
||||||
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
|
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
|
||||||
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
|
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
|
||||||
@@ -25,7 +24,7 @@ const url =
|
|||||||
? `http://${host}:${port}`
|
? `http://${host}:${port}`
|
||||||
: "/")
|
: "/")
|
||||||
|
|
||||||
export function DesktopInterface() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<MarkedProvider>
|
<MarkedProvider>
|
||||||
<DiffComponentProvider component={Diff}>
|
<DiffComponentProvider component={Diff}>
|
||||||
@@ -35,14 +34,7 @@ export function DesktopInterface() {
|
|||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Font />
|
<Font />
|
||||||
<Router root={Layout}>
|
<Router root={Layout}>
|
||||||
<Route
|
<Route path="/" component={Home} />
|
||||||
path="/"
|
|
||||||
component={() => {
|
|
||||||
const globalSync = useGlobalSync()
|
|
||||||
const slug = createMemo(() => base64Encode(globalSync.data.defaultProject!.worktree))
|
|
||||||
return <Navigate href={`${slug()}/session`} />
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Route path="/:dir" component={DirectoryLayout}>
|
<Route path="/:dir" component={DirectoryLayout}>
|
||||||
<Route path="/" component={() => <Navigate href="session" />} />
|
<Route path="/" component={() => <Navigate href="session" />} />
|
||||||
<Route
|
<Route
|
||||||
@@ -19,7 +19,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
sdk.global.event().then(async (events) => {
|
sdk.global.event().then(async (events) => {
|
||||||
for await (const event of events.stream) {
|
for await (const event of events.stream) {
|
||||||
// console.log("event", event)
|
// console.log("event", event)
|
||||||
emitter.emit(event.directory, event.payload)
|
emitter.emit(event.directory ?? "global", event.payload)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type State = {
|
|||||||
ready: boolean
|
ready: boolean
|
||||||
provider: Provider[]
|
provider: Provider[]
|
||||||
agent: Agent[]
|
agent: Agent[]
|
||||||
project: Project
|
project: string
|
||||||
config: Config
|
config: Config
|
||||||
path: Path
|
path: Path
|
||||||
session: Session[]
|
session: Session[]
|
||||||
@@ -51,7 +51,6 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
|||||||
init: () => {
|
init: () => {
|
||||||
const [globalStore, setGlobalStore] = createStore<{
|
const [globalStore, setGlobalStore] = createStore<{
|
||||||
ready: boolean
|
ready: boolean
|
||||||
defaultProject?: Project // TODO: remove this when we can select projects
|
|
||||||
projects: Project[]
|
projects: Project[]
|
||||||
children: Record<string, State>
|
children: Record<string, State>
|
||||||
}>({
|
}>({
|
||||||
@@ -61,11 +60,10 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
|||||||
})
|
})
|
||||||
|
|
||||||
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
|
const children: Record<string, ReturnType<typeof createStore<State>>> = {}
|
||||||
|
|
||||||
function child(directory: string) {
|
function child(directory: string) {
|
||||||
if (!children[directory]) {
|
if (!children[directory]) {
|
||||||
setGlobalStore("children", directory, {
|
setGlobalStore("children", directory, {
|
||||||
project: { id: "", worktree: "", time: { created: 0, initialized: 0 } },
|
project: "",
|
||||||
config: {},
|
config: {},
|
||||||
path: { state: "", config: "", worktree: "", directory: "" },
|
path: { state: "", config: "", worktree: "", directory: "" },
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -75,7 +73,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
|||||||
session_status: {},
|
session_status: {},
|
||||||
session_diff: {},
|
session_diff: {},
|
||||||
todo: {},
|
todo: {},
|
||||||
limit: 10,
|
limit: 5,
|
||||||
message: {},
|
message: {},
|
||||||
part: {},
|
part: {},
|
||||||
node: [],
|
node: [],
|
||||||
@@ -89,9 +87,29 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
|||||||
const sdk = useGlobalSDK()
|
const sdk = useGlobalSDK()
|
||||||
sdk.event.listen((e) => {
|
sdk.event.listen((e) => {
|
||||||
const directory = e.name
|
const directory = e.name
|
||||||
const [store, setStore] = child(directory)
|
|
||||||
|
|
||||||
const event = e.details
|
const event = e.details
|
||||||
|
|
||||||
|
if (directory === "global") {
|
||||||
|
switch (event.type) {
|
||||||
|
case "project.updated": {
|
||||||
|
const result = Binary.search(globalStore.projects, event.properties.id, (s) => s.id)
|
||||||
|
if (result.found) {
|
||||||
|
setGlobalStore("projects", result.index, reconcile(event.properties))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
setGlobalStore(
|
||||||
|
"projects",
|
||||||
|
produce((draft) => {
|
||||||
|
draft.splice(result.index, 0, event.properties)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [store, setStore] = child(directory)
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "session.updated": {
|
case "session.updated": {
|
||||||
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
||||||
@@ -165,11 +183,11 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
|||||||
sdk.client.project.list().then((x) =>
|
sdk.client.project.list().then((x) =>
|
||||||
setGlobalStore(
|
setGlobalStore(
|
||||||
"projects",
|
"projects",
|
||||||
x.data!.filter((x) => !x.worktree.includes("opencode-test")),
|
x
|
||||||
|
.data!.filter((x) => !x.worktree.includes("opencode-test") && x.vcs)
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// TODO: remove this when we can select projects
|
|
||||||
sdk.client.project.current().then((x) => setGlobalStore("defaultProject", x.data)),
|
|
||||||
]).then(() => setGlobalStore("ready", true))
|
]).then(() => setGlobalStore("ready", true))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createMemo } from "solid-js"
|
import { createMemo, onMount } from "solid-js"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { makePersisted } from "@solid-primitives/storage"
|
import { makePersisted } from "@solid-primitives/storage"
|
||||||
import { useGlobalSync } from "./global-sync"
|
import { useGlobalSync } from "./global-sync"
|
||||||
|
import { useGlobalSDK } from "./global-sdk"
|
||||||
|
|
||||||
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
|
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
|
||||||
name: "Layout",
|
name: "Layout",
|
||||||
init: () => {
|
init: () => {
|
||||||
|
const globalSdk = useGlobalSDK()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const [store, setStore] = makePersisted(
|
const [store, setStore] = makePersisted(
|
||||||
createStore({
|
createStore({
|
||||||
projects: [] as { directory: string; expanded: boolean }[],
|
projects: [] as { worktree: string; expanded: boolean }[],
|
||||||
sidebar: {
|
sidebar: {
|
||||||
opened: true,
|
opened: false,
|
||||||
width: 280,
|
width: 280,
|
||||||
},
|
},
|
||||||
terminal: {
|
terminal: {
|
||||||
@@ -24,29 +26,66 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "____default-layout",
|
name: "default-layout.v6",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async function loadProjectSessions(directory: string) {
|
||||||
|
const [, setStore] = globalSync.child(directory)
|
||||||
|
globalSdk.client.session.list({ directory }).then((x) => {
|
||||||
|
const sessions = (x.data ?? [])
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
|
.slice(0, 5)
|
||||||
|
setStore("session", sessions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
Promise.all(
|
||||||
|
store.projects.map(({ worktree }) => {
|
||||||
|
return loadProjectSessions(worktree)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function enrich(project: { worktree: string; expanded: boolean }) {
|
||||||
|
const metadata = globalSync.data.projects.find((x) => x.worktree === project.worktree)
|
||||||
|
if (!metadata) return []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...project,
|
||||||
|
...metadata,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projects: {
|
projects: {
|
||||||
list: createMemo(() =>
|
list: createMemo(() => store.projects.flatMap(enrich)),
|
||||||
globalSync.data.defaultProject
|
|
||||||
? [{ directory: globalSync.data.defaultProject!.worktree, expanded: true }, ...store.projects]
|
|
||||||
: store.projects,
|
|
||||||
),
|
|
||||||
open(directory: string) {
|
open(directory: string) {
|
||||||
if (store.projects.find((x) => x.directory === directory)) return
|
if (store.projects.find((x) => x.worktree === directory)) return
|
||||||
setStore("projects", (x) => [...x, { directory, expanded: true }])
|
loadProjectSessions(directory)
|
||||||
|
setStore("projects", (x) => [...x, { worktree: directory, expanded: true }])
|
||||||
},
|
},
|
||||||
close(directory: string) {
|
close(directory: string) {
|
||||||
setStore("projects", (x) => x.filter((x) => x.directory !== directory))
|
setStore("projects", (x) => x.filter((x) => x.worktree !== directory))
|
||||||
},
|
},
|
||||||
expand(directory: string) {
|
expand(directory: string) {
|
||||||
setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: true } : x)))
|
setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: true } : x)))
|
||||||
},
|
},
|
||||||
collapse(directory: string) {
|
collapse(directory: string) {
|
||||||
setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: false } : x)))
|
setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: false } : x)))
|
||||||
|
},
|
||||||
|
move(directory: string, toIndex: number) {
|
||||||
|
setStore("projects", (projects) => {
|
||||||
|
const fromIndex = projects.findIndex((x) => x.worktree === directory)
|
||||||
|
if (fromIndex === -1 || fromIndex === toIndex) return projects
|
||||||
|
const result = [...projects]
|
||||||
|
const [item] = result.splice(fromIndex, 1)
|
||||||
|
result.splice(toIndex, 0, item)
|
||||||
|
return result
|
||||||
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sidebar: {
|
sidebar: {
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
|
|
||||||
const load = async (path: string) => {
|
const load = async (path: string) => {
|
||||||
const relativePath = relative(path)
|
const relativePath = relative(path)
|
||||||
sdk.client.file.read({ path: relativePath }).then((x) => {
|
await sdk.client.file.read({ path: relativePath }).then((x) => {
|
||||||
setStore(
|
setStore(
|
||||||
"node",
|
"node",
|
||||||
relativePath,
|
relativePath,
|
||||||
@@ -335,7 +335,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
node: async (path: string) => {
|
node: async (path: string) => {
|
||||||
if (!store.node[path]) {
|
if (!store.node[path] || !store.node[path].loaded) {
|
||||||
await init(path)
|
await init(path)
|
||||||
}
|
}
|
||||||
return store.node[path]
|
return store.node[path]
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
|
|
||||||
|
export type Platform = {
|
||||||
|
/** Platform discriminator */
|
||||||
|
platform: "web" | "tauri"
|
||||||
|
|
||||||
|
/** Open native directory picker dialog (Tauri only) */
|
||||||
|
openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>
|
||||||
|
|
||||||
|
/** Open native file picker dialog (Tauri only) */
|
||||||
|
openFilePickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>
|
||||||
|
|
||||||
|
/** Save file picker dialog (Tauri only) */
|
||||||
|
saveFilePickerDialog?(opts?: { title?: string; defaultPath?: string }): Promise<string | null>
|
||||||
|
|
||||||
|
/** Open a URL in the default browser */
|
||||||
|
openLink(url: string): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
|
||||||
|
name: "Platform",
|
||||||
|
init: (props: { value: Platform }) => {
|
||||||
|
return props.value
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -7,7 +7,6 @@ import { TextSelection } from "./local"
|
|||||||
import { pipe, sumBy } from "remeda"
|
import { pipe, sumBy } from "remeda"
|
||||||
import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2"
|
import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
|
||||||
import { useSDK } from "./sdk"
|
import { useSDK } from "./sdk"
|
||||||
|
|
||||||
export type LocalPTY = {
|
export type LocalPTY = {
|
||||||
@@ -25,9 +24,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
|
|||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const name = createMemo(
|
const name = createMemo(() => `${params.dir}/session${params.id ? "/" + params.id : ""}.v3`)
|
||||||
() => `${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}.v2`,
|
|
||||||
)
|
|
||||||
|
|
||||||
const [store, setStore] = makePersisted(
|
const [store, setStore] = makePersisted(
|
||||||
createStore<{
|
createStore<{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
const [store, setStore] = globalSync.child(sdk.directory)
|
const [store, setStore] = globalSync.child(sdk.directory)
|
||||||
|
|
||||||
const load = {
|
const load = {
|
||||||
project: () => sdk.client.project.current().then((x) => setStore("project", x.data!)),
|
project: () => sdk.client.project.current().then((x) => setStore("project", x.data!.id)),
|
||||||
provider: () => sdk.client.config.providers().then((x) => setStore("provider", x.data!.providers)),
|
provider: () => sdk.client.config.providers().then((x) => setStore("provider", x.data!.providers)),
|
||||||
path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)),
|
path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)),
|
||||||
agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||||
@@ -41,6 +41,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
get ready() {
|
get ready() {
|
||||||
return store.ready
|
return store.ready
|
||||||
},
|
},
|
||||||
|
get project() {
|
||||||
|
const match = Binary.search(globalSync.data.projects, store.project, (p) => p.id)
|
||||||
|
if (match.found) return globalSync.data.projects[match.index]
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
session: {
|
session: {
|
||||||
get(sessionID: string) {
|
get(sessionID: string) {
|
||||||
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// @refresh reload
|
// @refresh reload
|
||||||
import { render } from "solid-js/web"
|
import { render } from "solid-js/web"
|
||||||
import { DesktopInterface } from "@/DesktopInterface"
|
import { App } from "@/app"
|
||||||
import { Platform, PlatformProvider } from "@/PlatformContext"
|
import { Platform, PlatformProvider } from "@/context/platform"
|
||||||
|
|
||||||
const root = document.getElementById("root")
|
const root = document.getElementById("root")
|
||||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||||
@@ -10,12 +10,17 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const platform: Platform = {}
|
const platform: Platform = {
|
||||||
|
platform: "web",
|
||||||
|
openLink(url: string) {
|
||||||
|
window.open(url, "_blank")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
() => (
|
() => (
|
||||||
<PlatformProvider value={platform}>
|
<PlatformProvider value={platform}>
|
||||||
<DesktopInterface />
|
<App />
|
||||||
</PlatformProvider>
|
</PlatformProvider>
|
||||||
),
|
),
|
||||||
root!,
|
root!,
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { PlatformProvider, type Platform } from "./PlatformContext"
|
export { PlatformProvider, type Platform } from "./context/platform"
|
||||||
export { DesktopInterface } from "./DesktopInterface"
|
export { App } from "./app"
|
||||||
|
|||||||
@@ -1,32 +1,31 @@
|
|||||||
import { createMemo, type ParentProps } from "solid-js"
|
import { createMemo, Show, type ParentProps } from "solid-js"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { SDKProvider } from "@/context/sdk"
|
import { SDKProvider } from "@/context/sdk"
|
||||||
import { SyncProvider, useSync } from "@/context/sync"
|
import { SyncProvider, useSync } from "@/context/sync"
|
||||||
import { LocalProvider } from "@/context/local"
|
import { LocalProvider } from "@/context/local"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
|
||||||
import { base64Decode } from "@opencode-ai/util/encode"
|
import { base64Decode } from "@opencode-ai/util/encode"
|
||||||
import { DataProvider } from "@opencode-ai/ui/context"
|
import { DataProvider } from "@opencode-ai/ui/context"
|
||||||
import { iife } from "@opencode-ai/util/iife"
|
import { iife } from "@opencode-ai/util/iife"
|
||||||
|
|
||||||
export default function Layout(props: ParentProps) {
|
export default function Layout(props: ParentProps) {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const sync = useGlobalSync()
|
|
||||||
const directory = createMemo(() => {
|
const directory = createMemo(() => {
|
||||||
const decoded = base64Decode(params.dir!)
|
return base64Decode(params.dir!)
|
||||||
return sync.data.projects.find((x) => x.worktree === decoded)?.worktree ?? "/"
|
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<SDKProvider directory={directory()}>
|
<Show when={params.dir} keyed>
|
||||||
<SyncProvider>
|
<SDKProvider directory={directory()}>
|
||||||
{iife(() => {
|
<SyncProvider>
|
||||||
const sync = useSync()
|
{iife(() => {
|
||||||
return (
|
const sync = useSync()
|
||||||
<DataProvider data={sync.data} directory={directory()}>
|
return (
|
||||||
<LocalProvider>{props.children}</LocalProvider>
|
<DataProvider data={sync.data} directory={directory()}>
|
||||||
</DataProvider>
|
<LocalProvider>{props.children}</LocalProvider>
|
||||||
)
|
</DataProvider>
|
||||||
})}
|
)
|
||||||
</SyncProvider>
|
})}
|
||||||
</SDKProvider>
|
</SyncProvider>
|
||||||
|
</SDKProvider>
|
||||||
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,92 @@
|
|||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { For, Match, Show, Switch } from "solid-js"
|
||||||
import { For } from "solid-js"
|
|
||||||
import { A } from "@solidjs/router"
|
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { Logo } from "@opencode-ai/ui/logo"
|
||||||
|
import { useLayout } from "@/context/layout"
|
||||||
|
import { useNavigate } from "@solidjs/router"
|
||||||
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { DateTime } from "luxon"
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const sync = useGlobalSync()
|
const sync = useGlobalSync()
|
||||||
|
const layout = useLayout()
|
||||||
|
const platform = usePlatform()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
function openProject(directory: string) {
|
||||||
|
layout.projects.open(directory)
|
||||||
|
navigate(`/${base64Encode(directory)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function chooseProject() {
|
||||||
|
const result = await platform.openDirectoryPickerDialog?.({
|
||||||
|
title: "Open project",
|
||||||
|
multiple: true,
|
||||||
|
})
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
for (const directory of result) {
|
||||||
|
openProject(directory)
|
||||||
|
}
|
||||||
|
} else if (result) {
|
||||||
|
openProject(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-3">
|
<div class="mx-auto mt-55">
|
||||||
<For each={sync.data.projects}>
|
<Logo class="w-xl opacity-12" />
|
||||||
{(project) => (
|
<Switch>
|
||||||
<Button as={A} href={base64Encode(project.worktree)}>
|
<Match when={sync.data.projects.length > 0}>
|
||||||
{getFilename(project.worktree)}
|
<div class="mt-20 w-full flex flex-col gap-4">
|
||||||
</Button>
|
<div class="flex gap-2 items-center justify-between pl-3">
|
||||||
)}
|
<div class="text-14-medium text-text-strong">Recent projects</div>
|
||||||
</For>
|
<Show when={platform.openDirectoryPickerDialog}>
|
||||||
|
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
|
||||||
|
Open project
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<ul class="flex flex-col gap-2">
|
||||||
|
<For
|
||||||
|
each={sync.data.projects
|
||||||
|
.toSorted((a, b) => (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created))
|
||||||
|
.slice(0, 5)}
|
||||||
|
>
|
||||||
|
{(project) => (
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
variant="ghost"
|
||||||
|
class="text-14-mono text-left justify-between px-3"
|
||||||
|
onClick={() => openProject(project.worktree)}
|
||||||
|
>
|
||||||
|
{project.worktree}
|
||||||
|
<div class="text-14-regular text-text-weak">
|
||||||
|
{DateTime.fromMillis(project.time.updated ?? project.time.created).toRelative()}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
<Match when={true}>
|
||||||
|
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
||||||
|
<Icon name="folder-add-left" size="large" />
|
||||||
|
<div class="flex flex-col gap-1 items-center justify-center">
|
||||||
|
<div class="text-14-medium text-text-strong">No recent projects</div>
|
||||||
|
<div class="text-12-regular text-text-weak">Get started by opening a local project</div>
|
||||||
|
</div>
|
||||||
|
<div />
|
||||||
|
<Show when={platform.openDirectoryPickerDialog}>
|
||||||
|
<Button class="px-3" onClick={chooseProject}>
|
||||||
|
Open project
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { createMemo, For, ParentProps, Show } from "solid-js"
|
import { createEffect, createMemo, For, Match, ParentProps, Show, Switch, type JSX } from "solid-js"
|
||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
import { A, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
|
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { Mark } from "@opencode-ai/ui/logo"
|
import { Mark } from "@opencode-ai/ui/logo"
|
||||||
|
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||||
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
@@ -14,24 +15,296 @@ import { Collapsible } from "@opencode-ai/ui/collapsible"
|
|||||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { Select } from "@opencode-ai/ui/select"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { Session } from "@opencode-ai/sdk/v2/client"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
|
import { Session, Project } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
|
import {
|
||||||
|
DragDropProvider,
|
||||||
|
DragDropSensors,
|
||||||
|
DragOverlay,
|
||||||
|
SortableProvider,
|
||||||
|
closestCenter,
|
||||||
|
createSortable,
|
||||||
|
useDragDropContext,
|
||||||
|
} from "@thisbeyond/solid-dnd"
|
||||||
|
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
|
||||||
|
|
||||||
export default function Layout(props: ParentProps) {
|
export default function Layout(props: ParentProps) {
|
||||||
const navigate = useNavigate()
|
const [store, setStore] = createStore({
|
||||||
|
lastSession: {} as { [directory: string]: string },
|
||||||
|
activeDraggable: undefined as string | undefined,
|
||||||
|
})
|
||||||
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
|
const platform = usePlatform()
|
||||||
|
const navigate = useNavigate()
|
||||||
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
|
||||||
const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? [])
|
const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? [])
|
||||||
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
|
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
|
||||||
|
|
||||||
|
function navigateToProject(directory: string | undefined) {
|
||||||
|
if (!directory) return
|
||||||
|
const lastSession = store.lastSession[directory]
|
||||||
|
navigate(`/${base64Encode(directory)}${lastSession ? `/session/${lastSession}` : ""}`)
|
||||||
|
}
|
||||||
|
|
||||||
function navigateToSession(session: Session | undefined) {
|
function navigateToSession(session: Session | undefined) {
|
||||||
if (!session) return
|
if (!session) return
|
||||||
navigate(`/${params.dir}/session/${session?.id}`)
|
navigate(`/${params.dir}/session/${session?.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOpenProject = async () => {
|
function openProject(directory: string, navigate = true) {
|
||||||
// layout.projects.open(dir.)
|
layout.projects.open(directory)
|
||||||
|
if (navigate) navigateToProject(directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeProject(directory: string) {
|
||||||
|
layout.projects.close(directory)
|
||||||
|
// TODO: more intelligent navigation
|
||||||
|
navigate("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function chooseProject() {
|
||||||
|
const result = await platform.openDirectoryPickerDialog?.({
|
||||||
|
title: "Open project",
|
||||||
|
multiple: true,
|
||||||
|
})
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
for (const directory of result) {
|
||||||
|
openProject(directory, false)
|
||||||
|
}
|
||||||
|
navigateToProject(result[0])
|
||||||
|
} else if (result) {
|
||||||
|
openProject(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!params.dir || !params.id) return
|
||||||
|
const directory = base64Decode(params.dir)
|
||||||
|
setStore("lastSession", directory, params.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
function getDraggableId(event: unknown): string | undefined {
|
||||||
|
if (typeof event !== "object" || event === null) return undefined
|
||||||
|
if (!("draggable" in event)) return undefined
|
||||||
|
const draggable = (event as { draggable?: { id?: unknown } }).draggable
|
||||||
|
if (!draggable) return undefined
|
||||||
|
return typeof draggable.id === "string" ? draggable.id : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragStart(event: unknown) {
|
||||||
|
const id = getDraggableId(event)
|
||||||
|
if (!id) return
|
||||||
|
setStore("activeDraggable", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragOver(event: DragEvent) {
|
||||||
|
const { draggable, droppable } = event
|
||||||
|
if (draggable && droppable) {
|
||||||
|
const projects = layout.projects.list()
|
||||||
|
const fromIndex = projects.findIndex((p) => p.worktree === draggable.id.toString())
|
||||||
|
const toIndex = projects.findIndex((p) => p.worktree === droppable.id.toString())
|
||||||
|
if (fromIndex !== toIndex && toIndex !== -1) {
|
||||||
|
layout.projects.move(draggable.id.toString(), toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragEnd() {
|
||||||
|
setStore("activeDraggable", undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConstrainDragXAxis = (): JSX.Element => {
|
||||||
|
const context = useDragDropContext()
|
||||||
|
if (!context) return <></>
|
||||||
|
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
|
||||||
|
const transformer: Transformer = {
|
||||||
|
id: "constrain-x-axis",
|
||||||
|
order: 100,
|
||||||
|
callback: (transform) => ({ ...transform, x: 0 }),
|
||||||
|
}
|
||||||
|
onDragStart((event) => {
|
||||||
|
const id = getDraggableId(event)
|
||||||
|
if (!id) return
|
||||||
|
addTransformer("draggables", id, transformer)
|
||||||
|
})
|
||||||
|
onDragEnd((event) => {
|
||||||
|
const id = getDraggableId(event)
|
||||||
|
if (!id) return
|
||||||
|
removeTransformer("draggables", id, transformer.id)
|
||||||
|
})
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectVisual = (props: { project: Project & { expanded: boolean }; class?: string }): JSX.Element => {
|
||||||
|
const name = createMemo(() => getFilename(props.project.worktree))
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Match when={layout.sidebar.opened()}>
|
||||||
|
<Button
|
||||||
|
as={"div"}
|
||||||
|
variant="ghost"
|
||||||
|
data-active
|
||||||
|
class="flex items-center justify-between gap-3 w-full px-1 self-stretch h-8 border-none rounded-lg"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3 p-0 text-left min-w-0 grow">
|
||||||
|
<div class="size-6 shrink-0">
|
||||||
|
<Avatar
|
||||||
|
fallback={name()}
|
||||||
|
src={props.project.icon?.url}
|
||||||
|
background={props.project.icon?.color ?? "var(--surface-info-base)"}
|
||||||
|
class="size-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="truncate text-14-medium text-text-strong">{name()}</span>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Match>
|
||||||
|
<Match when={true}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="large"
|
||||||
|
class="flex items-center justify-center p-0 aspect-square border-none rounded-lg"
|
||||||
|
data-selected={props.project.worktree === currentDirectory()}
|
||||||
|
onClick={() => navigateToProject(props.project.worktree)}
|
||||||
|
>
|
||||||
|
<div class="size-6 shrink-0">
|
||||||
|
<Avatar
|
||||||
|
fallback={name()}
|
||||||
|
src={props.project.icon?.url}
|
||||||
|
background={props.project.icon?.color ?? "var(--surface-info-base)"}
|
||||||
|
class="size-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SortableProject = (props: { project: Project & { expanded: boolean } }): JSX.Element => {
|
||||||
|
const sortable = createSortable(props.project.worktree)
|
||||||
|
const [projectStore] = globalSync.child(props.project.worktree)
|
||||||
|
const slug = createMemo(() => base64Encode(props.project.worktree))
|
||||||
|
const name = createMemo(() => getFilename(props.project.worktree))
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
|
||||||
|
<Switch>
|
||||||
|
<Match when={layout.sidebar.opened()}>
|
||||||
|
<Collapsible variant="ghost" defaultOpen class="gap-2 shrink-0">
|
||||||
|
<Button
|
||||||
|
as={"div"}
|
||||||
|
variant="ghost"
|
||||||
|
class="group/session flex items-center justify-between gap-3 w-full px-1 self-stretch h-auto border-none rounded-lg"
|
||||||
|
>
|
||||||
|
<Collapsible.Trigger class="group/trigger flex items-center gap-3 p-0 text-left min-w-0 grow border-none">
|
||||||
|
<div class="size-6 shrink-0">
|
||||||
|
<Avatar
|
||||||
|
fallback={name()}
|
||||||
|
src={props.project.icon?.url}
|
||||||
|
background={props.project.icon?.color ?? "var(--surface-info-base)"}
|
||||||
|
class="size-full group-hover/session:hidden"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name="chevron-right"
|
||||||
|
size="large"
|
||||||
|
class="hidden size-full items-center justify-center text-text-subtle group-hover/session:flex group-data-[expanded]/trigger:rotate-90 transition-transform duration-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="truncate text-14-medium text-text-strong">{name()}</span>
|
||||||
|
</Collapsible.Trigger>
|
||||||
|
<div class="flex invisible gap-1 items-center group-hover/session:visible has-[[data-expanded]]:visible">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenu.Trigger as={IconButton} icon="dot-grid" variant="ghost" />
|
||||||
|
<DropdownMenu.Portal>
|
||||||
|
<DropdownMenu.Content>
|
||||||
|
<DropdownMenu.Item onSelect={() => closeProject(props.project.worktree)}>
|
||||||
|
<DropdownMenu.ItemLabel>Close Project</DropdownMenu.ItemLabel>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Tooltip placement="top" value="New session">
|
||||||
|
<IconButton as={A} href={`${slug()}/session`} icon="plus-small" variant="ghost" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<Collapsible.Content>
|
||||||
|
<nav class="hidden @[4rem]:flex w-full flex-col gap-1.5">
|
||||||
|
<For each={projectStore.session}>
|
||||||
|
{(session) => {
|
||||||
|
const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
|
||||||
|
return (
|
||||||
|
<A
|
||||||
|
data-active={session.id === params.id}
|
||||||
|
href={`${slug()}/session/${session.id}`}
|
||||||
|
class="group/session focus:outline-none cursor-default"
|
||||||
|
>
|
||||||
|
<Tooltip placement="right" value={session.title}>
|
||||||
|
<div
|
||||||
|
class="w-full pl-4 pr-2 py-1 rounded-md
|
||||||
|
group-data-[active=true]/session:bg-surface-raised-base-hover
|
||||||
|
group-hover/session:bg-surface-raised-base-hover
|
||||||
|
group-focus/session:bg-surface-raised-base-hover"
|
||||||
|
>
|
||||||
|
<div class="flex items-center self-stretch gap-6 justify-between">
|
||||||
|
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
|
||||||
|
{session.title}
|
||||||
|
</span>
|
||||||
|
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
|
||||||
|
{Math.abs(updated().diffNow().as("seconds")) < 60
|
||||||
|
? "Now"
|
||||||
|
: updated()
|
||||||
|
.toRelative({
|
||||||
|
style: "short",
|
||||||
|
unit: ["days", "hours", "minutes"],
|
||||||
|
})
|
||||||
|
?.replace(" ago", "")
|
||||||
|
?.replace(/ days?/, "d")
|
||||||
|
?.replace(" min.", "m")
|
||||||
|
?.replace(" hr.", "h")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="hidden _flex justify-between items-center self-stretch">
|
||||||
|
<span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
|
||||||
|
<Show when={session.summary}>{(summary) => <DiffChanges changes={summary()} />}</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</A>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</nav>
|
||||||
|
</Collapsible.Content>
|
||||||
|
</Collapsible>
|
||||||
|
</Match>
|
||||||
|
<Match when={true}>
|
||||||
|
<Tooltip placement="right" value={props.project.worktree}>
|
||||||
|
<ProjectVisual project={props.project} />
|
||||||
|
</Tooltip>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectDragOverlay = (): JSX.Element => {
|
||||||
|
const project = createMemo(() => layout.projects.list().find((p) => p.worktree === store.activeDraggable))
|
||||||
|
return (
|
||||||
|
<Show when={project()}>
|
||||||
|
{(p) => (
|
||||||
|
<div class="bg-background-base rounded-md">
|
||||||
|
<ProjectVisual project={p()} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -50,61 +323,75 @@ export default function Layout(props: ParentProps) {
|
|||||||
<Mark class="shrink-0" />
|
<Mark class="shrink-0" />
|
||||||
</A>
|
</A>
|
||||||
<div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
|
<div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
|
||||||
<div class="flex items-center gap-3">
|
<Show when={params.dir && layout.projects.list().length > 0}>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-3">
|
||||||
<Select
|
<div class="flex items-center gap-2">
|
||||||
options={layout.projects.list().map((project) => getFilename(project.directory))}
|
<Select
|
||||||
current={getFilename(currentDirectory())}
|
options={layout.projects.list().map((project) => project.worktree)}
|
||||||
class="text-14-regular text-text-base"
|
current={currentDirectory()}
|
||||||
variant="ghost"
|
label={(x) => getFilename(x)}
|
||||||
/>
|
onSelect={(x) => (x ? navigateToProject(x) : undefined)}
|
||||||
<div class="text-text-weaker">/</div>
|
class="text-14-regular text-text-base"
|
||||||
<Select
|
variant="ghost"
|
||||||
options={sessions()}
|
>
|
||||||
current={currentSession()}
|
{/* @ts-ignore */}
|
||||||
placeholder="Select session"
|
{(i) => (
|
||||||
label={(x) => x.title}
|
<div class="flex items-center gap-2">
|
||||||
value={(x) => x.id}
|
<Icon name="folder" size="small" />
|
||||||
onSelect={navigateToSession}
|
<div class="text-text-strong">{getFilename(i)}</div>
|
||||||
class="text-14-regular text-text-base max-w-md"
|
</div>
|
||||||
variant="ghost"
|
)}
|
||||||
/>
|
</Select>
|
||||||
|
<div class="text-text-weaker">/</div>
|
||||||
|
<Select
|
||||||
|
options={sessions()}
|
||||||
|
current={currentSession()}
|
||||||
|
placeholder="New session"
|
||||||
|
label={(x) => x.title}
|
||||||
|
value={(x) => x.id}
|
||||||
|
onSelect={navigateToSession}
|
||||||
|
class="text-14-regular text-text-base max-w-md"
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Show when={currentSession()}>
|
||||||
|
<Button as={A} href={`/${params.dir}/session`} icon="plus-small">
|
||||||
|
New session
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Button as={A} href={`/${params.dir}/session`} icon="plus-small">
|
<div class="flex items-center gap-4">
|
||||||
New session
|
<Tooltip
|
||||||
</Button>
|
class="shrink-0"
|
||||||
</div>
|
value={
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-2">
|
||||||
<Tooltip
|
<span>Toggle terminal</span>
|
||||||
class="shrink-0"
|
<span class="text-icon-base text-12-medium">Ctrl `</span>
|
||||||
value={
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
}
|
||||||
<span>Toggle terminal</span>
|
>
|
||||||
<span class="text-icon-base text-12-medium">Ctrl `</span>
|
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
|
||||||
</div>
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
}
|
<Icon
|
||||||
>
|
size="small"
|
||||||
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
|
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
|
||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
class="group-hover/terminal-toggle:hidden"
|
||||||
<Icon
|
/>
|
||||||
size="small"
|
<Icon
|
||||||
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
|
size="small"
|
||||||
class="group-hover/terminal-toggle:hidden"
|
name="layout-bottom-partial"
|
||||||
/>
|
class="hidden group-hover/terminal-toggle:inline-block"
|
||||||
<Icon
|
/>
|
||||||
size="small"
|
<Icon
|
||||||
name="layout-bottom-partial"
|
size="small"
|
||||||
class="hidden group-hover/terminal-toggle:inline-block"
|
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
|
||||||
/>
|
class="hidden group-active/terminal-toggle:inline-block"
|
||||||
<Icon
|
/>
|
||||||
size="small"
|
</div>
|
||||||
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
|
</Button>
|
||||||
class="hidden group-active/terminal-toggle:inline-block"
|
</Tooltip>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</Show>
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="h-[calc(100vh-3rem)] flex">
|
<div class="h-[calc(100vh-3rem)] flex">
|
||||||
@@ -127,12 +414,12 @@ export default function Layout(props: ParentProps) {
|
|||||||
onCollapse={layout.sidebar.close}
|
onCollapse={layout.sidebar.close}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="grow flex flex-col items-start self-stretch gap-4 p-2 min-h-0">
|
<div class="flex flex-col items-start self-stretch gap-4 p-2 min-h-0 overflow-hidden">
|
||||||
<Tooltip class="shrink-0" placement="right" value="Toggle sidebar" inactive={layout.sidebar.opened()}>
|
<Tooltip class="shrink-0" placement="right" value="Toggle sidebar" inactive={layout.sidebar.opened()}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="large"
|
size="large"
|
||||||
class="group/sidebar-toggle shrink-0 w-full text-left justify-start"
|
class="group/sidebar-toggle shrink-0 w-full text-left justify-start rounded-lg"
|
||||||
onClick={layout.sidebar.toggle}
|
onClick={layout.sidebar.toggle}
|
||||||
>
|
>
|
||||||
<div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
@@ -159,103 +446,42 @@ export default function Layout(props: ParentProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div class="flex flex-col justify-center items-start gap-4 self-stretch min-h-0">
|
<DragDropProvider
|
||||||
<div class="hidden @[4rem]:flex size-full flex-col grow overflow-y-auto no-scrollbar">
|
onDragStart={handleDragStart}
|
||||||
<For each={layout.projects.list()}>
|
onDragEnd={handleDragEnd}
|
||||||
{(project) => {
|
onDragOver={handleDragOver}
|
||||||
const [store] = globalSync.child(project.directory)
|
collisionDetector={closestCenter}
|
||||||
const slug = createMemo(() => base64Encode(project.directory))
|
>
|
||||||
return (
|
<DragDropSensors />
|
||||||
<Collapsible variant="ghost" defaultOpen class="gap-2">
|
<ConstrainDragXAxis />
|
||||||
<Button
|
<div class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar">
|
||||||
as={"div"}
|
<SortableProvider ids={layout.projects.list().map((p) => p.worktree)}>
|
||||||
variant="ghost"
|
<For each={layout.projects.list()}>{(project) => <SortableProject project={project} />}</For>
|
||||||
class="flex items-center justify-between gap-3 w-full h-8 pl-2 pr-2.25 self-stretch"
|
</SortableProvider>
|
||||||
>
|
|
||||||
<Collapsible.Trigger class="p-0 text-left text-14-medium text-text-strong grow min-w-0 truncate">
|
|
||||||
{getFilename(project.directory)}
|
|
||||||
</Collapsible.Trigger>
|
|
||||||
<IconButton as={A} href={`${slug()}/session`} icon="plus-small" size="normal" />
|
|
||||||
</Button>
|
|
||||||
<Collapsible.Content>
|
|
||||||
<nav class="w-full flex flex-col gap-1.5">
|
|
||||||
<For each={store.session}>
|
|
||||||
{(session) => {
|
|
||||||
const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
|
|
||||||
return (
|
|
||||||
<A
|
|
||||||
data-active={session.id === params.id}
|
|
||||||
href={`${slug()}/session/${session.id}`}
|
|
||||||
class="group/session focus:outline-none cursor-default"
|
|
||||||
>
|
|
||||||
<Tooltip placement="right" value={session.title}>
|
|
||||||
<div
|
|
||||||
class="w-full px-2 py-1 rounded-md
|
|
||||||
group-data-[active=true]/session:bg-surface-raised-base-hover
|
|
||||||
group-hover/session:bg-surface-raised-base-hover
|
|
||||||
group-focus/session:bg-surface-raised-base-hover"
|
|
||||||
>
|
|
||||||
<div class="flex items-center self-stretch gap-6 justify-between">
|
|
||||||
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
|
|
||||||
{session.title}
|
|
||||||
</span>
|
|
||||||
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
|
|
||||||
{Math.abs(updated().diffNow().as("seconds")) < 60
|
|
||||||
? "Now"
|
|
||||||
: updated()
|
|
||||||
.toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
|
|
||||||
?.replace(" ago", "")
|
|
||||||
?.replace(/ days?/, "d")
|
|
||||||
?.replace(" min.", "m")
|
|
||||||
?.replace(" hr.", "h")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="hidden _flex justify-between items-center self-stretch">
|
|
||||||
<span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
|
|
||||||
<Show when={session.summary}>
|
|
||||||
{(summary) => <DiffChanges changes={summary()} />}
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</A>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</nav>
|
|
||||||
{/* <Show when={sync.session.more()}> */}
|
|
||||||
{/* <button */}
|
|
||||||
{/* class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong" */}
|
|
||||||
{/* onClick={() => sync.session.fetch()} */}
|
|
||||||
{/* > */}
|
|
||||||
{/* Show more */}
|
|
||||||
{/* </button> */}
|
|
||||||
{/* </Show> */}
|
|
||||||
</Collapsible.Content>
|
|
||||||
</Collapsible>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<DragOverlay>
|
||||||
|
<ProjectDragOverlay />
|
||||||
|
</DragOverlay>
|
||||||
|
</DragDropProvider>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
|
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
|
||||||
<Tooltip placement="right" value="Open project" inactive={layout.sidebar.opened()}>
|
<Show when={platform.openDirectoryPickerDialog}>
|
||||||
<Button
|
<Tooltip placement="right" value="Open project" inactive={layout.sidebar.opened()}>
|
||||||
disabled
|
<Button
|
||||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
|
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="large"
|
size="large"
|
||||||
icon="folder-add-left"
|
icon="folder-add-left"
|
||||||
onClick={handleOpenProject}
|
onClick={chooseProject}
|
||||||
>
|
>
|
||||||
<Show when={layout.sidebar.opened()}>Open project</Show>
|
<Show when={layout.sidebar.opened()}>Open project</Show>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</Show>
|
||||||
<Tooltip placement="right" value="Settings" inactive={layout.sidebar.opened()}>
|
<Tooltip placement="right" value="Settings" inactive={layout.sidebar.opened()}>
|
||||||
<Button
|
<Button
|
||||||
disabled
|
disabled
|
||||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
|
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="large"
|
size="large"
|
||||||
icon="settings-gear"
|
icon="settings-gear"
|
||||||
@@ -268,7 +494,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
as={"a"}
|
as={"a"}
|
||||||
href="https://opencode.ai/desktop-feedback"
|
href="https://opencode.ai/desktop-feedback"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
|
class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px] rounded-lg"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="large"
|
size="large"
|
||||||
icon="bubble-5"
|
icon="bubble-5"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
|
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
|
||||||
import type { JSX } from "solid-js"
|
import type { JSX } from "solid-js"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useSession } from "@/context/session"
|
import { useSession, type LocalPTY } from "@/context/session"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { Terminal } from "@/components/terminal"
|
import { Terminal } from "@/components/terminal"
|
||||||
@@ -43,6 +43,7 @@ export default function Page() {
|
|||||||
clickTimer: undefined as number | undefined,
|
clickTimer: undefined as number | undefined,
|
||||||
fileSelectOpen: false,
|
fileSelectOpen: false,
|
||||||
activeDraggable: undefined as string | undefined,
|
activeDraggable: undefined as string | undefined,
|
||||||
|
activeTerminalDraggable: undefined as string | undefined,
|
||||||
})
|
})
|
||||||
let inputRef!: HTMLDivElement
|
let inputRef!: HTMLDivElement
|
||||||
|
|
||||||
@@ -178,6 +179,49 @@ export default function Page() {
|
|||||||
setStore("activeDraggable", undefined)
|
setStore("activeDraggable", undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTerminalDragStart = (event: unknown) => {
|
||||||
|
const id = getDraggableId(event)
|
||||||
|
if (!id) return
|
||||||
|
setStore("activeTerminalDraggable", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTerminalDragOver = (event: DragEvent) => {
|
||||||
|
const { draggable, droppable } = event
|
||||||
|
if (draggable && droppable) {
|
||||||
|
const terminals = session.terminal.all()
|
||||||
|
const fromIndex = terminals.findIndex((t) => t.id === draggable.id.toString())
|
||||||
|
const toIndex = terminals.findIndex((t) => t.id === droppable.id.toString())
|
||||||
|
if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) {
|
||||||
|
session.terminal.move(draggable.id.toString(), toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTerminalDragEnd = () => {
|
||||||
|
setStore("activeTerminalDraggable", undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SortableTerminalTab = (props: { terminal: LocalPTY }): JSX.Element => {
|
||||||
|
const sortable = createSortable(props.terminal.id)
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
|
||||||
|
<div class="relative h-full">
|
||||||
|
<Tabs.Trigger
|
||||||
|
value={props.terminal.id}
|
||||||
|
closeButton={
|
||||||
|
session.terminal.all().length > 1 && (
|
||||||
|
<IconButton icon="close" variant="ghost" onClick={() => session.terminal.close(props.terminal.id)} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.terminal.title}
|
||||||
|
</Tabs.Trigger>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const FileVisual = (props: { file: LocalFile; active?: boolean }): JSX.Element => {
|
const FileVisual = (props: { file: LocalFile; active?: boolean }): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center gap-x-1.5">
|
<div class="flex items-center gap-x-1.5">
|
||||||
@@ -220,7 +264,6 @@ export default function Page() {
|
|||||||
onTabClose: (tab: string) => void
|
onTabClose: (tab: string) => void
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const sortable = createSortable(props.tab)
|
const sortable = createSortable(props.tab)
|
||||||
|
|
||||||
const [file] = createResource(
|
const [file] = createResource(
|
||||||
() => props.tab,
|
() => props.tab,
|
||||||
async (tab) => {
|
async (tab) => {
|
||||||
@@ -230,7 +273,6 @@ export default function Page() {
|
|||||||
return undefined
|
return undefined
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
|
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
|
||||||
@@ -403,15 +445,19 @@ export default function Page() {
|
|||||||
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
|
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center items-center gap-3">
|
<Show when={sync.project}>
|
||||||
<Icon name="pencil-line" size="small" />
|
{(project) => (
|
||||||
<div class="text-12-medium text-text-weak">
|
<div class="flex justify-center items-center gap-3">
|
||||||
Last modified
|
<Icon name="pencil-line" size="small" />
|
||||||
<span class="text-text-strong">
|
<div class="text-12-medium text-text-weak">
|
||||||
{DateTime.fromMillis(sync.data.project.time.created).toRelative()}
|
Last modified
|
||||||
</span>
|
<span class="text-text-strong">
|
||||||
</div>
|
{DateTime.fromMillis(project().time.updated ?? project().time.created).toRelative()}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
@@ -576,7 +622,6 @@ export default function Page() {
|
|||||||
onOpenChange={(open) => setStore("fileSelectOpen", open)}
|
onOpenChange={(open) => setStore("fileSelectOpen", open)}
|
||||||
onSelect={(x) => {
|
onSelect={(x) => {
|
||||||
if (x) {
|
if (x) {
|
||||||
local.file.open(x)
|
|
||||||
return session.layout.openTab("file://" + x)
|
return session.layout.openTab("file://" + x)
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
@@ -617,40 +662,54 @@ export default function Page() {
|
|||||||
onResize={layout.terminal.resize}
|
onResize={layout.terminal.resize}
|
||||||
onCollapse={layout.terminal.close}
|
onCollapse={layout.terminal.close}
|
||||||
/>
|
/>
|
||||||
<Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}>
|
<DragDropProvider
|
||||||
<Tabs.List class="h-10">
|
onDragStart={handleTerminalDragStart}
|
||||||
|
onDragEnd={handleTerminalDragEnd}
|
||||||
|
onDragOver={handleTerminalDragOver}
|
||||||
|
collisionDetector={closestCenter}
|
||||||
|
>
|
||||||
|
<DragDropSensors />
|
||||||
|
<ConstrainDragYAxis />
|
||||||
|
<Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}>
|
||||||
|
<Tabs.List class="h-10">
|
||||||
|
<SortableProvider ids={session.terminal.all().map((t) => t.id)}>
|
||||||
|
<For each={session.terminal.all()}>{(terminal) => <SortableTerminalTab terminal={terminal} />}</For>
|
||||||
|
</SortableProvider>
|
||||||
|
<div class="h-full flex items-center justify-center">
|
||||||
|
<Tooltip value="Open file" class="flex items-center">
|
||||||
|
<IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={session.terminal.new} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Tabs.List>
|
||||||
<For each={session.terminal.all()}>
|
<For each={session.terminal.all()}>
|
||||||
{(terminal) => (
|
{(terminal) => (
|
||||||
<Tabs.Trigger
|
<Tabs.Content value={terminal.id}>
|
||||||
value={terminal.id}
|
<Terminal
|
||||||
closeButton={
|
pty={terminal}
|
||||||
session.terminal.all().length > 1 && (
|
onCleanup={session.terminal.update}
|
||||||
<IconButton icon="close" variant="ghost" onClick={() => session.terminal.close(terminal.id)} />
|
onConnectError={() => session.terminal.clone(terminal.id)}
|
||||||
)
|
/>
|
||||||
}
|
</Tabs.Content>
|
||||||
>
|
|
||||||
{terminal.title}
|
|
||||||
</Tabs.Trigger>
|
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
<div class="h-full flex items-center justify-center">
|
</Tabs>
|
||||||
<Tooltip value="Open file" class="flex items-center">
|
<DragOverlay>
|
||||||
<IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={session.terminal.new} />
|
<Show when={store.activeTerminalDraggable}>
|
||||||
</Tooltip>
|
{(draggedId) => {
|
||||||
</div>
|
const terminal = createMemo(() => session.terminal.all().find((t) => t.id === draggedId()))
|
||||||
</Tabs.List>
|
return (
|
||||||
<For each={session.terminal.all()}>
|
<Show when={terminal()}>
|
||||||
{(terminal) => (
|
{(t) => (
|
||||||
<Tabs.Content value={terminal.id}>
|
<div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
|
||||||
<Terminal
|
{t().title}
|
||||||
pty={terminal}
|
</div>
|
||||||
onCleanup={session.terminal.update}
|
)}
|
||||||
onConnectError={() => session.terminal.clone(terminal.id)}
|
</Show>
|
||||||
/>
|
)
|
||||||
</Tabs.Content>
|
}}
|
||||||
)}
|
</Show>
|
||||||
</For>
|
</DragOverlay>
|
||||||
</Tabs>
|
</DragDropProvider>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
@@ -11,7 +12,9 @@
|
|||||||
"jsxImportSource": "solid-js",
|
"jsxImportSource": "solid-js",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": false,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"outDir": "ts-dist",
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FileDiff, Message, Model, Part, Session, SessionStatus } from "@opencode-ai/sdk"
|
import { FileDiff, Message, Model, Part, Session } from "@opencode-ai/sdk/v2"
|
||||||
import { fn } from "@opencode-ai/util/fn"
|
import { fn } from "@opencode-ai/util/fn"
|
||||||
import { iife } from "@opencode-ai/util/iife"
|
import { iife } from "@opencode-ai/util/iife"
|
||||||
import { Identifier } from "@opencode-ai/util/identifier"
|
import { Identifier } from "@opencode-ai/util/identifier"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } from "@opencode-ai/sdk"
|
import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||||
import { DataProvider } from "@opencode-ai/ui/context"
|
import { DataProvider } from "@opencode-ai/ui/context"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "opencode"
|
id = "opencode"
|
||||||
name = "OpenCode"
|
name = "OpenCode"
|
||||||
description = "The open source coding agent."
|
description = "The open source coding agent."
|
||||||
version = "1.0.137"
|
version = "1.0.138"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Anomaly"]
|
authors = ["Anomaly"]
|
||||||
repository = "https://github.com/sst/opencode"
|
repository = "https://github.com/sst/opencode"
|
||||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
|||||||
icon = "./icons/opencode.svg"
|
icon = "./icons/opencode.svg"
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-aarch64]
|
[agent_servers.opencode.targets.darwin-aarch64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.137/opencode-darwin-arm64.zip"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-darwin-arm64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-x86_64]
|
[agent_servers.opencode.targets.darwin-x86_64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.137/opencode-darwin-x64.zip"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-darwin-x64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-aarch64]
|
[agent_servers.opencode.targets.linux-aarch64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.137/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-linux-arm64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-x86_64]
|
[agent_servers.opencode.targets.linux-x86_64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.137/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-linux-x64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.windows-x86_64]
|
[agent_servers.opencode.targets.windows-x86_64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.137/opencode-windows-x64.zip"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.138/opencode-windows-x64.zip"
|
||||||
cmd = "./opencode.exe"
|
cmd = "./opencode.exe"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ if (!Script.preview) {
|
|||||||
"license=('MIT')",
|
"license=('MIT')",
|
||||||
"provides=('opencode')",
|
"provides=('opencode')",
|
||||||
"conflicts=('opencode')",
|
"conflicts=('opencode')",
|
||||||
"depends=('fzf' 'ripgrep')",
|
"depends=('ripgrep')",
|
||||||
"",
|
"",
|
||||||
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
|
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
|
||||||
`sha256sums_aarch64=('${arm64Sha}')`,
|
`sha256sums_aarch64=('${arm64Sha}')`,
|
||||||
@@ -120,7 +120,7 @@ if (!Script.preview) {
|
|||||||
"license=('MIT')",
|
"license=('MIT')",
|
||||||
"provides=('opencode')",
|
"provides=('opencode')",
|
||||||
"conflicts=('opencode-bin')",
|
"conflicts=('opencode-bin')",
|
||||||
"depends=('fzf' 'ripgrep')",
|
"depends=('ripgrep')",
|
||||||
"makedepends=('git' 'bun-bin' 'go')",
|
"makedepends=('git' 'bun-bin' 'go')",
|
||||||
"",
|
"",
|
||||||
`source=("opencode-\${pkgver}.tar.gz::https://github.com/sst/opencode/archive/v\${pkgver}\${_subver}.tar.gz")`,
|
`source=("opencode-\${pkgver}.tar.gz::https://github.com/sst/opencode/archive/v\${pkgver}\${_subver}.tar.gz")`,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { Provider } from "../provider/provider"
|
|||||||
import { Installation } from "@/installation"
|
import { Installation } from "@/installation"
|
||||||
import { MessageV2 } from "@/session/message-v2"
|
import { MessageV2 } from "@/session/message-v2"
|
||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { MCP } from "@/mcp"
|
|
||||||
import { Todo } from "@/session/todo"
|
import { Todo } from "@/session/todo"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { LoadAPIKeyError } from "ai"
|
import { LoadAPIKeyError } from "ai"
|
||||||
|
|||||||
@@ -245,7 +245,12 @@ export namespace Agent {
|
|||||||
system.push(PROMPT_GENERATE)
|
system.push(PROMPT_GENERATE)
|
||||||
const existing = await list()
|
const existing = await list()
|
||||||
const result = await generateObject({
|
const result = await generateObject({
|
||||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
experimental_telemetry: {
|
||||||
|
isEnabled: cfg.experimental?.openTelemetry,
|
||||||
|
metadata: {
|
||||||
|
userId: cfg.username ?? "unknown",
|
||||||
|
},
|
||||||
|
},
|
||||||
temperature: 0.3,
|
temperature: 0.3,
|
||||||
prompt: [
|
prompt: [
|
||||||
...system.map(
|
...system.map(
|
||||||
|
|||||||
@@ -127,7 +127,18 @@ export namespace BunProc {
|
|||||||
|
|
||||||
await runInstall()
|
await runInstall()
|
||||||
|
|
||||||
parsed.dependencies[pkg] = version
|
// Resolve actual version from installed package when using "latest"
|
||||||
|
// This ensures subsequent starts use the cached version until explicitly updated
|
||||||
|
let resolvedVersion = version
|
||||||
|
if (version === "latest") {
|
||||||
|
const installedPkgJson = Bun.file(path.join(mod, "package.json"))
|
||||||
|
const installedPkg = await installedPkgJson.json().catch(() => null)
|
||||||
|
if (installedPkg?.version) {
|
||||||
|
resolvedVersion = installedPkg.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.dependencies[pkg] = resolvedVersion
|
||||||
await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2))
|
await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2))
|
||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import z from "zod"
|
||||||
|
import type { ZodType } from "zod"
|
||||||
|
import { Log } from "../util/log"
|
||||||
|
|
||||||
|
export namespace BusEvent {
|
||||||
|
const log = Log.create({ service: "event" })
|
||||||
|
|
||||||
|
export type Definition = ReturnType<typeof define>
|
||||||
|
|
||||||
|
const registry = new Map<string, Definition>()
|
||||||
|
|
||||||
|
export function define<Type extends string, Properties extends ZodType>(type: Type, properties: Properties) {
|
||||||
|
const result = {
|
||||||
|
type,
|
||||||
|
properties,
|
||||||
|
}
|
||||||
|
registry.set(type, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function payloads() {
|
||||||
|
return z
|
||||||
|
.discriminatedUnion(
|
||||||
|
"type",
|
||||||
|
registry
|
||||||
|
.entries()
|
||||||
|
.map(([type, def]) => {
|
||||||
|
return z
|
||||||
|
.object({
|
||||||
|
type: z.literal(type),
|
||||||
|
properties: def.properties,
|
||||||
|
})
|
||||||
|
.meta({
|
||||||
|
ref: "Event" + "." + def.type,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.toArray() as any,
|
||||||
|
)
|
||||||
|
.meta({
|
||||||
|
ref: "Event",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { EventEmitter } from "events"
|
|||||||
export const GlobalBus = new EventEmitter<{
|
export const GlobalBus = new EventEmitter<{
|
||||||
event: [
|
event: [
|
||||||
{
|
{
|
||||||
directory: string
|
directory?: string
|
||||||
payload: any
|
payload: any
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import type { ZodType } from "zod"
|
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
|
import { BusEvent } from "./bus-event"
|
||||||
import { GlobalBus } from "./global"
|
import { GlobalBus } from "./global"
|
||||||
|
|
||||||
export namespace Bus {
|
export namespace Bus {
|
||||||
@@ -9,10 +9,6 @@ export namespace Bus {
|
|||||||
type Subscription = (event: any) => void
|
type Subscription = (event: any) => void
|
||||||
const disposedEventType = "server.instance.disposed"
|
const disposedEventType = "server.instance.disposed"
|
||||||
|
|
||||||
export type EventDefinition = ReturnType<typeof event>
|
|
||||||
|
|
||||||
const registry = new Map<string, EventDefinition>()
|
|
||||||
|
|
||||||
const state = Instance.state(
|
const state = Instance.state(
|
||||||
() => {
|
() => {
|
||||||
const subscriptions = new Map<any, Subscription[]>()
|
const subscriptions = new Map<any, Subscription[]>()
|
||||||
@@ -36,46 +32,14 @@ export namespace Bus {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export function event<Type extends string, Properties extends ZodType>(type: Type, properties: Properties) {
|
export const InstanceDisposed = BusEvent.define(
|
||||||
const result = {
|
|
||||||
type,
|
|
||||||
properties,
|
|
||||||
}
|
|
||||||
registry.set(type, result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export const InstanceDisposed = event(
|
|
||||||
disposedEventType,
|
disposedEventType,
|
||||||
z.object({
|
z.object({
|
||||||
directory: z.string(),
|
directory: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export function payloads() {
|
export async function publish<Definition extends BusEvent.Definition>(
|
||||||
return z
|
|
||||||
.discriminatedUnion(
|
|
||||||
"type",
|
|
||||||
registry
|
|
||||||
.entries()
|
|
||||||
.map(([type, def]) => {
|
|
||||||
return z
|
|
||||||
.object({
|
|
||||||
type: z.literal(type),
|
|
||||||
properties: def.properties,
|
|
||||||
})
|
|
||||||
.meta({
|
|
||||||
ref: "Event" + "." + def.type,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.toArray() as any,
|
|
||||||
)
|
|
||||||
.meta({
|
|
||||||
ref: "Event",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function publish<Definition extends EventDefinition>(
|
|
||||||
def: Definition,
|
def: Definition,
|
||||||
properties: z.output<Definition["properties"]>,
|
properties: z.output<Definition["properties"]>,
|
||||||
) {
|
) {
|
||||||
@@ -100,14 +64,14 @@ export namespace Bus {
|
|||||||
return Promise.all(pending)
|
return Promise.all(pending)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribe<Definition extends EventDefinition>(
|
export function subscribe<Definition extends BusEvent.Definition>(
|
||||||
def: Definition,
|
def: Definition,
|
||||||
callback: (event: { type: Definition["type"]; properties: z.infer<Definition["properties"]> }) => void,
|
callback: (event: { type: Definition["type"]; properties: z.infer<Definition["properties"]> }) => void,
|
||||||
) {
|
) {
|
||||||
return raw(def.type, callback)
|
return raw(def.type, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function once<Definition extends EventDefinition>(
|
export function once<Definition extends BusEvent.Definition>(
|
||||||
def: Definition,
|
def: Definition,
|
||||||
callback: (event: {
|
callback: (event: {
|
||||||
type: Definition["type"]
|
type: Definition["type"]
|
||||||
|
|||||||
@@ -813,10 +813,10 @@ export function Prompt(props: PromptProps) {
|
|||||||
</text>
|
</text>
|
||||||
<Show when={store.mode === "normal"}>
|
<Show when={store.mode === "normal"}>
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
|
|
||||||
<text flexShrink={0} fg={theme.text}>
|
<text flexShrink={0} fg={theme.text}>
|
||||||
{local.model.parsed().model}
|
{local.model.parsed().model}
|
||||||
</text>
|
</text>
|
||||||
|
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
|
|
||||||
export const TuiEvent = {
|
export const TuiEvent = {
|
||||||
PromptAppend: Bus.event("tui.prompt.append", z.object({ text: z.string() })),
|
PromptAppend: BusEvent.define("tui.prompt.append", z.object({ text: z.string() })),
|
||||||
CommandExecute: Bus.event(
|
CommandExecute: BusEvent.define(
|
||||||
"tui.command.execute",
|
"tui.command.execute",
|
||||||
z.object({
|
z.object({
|
||||||
command: z.union([
|
command: z.union([
|
||||||
@@ -27,7 +28,7 @@ export const TuiEvent = {
|
|||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
ToastShow: Bus.event(
|
ToastShow: BusEvent.define(
|
||||||
"tui.toast.show",
|
"tui.toast.show",
|
||||||
z.object({
|
z.object({
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
|
import { Bus } from "@/bus"
|
||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { Identifier } from "../id/id"
|
import { Identifier } from "../id/id"
|
||||||
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
||||||
import PROMPT_REVIEW from "./template/review.txt"
|
import PROMPT_REVIEW from "./template/review.txt"
|
||||||
|
|
||||||
export namespace Command {
|
export namespace Command {
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Executed: Bus.event(
|
Executed: BusEvent.define(
|
||||||
"command.executed",
|
"command.executed",
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
import path from "path"
|
|
||||||
import { Global } from "../global"
|
|
||||||
import fs from "fs/promises"
|
|
||||||
import z from "zod"
|
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
|
||||||
import { lazy } from "../util/lazy"
|
|
||||||
import { Log } from "../util/log"
|
|
||||||
import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js"
|
|
||||||
|
|
||||||
export namespace Fzf {
|
|
||||||
const log = Log.create({ service: "fzf" })
|
|
||||||
|
|
||||||
const VERSION = "0.62.0"
|
|
||||||
const PLATFORM = {
|
|
||||||
darwin: { extension: "tar.gz" },
|
|
||||||
linux: { extension: "tar.gz" },
|
|
||||||
win32: { extension: "zip" },
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export const ExtractionFailedError = NamedError.create(
|
|
||||||
"FzfExtractionFailedError",
|
|
||||||
z.object({
|
|
||||||
filepath: z.string(),
|
|
||||||
stderr: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const UnsupportedPlatformError = NamedError.create(
|
|
||||||
"FzfUnsupportedPlatformError",
|
|
||||||
z.object({
|
|
||||||
platform: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const DownloadFailedError = NamedError.create(
|
|
||||||
"FzfDownloadFailedError",
|
|
||||||
z.object({
|
|
||||||
url: z.string(),
|
|
||||||
status: z.number(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const state = lazy(async () => {
|
|
||||||
let filepath = Bun.which("fzf")
|
|
||||||
if (filepath) {
|
|
||||||
log.info("found", { filepath })
|
|
||||||
return { filepath }
|
|
||||||
}
|
|
||||||
filepath = path.join(Global.Path.bin, "fzf" + (process.platform === "win32" ? ".exe" : ""))
|
|
||||||
|
|
||||||
const file = Bun.file(filepath)
|
|
||||||
if (!(await file.exists())) {
|
|
||||||
const archMap = { x64: "amd64", arm64: "arm64" } as const
|
|
||||||
const arch = archMap[process.arch as keyof typeof archMap] ?? "amd64"
|
|
||||||
|
|
||||||
const config = PLATFORM[process.platform as keyof typeof PLATFORM]
|
|
||||||
if (!config) throw new UnsupportedPlatformError({ platform: process.platform })
|
|
||||||
|
|
||||||
const version = VERSION
|
|
||||||
const platformName = process.platform === "win32" ? "windows" : process.platform
|
|
||||||
const filename = `fzf-${version}-${platformName}_${arch}.${config.extension}`
|
|
||||||
const url = `https://github.com/junegunn/fzf/releases/download/v${version}/${filename}`
|
|
||||||
|
|
||||||
const response = await fetch(url)
|
|
||||||
if (!response.ok) throw new DownloadFailedError({ url, status: response.status })
|
|
||||||
|
|
||||||
const buffer = await response.arrayBuffer()
|
|
||||||
const archivePath = path.join(Global.Path.bin, filename)
|
|
||||||
await Bun.write(archivePath, buffer)
|
|
||||||
if (config.extension === "tar.gz") {
|
|
||||||
const proc = Bun.spawn(["tar", "-xzf", archivePath, "fzf"], {
|
|
||||||
cwd: Global.Path.bin,
|
|
||||||
stderr: "pipe",
|
|
||||||
stdout: "pipe",
|
|
||||||
})
|
|
||||||
await proc.exited
|
|
||||||
if (proc.exitCode !== 0)
|
|
||||||
throw new ExtractionFailedError({
|
|
||||||
filepath,
|
|
||||||
stderr: await Bun.readableStreamToText(proc.stderr),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (config.extension === "zip") {
|
|
||||||
const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()])))
|
|
||||||
const entries = await zipFileReader.getEntries()
|
|
||||||
let fzfEntry: any
|
|
||||||
for (const entry of entries) {
|
|
||||||
if (entry.filename === "fzf.exe") {
|
|
||||||
fzfEntry = entry
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fzfEntry) {
|
|
||||||
throw new ExtractionFailedError({
|
|
||||||
filepath: archivePath,
|
|
||||||
stderr: "fzf.exe not found in zip archive",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fzfBlob = await fzfEntry.getData(new BlobWriter())
|
|
||||||
if (!fzfBlob) {
|
|
||||||
throw new ExtractionFailedError({
|
|
||||||
filepath: archivePath,
|
|
||||||
stderr: "Failed to extract fzf.exe from zip archive",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await Bun.write(filepath, await fzfBlob.arrayBuffer())
|
|
||||||
await zipFileReader.close()
|
|
||||||
}
|
|
||||||
await fs.unlink(archivePath)
|
|
||||||
if (process.platform !== "win32") await fs.chmod(filepath, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
filepath,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export async function filepath() {
|
|
||||||
const { filepath } = await state()
|
|
||||||
return filepath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import type { BunFile } from "bun"
|
import type { BunFile } from "bun"
|
||||||
import { formatPatch, structuredPatch } from "diff"
|
import { formatPatch, structuredPatch } from "diff"
|
||||||
@@ -111,7 +112,7 @@ export namespace File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Edited: Bus.event(
|
Edited: BusEvent.define(
|
||||||
"file.edited",
|
"file.edited",
|
||||||
z.object({
|
z.object({
|
||||||
file: z.string(),
|
file: z.string(),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { FileIgnore } from "./ignore"
|
import { FileIgnore } from "./ignore"
|
||||||
@@ -8,6 +9,7 @@ import { Config } from "../config/config"
|
|||||||
import { createWrapper } from "@parcel/watcher/wrapper"
|
import { createWrapper } from "@parcel/watcher/wrapper"
|
||||||
import { lazy } from "@/util/lazy"
|
import { lazy } from "@/util/lazy"
|
||||||
import type ParcelWatcher from "@parcel/watcher"
|
import type ParcelWatcher from "@parcel/watcher"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
declare const OPENCODE_LIBC: string | undefined
|
declare const OPENCODE_LIBC: string | undefined
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ export namespace FileWatcher {
|
|||||||
const log = Log.create({ service: "file.watcher" })
|
const log = Log.create({ service: "file.watcher" })
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Updated: Bus.event(
|
Updated: BusEvent.define(
|
||||||
"file.watcher.updated",
|
"file.watcher.updated",
|
||||||
z.object({
|
z.object({
|
||||||
file: z.string(),
|
file: z.string(),
|
||||||
@@ -65,7 +67,7 @@ export namespace FileWatcher {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const vcsDir = Instance.project.vcsDir
|
const vcsDir = await $`git rev-parse --git-dir`.quiet().nothrow().cwd(Instance.worktree).text()
|
||||||
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
|
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
|
||||||
subs.push(
|
subs.push(
|
||||||
await watcher().subscribe(vcsDir, subscribe, {
|
await watcher().subscribe(vcsDir, subscribe, {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ export namespace Flag {
|
|||||||
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
|
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
|
||||||
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
|
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
|
||||||
export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
|
export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
|
||||||
export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH =
|
export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
|
||||||
process.env["OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH"]
|
export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT")
|
||||||
|
|
||||||
// Experimental
|
// Experimental
|
||||||
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
|
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
|
||||||
@@ -25,4 +25,11 @@ export namespace Flag {
|
|||||||
const value = process.env[key]?.toLowerCase()
|
const value = process.env[key]?.toLowerCase()
|
||||||
return value === "true" || value === "1"
|
return value === "true" || value === "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function number(key: string) {
|
||||||
|
const value = process.env[key]
|
||||||
|
if (!value) return undefined
|
||||||
|
const parsed = Number(value)
|
||||||
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import { spawn } from "bun"
|
import { spawn } from "bun"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { Bus } from "../bus"
|
|
||||||
|
|
||||||
const SUPPORTED_IDES = [
|
const SUPPORTED_IDES = [
|
||||||
{ name: "Windsurf" as const, cmd: "windsurf" },
|
{ name: "Windsurf" as const, cmd: "windsurf" },
|
||||||
@@ -16,7 +17,7 @@ export namespace Ide {
|
|||||||
const log = Log.create({ service: "ide" })
|
const log = Log.create({ service: "ide" })
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Installed: Bus.event(
|
Installed: BusEvent.define(
|
||||||
"ide.installed",
|
"ide.installed",
|
||||||
z.object({
|
z.object({
|
||||||
ide: z.string(),
|
ide: z.string(),
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { iife } from "@/util/iife"
|
import { iife } from "@/util/iife"
|
||||||
|
|
||||||
@@ -17,13 +18,13 @@ export namespace Installation {
|
|||||||
export type Method = Awaited<ReturnType<typeof method>>
|
export type Method = Awaited<ReturnType<typeof method>>
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Updated: Bus.event(
|
Updated: BusEvent.define(
|
||||||
"installation.updated",
|
"installation.updated",
|
||||||
z.object({
|
z.object({
|
||||||
version: z.string(),
|
version: z.string(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
UpdateAvailable: Bus.event(
|
UpdateAvailable: BusEvent.define(
|
||||||
"installation.update-available",
|
"installation.update-available",
|
||||||
z.object({
|
z.object({
|
||||||
version: z.string(),
|
version: z.string(),
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
|
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
|
||||||
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
|
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { LANGUAGE_EXTENSIONS } from "./language"
|
import { LANGUAGE_EXTENSIONS } from "./language"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import type { LSPServer } from "./server"
|
import type { LSPServer } from "./server"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
@@ -25,7 +26,7 @@ export namespace LSPClient {
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Diagnostics: Bus.event(
|
Diagnostics: BusEvent.define(
|
||||||
"lsp.client.diagnostics",
|
"lsp.client.diagnostics",
|
||||||
z.object({
|
z.object({
|
||||||
serverID: z.string(),
|
serverID: z.string(),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { LSPClient } from "./client"
|
import { LSPClient } from "./client"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
@@ -6,13 +8,12 @@ import z from "zod"
|
|||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { spawn } from "child_process"
|
import { spawn } from "child_process"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Bus } from "../bus"
|
|
||||||
|
|
||||||
export namespace LSP {
|
export namespace LSP {
|
||||||
const log = Log.create({ service: "lsp" })
|
const log = Log.create({ service: "lsp" })
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Updated: Bus.event("lsp.updated", z.object({})),
|
Updated: BusEvent.define("lsp.updated", z.object({})),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Range = z
|
export const Range = z
|
||||||
|
|||||||
@@ -209,6 +209,68 @@ export namespace LSPServer {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Biome: Info = {
|
||||||
|
id: "biome",
|
||||||
|
root: NearestRoot([
|
||||||
|
"biome.json",
|
||||||
|
"biome.jsonc",
|
||||||
|
"package-lock.json",
|
||||||
|
"bun.lockb",
|
||||||
|
"bun.lock",
|
||||||
|
"pnpm-lock.yaml",
|
||||||
|
"yarn.lock",
|
||||||
|
]),
|
||||||
|
extensions: [
|
||||||
|
".ts",
|
||||||
|
".tsx",
|
||||||
|
".js",
|
||||||
|
".jsx",
|
||||||
|
".mjs",
|
||||||
|
".cjs",
|
||||||
|
".mts",
|
||||||
|
".cts",
|
||||||
|
".json",
|
||||||
|
".jsonc",
|
||||||
|
".vue",
|
||||||
|
".astro",
|
||||||
|
".svelte",
|
||||||
|
".css",
|
||||||
|
".graphql",
|
||||||
|
".gql",
|
||||||
|
".html",
|
||||||
|
],
|
||||||
|
async spawn(root) {
|
||||||
|
const localBin = path.join(root, "node_modules", ".bin", "biome")
|
||||||
|
let bin: string | undefined
|
||||||
|
if (await Bun.file(localBin).exists()) bin = localBin
|
||||||
|
if (!bin) {
|
||||||
|
const found = Bun.which("biome")
|
||||||
|
if (found) bin = found
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = ["lsp-proxy", "--stdio"]
|
||||||
|
|
||||||
|
if (!bin) {
|
||||||
|
const resolved = await Bun.resolve("biome", root).catch(() => undefined)
|
||||||
|
if (!resolved) return
|
||||||
|
bin = BunProc.which()
|
||||||
|
args = ["x", "biome", "lsp-proxy", "--stdio"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const proc = spawn(bin, args, {
|
||||||
|
cwd: root,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
BUN_BE_BUN: "1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
process: proc,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export const Gopls: Info = {
|
export const Gopls: Info = {
|
||||||
id: "gopls",
|
id: "gopls",
|
||||||
root: async (file) => {
|
root: async (file) => {
|
||||||
|
|||||||
@@ -189,14 +189,14 @@ export namespace MCP {
|
|||||||
name: "StreamableHTTP",
|
name: "StreamableHTTP",
|
||||||
transport: new StreamableHTTPClientTransport(new URL(mcp.url), {
|
transport: new StreamableHTTPClientTransport(new URL(mcp.url), {
|
||||||
authProvider,
|
authProvider,
|
||||||
requestInit: oauthDisabled && mcp.headers ? { headers: mcp.headers } : undefined,
|
requestInit: mcp.headers ? { headers: mcp.headers } : undefined,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SSE",
|
name: "SSE",
|
||||||
transport: new SSEClientTransport(new URL(mcp.url), {
|
transport: new SSEClientTransport(new URL(mcp.url), {
|
||||||
authProvider,
|
authProvider,
|
||||||
requestInit: oauthDisabled && mcp.headers ? { headers: mcp.headers } : undefined,
|
requestInit: mcp.headers ? { headers: mcp.headers } : undefined,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { Identifier } from "../id/id"
|
import { Identifier } from "../id/id"
|
||||||
import { Plugin } from "../plugin"
|
import { Plugin } from "../plugin"
|
||||||
@@ -38,8 +39,8 @@ export namespace Permission {
|
|||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Updated: Bus.event("permission.updated", Info),
|
Updated: BusEvent.define("permission.updated", Info),
|
||||||
Replied: Bus.event(
|
Replied: BusEvent.define(
|
||||||
"permission.replied",
|
"permission.replied",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import { Log } from "../util/log"
|
|||||||
import { Flag } from "@/flag/flag"
|
import { Flag } from "@/flag/flag"
|
||||||
import { Session } from "../session"
|
import { Session } from "../session"
|
||||||
import { work } from "../util/queue"
|
import { work } from "../util/queue"
|
||||||
|
import { fn } from "@opencode-ai/util/fn"
|
||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { iife } from "@/util/iife"
|
||||||
|
import { GlobalBus } from "@/bus/global"
|
||||||
|
|
||||||
export namespace Project {
|
export namespace Project {
|
||||||
const log = Log.create({ service: "project" })
|
const log = Log.create({ service: "project" })
|
||||||
@@ -14,10 +18,17 @@ export namespace Project {
|
|||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
worktree: z.string(),
|
worktree: z.string(),
|
||||||
vcsDir: z.string().optional(),
|
|
||||||
vcs: z.literal("git").optional(),
|
vcs: z.literal("git").optional(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
icon: z
|
||||||
|
.object({
|
||||||
|
url: z.string().optional(),
|
||||||
|
color: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
time: z.object({
|
time: z.object({
|
||||||
created: z.number(),
|
created: z.number(),
|
||||||
|
updated: z.number(),
|
||||||
initialized: z.number().optional(),
|
initialized: z.number().optional(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -26,74 +37,123 @@ export namespace Project {
|
|||||||
})
|
})
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
|
export const Event = {
|
||||||
|
Updated: BusEvent.define("project.updated", Info),
|
||||||
|
}
|
||||||
|
|
||||||
export async function fromDirectory(directory: string) {
|
export async function fromDirectory(directory: string) {
|
||||||
log.info("fromDirectory", { directory })
|
log.info("fromDirectory", { directory })
|
||||||
const matches = Filesystem.up({ targets: [".git"], start: directory })
|
|
||||||
const git = await matches.next().then((x) => x.value)
|
const { id, worktree, vcs } = await iife(async () => {
|
||||||
await matches.return()
|
const matches = Filesystem.up({ targets: [".git"], start: directory })
|
||||||
if (!git) {
|
const git = await matches.next().then((x) => x.value)
|
||||||
const project: Info = {
|
await matches.return()
|
||||||
|
if (git) {
|
||||||
|
let worktree = path.dirname(git)
|
||||||
|
let id = await Bun.file(path.join(git, "opencode"))
|
||||||
|
.text()
|
||||||
|
.then((x) => x.trim())
|
||||||
|
.catch(() => {})
|
||||||
|
if (!id) {
|
||||||
|
const roots = await $`git rev-list --max-parents=0 --all`
|
||||||
|
.quiet()
|
||||||
|
.nothrow()
|
||||||
|
.cwd(worktree)
|
||||||
|
.text()
|
||||||
|
.then((x) =>
|
||||||
|
x
|
||||||
|
.split("\n")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((x) => x.trim())
|
||||||
|
.toSorted(),
|
||||||
|
)
|
||||||
|
id = roots[0]
|
||||||
|
if (id) Bun.file(path.join(git, "opencode")).write(id)
|
||||||
|
}
|
||||||
|
if (!id)
|
||||||
|
return {
|
||||||
|
id: "global",
|
||||||
|
worktree,
|
||||||
|
vcs: "git",
|
||||||
|
}
|
||||||
|
worktree = await $`git rev-parse --show-toplevel`
|
||||||
|
.quiet()
|
||||||
|
.nothrow()
|
||||||
|
.cwd(worktree)
|
||||||
|
.text()
|
||||||
|
.then((x) => path.resolve(worktree, x.trim()))
|
||||||
|
return { id, worktree, vcs: "git" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
id: "global",
|
id: "global",
|
||||||
worktree: "/",
|
worktree: "/",
|
||||||
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
|
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let existing = await Storage.read<Info>(["project", id]).catch(() => undefined)
|
||||||
|
if (!existing) {
|
||||||
|
existing = {
|
||||||
|
id,
|
||||||
|
worktree,
|
||||||
|
vcs: vcs as Info["vcs"],
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
|
updated: Date.now(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await Storage.write<Info>(["project", "global"], project)
|
if (id !== "global") {
|
||||||
return project
|
await migrateFromGlobal(id, worktree)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let worktree = path.dirname(git)
|
discover(existing)
|
||||||
const timer = log.time("git.rev-parse")
|
const result: Info = {
|
||||||
let id = await Bun.file(path.join(git, "opencode"))
|
...existing,
|
||||||
.text()
|
|
||||||
.then((x) => x.trim())
|
|
||||||
.catch(() => {})
|
|
||||||
if (!id) {
|
|
||||||
const roots = await $`git rev-list --max-parents=0 --all`
|
|
||||||
.quiet()
|
|
||||||
.nothrow()
|
|
||||||
.cwd(worktree)
|
|
||||||
.text()
|
|
||||||
.then((x) =>
|
|
||||||
x
|
|
||||||
.split("\n")
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((x) => x.trim())
|
|
||||||
.toSorted(),
|
|
||||||
)
|
|
||||||
id = roots[0]
|
|
||||||
if (id) Bun.file(path.join(git, "opencode")).write(id)
|
|
||||||
}
|
|
||||||
timer.stop()
|
|
||||||
worktree = await $`git rev-parse --show-toplevel`
|
|
||||||
.quiet()
|
|
||||||
.nothrow()
|
|
||||||
.cwd(worktree)
|
|
||||||
.text()
|
|
||||||
.then((x) => path.resolve(worktree, x.trim()))
|
|
||||||
const vcsDir = await $`git rev-parse --git-dir`
|
|
||||||
.quiet()
|
|
||||||
.nothrow()
|
|
||||||
.cwd(worktree)
|
|
||||||
.text()
|
|
||||||
.then((x) => path.resolve(worktree, x.trim()))
|
|
||||||
const projectID = id || "global"
|
|
||||||
const existing = id ? await Storage.read<Info>(["project", id]).catch(() => undefined) : undefined
|
|
||||||
if (!existing && id) {
|
|
||||||
await migrateFromGlobal(projectID, worktree)
|
|
||||||
}
|
|
||||||
const project: Info = {
|
|
||||||
id: projectID,
|
|
||||||
worktree,
|
worktree,
|
||||||
vcsDir,
|
vcs: vcs as Info["vcs"],
|
||||||
vcs: "git",
|
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
...existing.time,
|
||||||
|
updated: Date.now(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await Storage.write<Info>(["project", projectID], project)
|
await Storage.write<Info>(["project", id], result)
|
||||||
return project
|
GlobalBus.emit("event", {
|
||||||
|
payload: {
|
||||||
|
type: Event.Updated.type,
|
||||||
|
properties: result,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function discover(input: Info) {
|
||||||
|
if (input.vcs !== "git") return
|
||||||
|
if (input.icon) return
|
||||||
|
const glob = new Bun.Glob("**/{favicon}.{ico,png,svg,jpg,jpeg,webp}")
|
||||||
|
const matches = await Array.fromAsync(
|
||||||
|
glob.scan({
|
||||||
|
cwd: input.worktree,
|
||||||
|
absolute: true,
|
||||||
|
onlyFiles: true,
|
||||||
|
followSymlinks: false,
|
||||||
|
dot: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const shortest = matches.sort((a, b) => a.length - b.length)[0]
|
||||||
|
if (!shortest) return
|
||||||
|
const file = Bun.file(shortest)
|
||||||
|
const buffer = await file.arrayBuffer()
|
||||||
|
const base64 = Buffer.from(buffer).toString("base64")
|
||||||
|
const mime = file.type || "image/png"
|
||||||
|
const url = `data:${mime};base64,${base64}`
|
||||||
|
await update({
|
||||||
|
projectID: input.id,
|
||||||
|
icon: {
|
||||||
|
url,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
async function migrateFromGlobal(newProjectID: string, worktree: string) {
|
async function migrateFromGlobal(newProjectID: string, worktree: string) {
|
||||||
@@ -130,4 +190,19 @@ export namespace Project {
|
|||||||
const keys = await Storage.list(["project"])
|
const keys = await Storage.list(["project"])
|
||||||
return await Promise.all(keys.map((x) => Storage.read<Info>(x)))
|
return await Promise.all(keys.map((x) => Storage.read<Info>(x)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const update = fn(
|
||||||
|
z.object({
|
||||||
|
projectID: z.string(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
icon: Info.shape.icon.optional(),
|
||||||
|
}),
|
||||||
|
async (input) => {
|
||||||
|
return await Storage.update<Info>(["project", input.projectID], (draft) => {
|
||||||
|
if (input.name !== undefined) draft.name = input.name
|
||||||
|
if (input.icon !== undefined) draft.icon = input.icon
|
||||||
|
draft.time.updated = Date.now()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
import { Bus } from "@/bus"
|
|
||||||
import { Instance } from "./instance"
|
import { Instance } from "./instance"
|
||||||
import { FileWatcher } from "@/file/watcher"
|
import { FileWatcher } from "@/file/watcher"
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ const log = Log.create({ service: "vcs" })
|
|||||||
|
|
||||||
export namespace Vcs {
|
export namespace Vcs {
|
||||||
export const Event = {
|
export const Event = {
|
||||||
BranchUpdated: Bus.event(
|
BranchUpdated: BusEvent.define(
|
||||||
"vcs.branch.updated",
|
"vcs.branch.updated",
|
||||||
z.object({
|
z.object({
|
||||||
branch: z.string().optional(),
|
branch: z.string().optional(),
|
||||||
@@ -39,16 +40,14 @@ export namespace Vcs {
|
|||||||
|
|
||||||
const state = Instance.state(
|
const state = Instance.state(
|
||||||
async () => {
|
async () => {
|
||||||
const vcsDir = Instance.project.vcsDir
|
if (Instance.project.vcs !== "git") {
|
||||||
if (Instance.project.vcs !== "git" || !vcsDir) {
|
|
||||||
return { branch: async () => undefined, unsubscribe: undefined }
|
return { branch: async () => undefined, unsubscribe: undefined }
|
||||||
}
|
}
|
||||||
let current = await currentBranch()
|
let current = await currentBranch()
|
||||||
log.info("initialized", { branch: current })
|
log.info("initialized", { branch: current })
|
||||||
|
|
||||||
const head = path.join(vcsDir, "HEAD")
|
|
||||||
const unsubscribe = Bus.subscribe(FileWatcher.Event.Updated, async (evt) => {
|
const unsubscribe = Bus.subscribe(FileWatcher.Event.Updated, async (evt) => {
|
||||||
if (evt.properties.file !== head) return
|
if (evt.properties.file.endsWith("HEAD")) return
|
||||||
const next = await currentBranch()
|
const next = await currentBranch()
|
||||||
if (next !== current) {
|
if (next !== current) {
|
||||||
log.info("branch changed", { from: current, to: next })
|
log.info("branch changed", { from: current, to: next })
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ export namespace ModelsDev {
|
|||||||
reasoning: z.boolean(),
|
reasoning: z.boolean(),
|
||||||
temperature: z.boolean(),
|
temperature: z.boolean(),
|
||||||
tool_call: z.boolean(),
|
tool_call: z.boolean(),
|
||||||
|
interleaved: z
|
||||||
|
.union([
|
||||||
|
z.literal(true),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
field: z.enum(["reasoning_content", "reasoning_details"]),
|
||||||
|
})
|
||||||
|
.strict(),
|
||||||
|
])
|
||||||
|
.optional(),
|
||||||
cost: z
|
cost: z
|
||||||
.object({
|
.object({
|
||||||
input: z.number(),
|
input: z.number(),
|
||||||
|
|||||||
@@ -349,6 +349,12 @@ export namespace Provider {
|
|||||||
video: z.boolean(),
|
video: z.boolean(),
|
||||||
pdf: z.boolean(),
|
pdf: z.boolean(),
|
||||||
}),
|
}),
|
||||||
|
interleaved: z.union([
|
||||||
|
z.boolean(),
|
||||||
|
z.object({
|
||||||
|
field: z.enum(["reasoning_content", "reasoning_details"]),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
cost: z.object({
|
cost: z.object({
|
||||||
input: z.number(),
|
input: z.number(),
|
||||||
@@ -450,6 +456,7 @@ export namespace Provider {
|
|||||||
video: model.modalities?.output?.includes("video") ?? false,
|
video: model.modalities?.output?.includes("video") ?? false,
|
||||||
pdf: model.modalities?.output?.includes("pdf") ?? false,
|
pdf: model.modalities?.output?.includes("pdf") ?? false,
|
||||||
},
|
},
|
||||||
|
interleaved: model.interleaved ?? false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,6 +574,7 @@ export namespace Provider {
|
|||||||
video: model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
|
video: model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
|
||||||
pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
|
pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
|
||||||
},
|
},
|
||||||
|
interleaved: model.interleaved ?? false,
|
||||||
},
|
},
|
||||||
cost: {
|
cost: {
|
||||||
input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
|
input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
|
||||||
|
|||||||
@@ -273,8 +273,24 @@ export namespace ProviderTransform {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
export function providerOptions(npm: string | undefined, providerID: string, options: { [x: string]: any }) {
|
export function providerOptions(model: Provider.Model, options: { [x: string]: any }, messages: ModelMessage[]) {
|
||||||
switch (npm) {
|
if (model.capabilities.interleaved && typeof model.capabilities.interleaved === "object") {
|
||||||
|
const cot = []
|
||||||
|
const assistantMessages = messages.filter((msg) => msg.role === "assistant")
|
||||||
|
for (const msg of assistantMessages) {
|
||||||
|
for (const part of msg.content) {
|
||||||
|
if (typeof part === "string") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (part.type === "reasoning") {
|
||||||
|
cot.push(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options[model.capabilities.interleaved.field] = cot
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (model.api.npm) {
|
||||||
case "@ai-sdk/openai":
|
case "@ai-sdk/openai":
|
||||||
case "@ai-sdk/azure":
|
case "@ai-sdk/azure":
|
||||||
return {
|
return {
|
||||||
@@ -302,7 +318,7 @@ export namespace ProviderTransform {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
[providerID]: options,
|
[model.providerID]: options,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import { type IPty } from "bun-pty"
|
import { type IPty } from "bun-pty"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Identifier } from "../id/id"
|
import { Identifier } from "../id/id"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import type { WSContext } from "hono/ws"
|
import type { WSContext } from "hono/ws"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { shell } from "@opencode-ai/util/shell"
|
import { shell } from "@opencode-ai/util/shell"
|
||||||
@@ -73,10 +74,10 @@ export namespace Pty {
|
|||||||
export type UpdateInput = z.infer<typeof UpdateInput>
|
export type UpdateInput = z.infer<typeof UpdateInput>
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Created: Bus.event("pty.created", z.object({ info: Info })),
|
Created: BusEvent.define("pty.created", z.object({ info: Info })),
|
||||||
Updated: Bus.event("pty.updated", z.object({ info: Info })),
|
Updated: BusEvent.define("pty.updated", z.object({ info: Info })),
|
||||||
Exited: Bus.event("pty.exited", z.object({ id: Identifier.schema("pty"), exitCode: z.number() })),
|
Exited: BusEvent.define("pty.exited", z.object({ id: Identifier.schema("pty"), exitCode: z.number() })),
|
||||||
Deleted: Bus.event("pty.deleted", z.object({ id: Identifier.schema("pty") })),
|
Deleted: BusEvent.define("pty.deleted", z.object({ id: Identifier.schema("pty") })),
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActiveSession {
|
interface ActiveSession {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
import { describeRoute } from "hono-openapi"
|
import { describeRoute, validator } from "hono-openapi"
|
||||||
import { resolver } from "hono-openapi"
|
import { resolver } from "hono-openapi"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Project } from "../project/project"
|
import { Project } from "../project/project"
|
||||||
|
import z from "zod"
|
||||||
|
import { errors } from "./error"
|
||||||
|
|
||||||
export const ProjectRoute = new Hono()
|
export const ProjectRoute = new Hono()
|
||||||
.get(
|
.get(
|
||||||
@@ -48,3 +50,30 @@ export const ProjectRoute = new Hono()
|
|||||||
return c.json(Instance.project)
|
return c.json(Instance.project)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.patch(
|
||||||
|
"/:projectID",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Update project",
|
||||||
|
description: "Update project properties such as name, icon and color.",
|
||||||
|
operationId: "project.update",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Updated project information",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Project.Info),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...errors(400, 404),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
validator("param", z.object({ projectID: z.string() })),
|
||||||
|
validator("json", Project.update.schema.omit({ projectID: true })),
|
||||||
|
async (c) => {
|
||||||
|
const projectID = c.req.valid("param").projectID
|
||||||
|
const body = c.req.valid("json")
|
||||||
|
const project = await Project.update({ ...body, projectID })
|
||||||
|
return c.json(project)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
|
import { GlobalBus } from "@/bus/global"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi"
|
import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi"
|
||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
import { cors } from "hono/cors"
|
import { cors } from "hono/cors"
|
||||||
@@ -8,7 +10,7 @@ import { proxy } from "hono/proxy"
|
|||||||
import { Session } from "../session"
|
import { Session } from "../session"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Provider } from "../provider/provider"
|
import { Provider } from "../provider/provider"
|
||||||
import { mapValues, pipe } from "remeda"
|
import { mapValues } from "remeda"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { ModelsDev } from "../provider/models"
|
import { ModelsDev } from "../provider/models"
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Ripgrep } from "../file/ripgrep"
|
||||||
@@ -41,7 +43,6 @@ import type { ContentfulStatusCode } from "hono/utils/http-status"
|
|||||||
import { TuiEvent } from "@/cli/cmd/tui/event"
|
import { TuiEvent } from "@/cli/cmd/tui/event"
|
||||||
import { Snapshot } from "@/snapshot"
|
import { Snapshot } from "@/snapshot"
|
||||||
import { SessionSummary } from "@/session/summary"
|
import { SessionSummary } from "@/session/summary"
|
||||||
import { GlobalBus } from "@/bus/global"
|
|
||||||
import { SessionStatus } from "@/session/status"
|
import { SessionStatus } from "@/session/status"
|
||||||
import { upgradeWebSocket, websocket } from "hono/bun"
|
import { upgradeWebSocket, websocket } from "hono/bun"
|
||||||
import { errors } from "./error"
|
import { errors } from "./error"
|
||||||
@@ -54,7 +55,7 @@ export namespace Server {
|
|||||||
const log = Log.create({ service: "server" })
|
const log = Log.create({ service: "server" })
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Connected: Bus.event("server.connected", z.object({})),
|
Connected: BusEvent.define("server.connected", z.object({})),
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
@@ -109,7 +110,7 @@ export namespace Server {
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
directory: z.string(),
|
directory: z.string(),
|
||||||
payload: Bus.payloads(),
|
payload: BusEvent.payloads(),
|
||||||
})
|
})
|
||||||
.meta({
|
.meta({
|
||||||
ref: "GlobalEvent",
|
ref: "GlobalEvent",
|
||||||
@@ -2393,7 +2394,7 @@ export namespace Server {
|
|||||||
description: "Event stream",
|
description: "Event stream",
|
||||||
content: {
|
content: {
|
||||||
"text/event-stream": {
|
"text/event-stream": {
|
||||||
schema: resolver(Bus.payloads()),
|
schema: resolver(BusEvent.payloads()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import { wrapLanguageModel, type ModelMessage } from "ai"
|
import { wrapLanguageModel, type ModelMessage } from "ai"
|
||||||
import { Session } from "."
|
import { Session } from "."
|
||||||
import { Identifier } from "../id/id"
|
import { Identifier } from "../id/id"
|
||||||
@@ -5,7 +7,6 @@ import { Instance } from "../project/instance"
|
|||||||
import { Provider } from "../provider/provider"
|
import { Provider } from "../provider/provider"
|
||||||
import { MessageV2 } from "./message-v2"
|
import { MessageV2 } from "./message-v2"
|
||||||
import { SystemPrompt } from "./system"
|
import { SystemPrompt } from "./system"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { SessionPrompt } from "./prompt"
|
import { SessionPrompt } from "./prompt"
|
||||||
import { Flag } from "../flag/flag"
|
import { Flag } from "../flag/flag"
|
||||||
@@ -21,7 +22,7 @@ export namespace SessionCompaction {
|
|||||||
const log = Log.create({ service: "session.compaction" })
|
const log = Log.create({ service: "session.compaction" })
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Compacted: Bus.event(
|
Compacted: BusEvent.define(
|
||||||
"session.compacted",
|
"session.compacted",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
@@ -140,9 +141,9 @@ export namespace SessionCompaction {
|
|||||||
// set to 0, we handle loop
|
// set to 0, we handle loop
|
||||||
maxRetries: 0,
|
maxRetries: 0,
|
||||||
providerOptions: ProviderTransform.providerOptions(
|
providerOptions: ProviderTransform.providerOptions(
|
||||||
model.api.npm,
|
model,
|
||||||
model.providerID,
|
|
||||||
pipe({}, mergeDeep(ProviderTransform.options(model, input.sessionID)), mergeDeep(model.options)),
|
pipe({}, mergeDeep(ProviderTransform.options(model, input.sessionID)), mergeDeep(model.options)),
|
||||||
|
[],
|
||||||
),
|
),
|
||||||
headers: model.headers,
|
headers: model.headers,
|
||||||
abortSignal: input.abort,
|
abortSignal: input.abort,
|
||||||
@@ -193,7 +194,13 @@ export namespace SessionCompaction {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
experimental_telemetry: {
|
||||||
|
isEnabled: cfg.experimental?.openTelemetry,
|
||||||
|
metadata: {
|
||||||
|
userId: cfg.username ?? "unknown",
|
||||||
|
sessionId: input.sessionID,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if (result === "continue" && input.auto) {
|
if (result === "continue" && input.auto) {
|
||||||
const continueMsg = await Session.updateMessage({
|
const continueMsg = await Session.updateMessage({
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import { Decimal } from "decimal.js"
|
import { Decimal } from "decimal.js"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { type LanguageModelUsage, type ProviderMetadata } from "ai"
|
import { type LanguageModelUsage, type ProviderMetadata } from "ai"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Flag } from "../flag/flag"
|
import { Flag } from "../flag/flag"
|
||||||
import { Identifier } from "../id/id"
|
import { Identifier } from "../id/id"
|
||||||
@@ -85,32 +86,32 @@ export namespace Session {
|
|||||||
export type ShareInfo = z.output<typeof ShareInfo>
|
export type ShareInfo = z.output<typeof ShareInfo>
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Created: Bus.event(
|
Created: BusEvent.define(
|
||||||
"session.created",
|
"session.created",
|
||||||
z.object({
|
z.object({
|
||||||
info: Info,
|
info: Info,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Updated: Bus.event(
|
Updated: BusEvent.define(
|
||||||
"session.updated",
|
"session.updated",
|
||||||
z.object({
|
z.object({
|
||||||
info: Info,
|
info: Info,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Deleted: Bus.event(
|
Deleted: BusEvent.define(
|
||||||
"session.deleted",
|
"session.deleted",
|
||||||
z.object({
|
z.object({
|
||||||
info: Info,
|
info: Info,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Diff: Bus.event(
|
Diff: BusEvent.define(
|
||||||
"session.diff",
|
"session.diff",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
diff: Snapshot.FileDiff.array(),
|
diff: Snapshot.FileDiff.array(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Error: Bus.event(
|
Error: BusEvent.define(
|
||||||
"session.error",
|
"session.error",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string().optional(),
|
sessionID: z.string().optional(),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { Message } from "./message"
|
import { Message } from "./message"
|
||||||
import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
|
import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
|
||||||
@@ -375,27 +376,27 @@ export namespace MessageV2 {
|
|||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Updated: Bus.event(
|
Updated: BusEvent.define(
|
||||||
"message.updated",
|
"message.updated",
|
||||||
z.object({
|
z.object({
|
||||||
info: Info,
|
info: Info,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Removed: Bus.event(
|
Removed: BusEvent.define(
|
||||||
"message.removed",
|
"message.removed",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
messageID: z.string(),
|
messageID: z.string(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
PartUpdated: Bus.event(
|
PartUpdated: BusEvent.define(
|
||||||
"message.part.updated",
|
"message.part.updated",
|
||||||
z.object({
|
z.object({
|
||||||
part: Part,
|
part: Part,
|
||||||
delta: z.string().optional(),
|
delta: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
PartRemoved: Bus.event(
|
PartRemoved: BusEvent.define(
|
||||||
"message.part.removed",
|
"message.part.removed",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
|
|||||||
@@ -515,6 +515,37 @@ export namespace SessionPrompt {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messages = [
|
||||||
|
...system.map(
|
||||||
|
(x): ModelMessage => ({
|
||||||
|
role: "system",
|
||||||
|
content: x,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
...MessageV2.toModelMessage(
|
||||||
|
msgs.filter((m) => {
|
||||||
|
if (m.info.role !== "assistant" || m.info.error === undefined) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
MessageV2.AbortedError.isInstance(m.info.error) &&
|
||||||
|
m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
...(isLastStep
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
role: "assistant" as const,
|
||||||
|
content: MAX_STEPS,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]
|
||||||
const result = await processor.process({
|
const result = await processor.process({
|
||||||
onError(error) {
|
onError(error) {
|
||||||
log.error("stream error", {
|
log.error("stream error", {
|
||||||
@@ -562,42 +593,12 @@ export namespace SessionPrompt {
|
|||||||
OUTPUT_TOKEN_MAX,
|
OUTPUT_TOKEN_MAX,
|
||||||
),
|
),
|
||||||
abortSignal: abort,
|
abortSignal: abort,
|
||||||
providerOptions: ProviderTransform.providerOptions(model.api.npm, model.providerID, params.options),
|
providerOptions: ProviderTransform.providerOptions(model, params.options, messages),
|
||||||
stopWhen: stepCountIs(1),
|
stopWhen: stepCountIs(1),
|
||||||
temperature: params.temperature,
|
temperature: params.temperature,
|
||||||
topP: params.topP,
|
topP: params.topP,
|
||||||
toolChoice: isLastStep ? "none" : undefined,
|
toolChoice: isLastStep ? "none" : undefined,
|
||||||
messages: [
|
messages,
|
||||||
...system.map(
|
|
||||||
(x): ModelMessage => ({
|
|
||||||
role: "system",
|
|
||||||
content: x,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
...MessageV2.toModelMessage(
|
|
||||||
msgs.filter((m) => {
|
|
||||||
if (m.info.role !== "assistant" || m.info.error === undefined) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
MessageV2.AbortedError.isInstance(m.info.error) &&
|
|
||||||
m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
...(isLastStep
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
role: "assistant" as const,
|
|
||||||
content: MAX_STEPS,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
tools: model.capabilities.toolcall === false ? undefined : tools,
|
tools: model.capabilities.toolcall === false ? undefined : tools,
|
||||||
model: wrapLanguageModel({
|
model: wrapLanguageModel({
|
||||||
model: language,
|
model: language,
|
||||||
@@ -628,7 +629,13 @@ export namespace SessionPrompt {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
experimental_telemetry: {
|
||||||
|
isEnabled: cfg.experimental?.openTelemetry,
|
||||||
|
metadata: {
|
||||||
|
userId: cfg.username ?? "unknown",
|
||||||
|
sessionId: sessionID,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if (result === "stop") break
|
if (result === "stop") break
|
||||||
continue
|
continue
|
||||||
@@ -1458,7 +1465,7 @@ export namespace SessionPrompt {
|
|||||||
await generateText({
|
await generateText({
|
||||||
// use higher # for reasoning models since reasoning tokens eat up a lot of the budget
|
// use higher # for reasoning models since reasoning tokens eat up a lot of the budget
|
||||||
maxOutputTokens: small.capabilities.reasoning ? 3000 : 20,
|
maxOutputTokens: small.capabilities.reasoning ? 3000 : 20,
|
||||||
providerOptions: ProviderTransform.providerOptions(small.api.npm, small.providerID, options),
|
providerOptions: ProviderTransform.providerOptions(small, options, []),
|
||||||
messages: [
|
messages: [
|
||||||
...SystemPrompt.title(small.providerID).map(
|
...SystemPrompt.title(small.providerID).map(
|
||||||
(x): ModelMessage => ({
|
(x): ModelMessage => ({
|
||||||
@@ -1491,7 +1498,13 @@ export namespace SessionPrompt {
|
|||||||
],
|
],
|
||||||
headers: small.headers,
|
headers: small.headers,
|
||||||
model: language,
|
model: language,
|
||||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
experimental_telemetry: {
|
||||||
|
isEnabled: cfg.experimental?.openTelemetry,
|
||||||
|
metadata: {
|
||||||
|
userId: cfg.username ?? "unknown",
|
||||||
|
sessionId: input.session.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.text)
|
if (result.text)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
import { Instance } from "@/project/instance"
|
import { Instance } from "@/project/instance"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
@@ -24,7 +25,7 @@ export namespace SessionStatus {
|
|||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Status: Bus.event(
|
Status: BusEvent.define(
|
||||||
"session.status",
|
"session.status",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
@@ -32,7 +33,7 @@ export namespace SessionStatus {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
// deprecated
|
// deprecated
|
||||||
Idle: Bus.event(
|
Idle: BusEvent.define(
|
||||||
"session.idle",
|
"session.idle",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export namespace SessionSummary {
|
|||||||
if (textPart && !userMsg.summary?.title) {
|
if (textPart && !userMsg.summary?.title) {
|
||||||
const result = await generateText({
|
const result = await generateText({
|
||||||
maxOutputTokens: small.capabilities.reasoning ? 1500 : 20,
|
maxOutputTokens: small.capabilities.reasoning ? 1500 : 20,
|
||||||
providerOptions: ProviderTransform.providerOptions(small.api.npm, small.providerID, options),
|
providerOptions: ProviderTransform.providerOptions(small, options, []),
|
||||||
messages: [
|
messages: [
|
||||||
...SystemPrompt.title(small.providerID).map(
|
...SystemPrompt.title(small.providerID).map(
|
||||||
(x): ModelMessage => ({
|
(x): ModelMessage => ({
|
||||||
@@ -111,7 +111,13 @@ export namespace SessionSummary {
|
|||||||
],
|
],
|
||||||
headers: small.headers,
|
headers: small.headers,
|
||||||
model: language,
|
model: language,
|
||||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
experimental_telemetry: {
|
||||||
|
isEnabled: cfg.experimental?.openTelemetry,
|
||||||
|
metadata: {
|
||||||
|
userId: cfg.username ?? "unknown",
|
||||||
|
sessionId: assistantMsg.sessionID,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
log.info("title", { title: result.text })
|
log.info("title", { title: result.text })
|
||||||
userMsg.summary.title = result.text
|
userMsg.summary.title = result.text
|
||||||
@@ -138,7 +144,7 @@ export namespace SessionSummary {
|
|||||||
const result = await generateText({
|
const result = await generateText({
|
||||||
model: language,
|
model: language,
|
||||||
maxOutputTokens: 100,
|
maxOutputTokens: 100,
|
||||||
providerOptions: ProviderTransform.providerOptions(small.api.npm, small.providerID, options),
|
providerOptions: ProviderTransform.providerOptions(small, options, []),
|
||||||
messages: [
|
messages: [
|
||||||
...SystemPrompt.summarize(small.providerID).map(
|
...SystemPrompt.summarize(small.providerID).map(
|
||||||
(x): ModelMessage => ({
|
(x): ModelMessage => ({
|
||||||
@@ -153,7 +159,13 @@ export namespace SessionSummary {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
headers: small.headers,
|
headers: small.headers,
|
||||||
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
|
experimental_telemetry: {
|
||||||
|
isEnabled: cfg.experimental?.openTelemetry,
|
||||||
|
metadata: {
|
||||||
|
userId: cfg.username ?? "unknown",
|
||||||
|
sessionId: assistantMsg.sessionID,
|
||||||
|
},
|
||||||
|
},
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
if (result) summary = result.text
|
if (result) summary = result.text
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { Bus } from "@/bus"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { Storage } from "../storage/storage"
|
import { Storage } from "../storage/storage"
|
||||||
|
|
||||||
export namespace Todo {
|
export namespace Todo {
|
||||||
@@ -14,7 +15,7 @@ export namespace Todo {
|
|||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Updated: Bus.event(
|
Updated: BusEvent.define(
|
||||||
"todo.updated",
|
"todo.updated",
|
||||||
z.object({
|
z.object({
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
|
|||||||
@@ -16,12 +16,8 @@ import { Flag } from "@/flag/flag.ts"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { iife } from "@/util/iife"
|
import { iife } from "@/util/iife"
|
||||||
|
|
||||||
const DEFAULT_MAX_OUTPUT_LENGTH = 30_000
|
const MAX_OUTPUT_LENGTH = Flag.OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH || 30_000
|
||||||
const MAX_OUTPUT_LENGTH = (() => {
|
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT || 2 * 60 * 1000
|
||||||
const parsed = Number(Flag.OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH)
|
|
||||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_OUTPUT_LENGTH
|
|
||||||
})()
|
|
||||||
const DEFAULT_TIMEOUT = 2 * 60 * 1000
|
|
||||||
const SIGKILL_TIMEOUT_MS = 200
|
const SIGKILL_TIMEOUT_MS = 200
|
||||||
|
|
||||||
export const log = Log.create({ service: "bash-tool" })
|
export const log = Log.create({ service: "bash-tool" })
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export const ReadTool = Tool.define("read", {
|
|||||||
throw new Error(`File not found: ${filepath}`)
|
throw new Error(`File not found: ${filepath}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isImage = file.type.startsWith("image/")
|
const isImage = file.type.startsWith("image/") && file.type !== "image/svg+xml"
|
||||||
const isPdf = file.type === "application/pdf"
|
const isPdf = file.type === "application/pdf"
|
||||||
if (isImage || isPdf) {
|
if (isImage || isPdf) {
|
||||||
const mime = file.type
|
const mime = file.type
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import { test, expect, mock, beforeEach } from "bun:test"
|
||||||
|
|
||||||
|
// Track what options were passed to each transport constructor
|
||||||
|
const transportCalls: Array<{
|
||||||
|
type: "streamable" | "sse"
|
||||||
|
url: string
|
||||||
|
options: { authProvider?: unknown; requestInit?: RequestInit }
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
// Mock the transport constructors to capture their arguments
|
||||||
|
mock.module("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({
|
||||||
|
StreamableHTTPClientTransport: class MockStreamableHTTP {
|
||||||
|
constructor(url: URL, options?: { authProvider?: unknown; requestInit?: RequestInit }) {
|
||||||
|
transportCalls.push({
|
||||||
|
type: "streamable",
|
||||||
|
url: url.toString(),
|
||||||
|
options: options ?? {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async start() {
|
||||||
|
throw new Error("Mock transport cannot connect")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
mock.module("@modelcontextprotocol/sdk/client/sse.js", () => ({
|
||||||
|
SSEClientTransport: class MockSSE {
|
||||||
|
constructor(url: URL, options?: { authProvider?: unknown; requestInit?: RequestInit }) {
|
||||||
|
transportCalls.push({
|
||||||
|
type: "sse",
|
||||||
|
url: url.toString(),
|
||||||
|
options: options ?? {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async start() {
|
||||||
|
throw new Error("Mock transport cannot connect")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
transportCalls.length = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// Import MCP after mocking
|
||||||
|
const { MCP } = await import("../../src/mcp/index")
|
||||||
|
const { Instance } = await import("../../src/project/instance")
|
||||||
|
const { tmpdir } = await import("../fixture/fixture")
|
||||||
|
|
||||||
|
test("headers are passed to transports when oauth is enabled (default)", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
await Bun.write(
|
||||||
|
`${dir}/opencode.json`,
|
||||||
|
JSON.stringify({
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
mcp: {
|
||||||
|
"test-server": {
|
||||||
|
type: "remote",
|
||||||
|
url: "https://example.com/mcp",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test-token",
|
||||||
|
"X-Custom-Header": "custom-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
// Trigger MCP initialization - it will fail to connect but we can check the transport options
|
||||||
|
await MCP.add("test-server", {
|
||||||
|
type: "remote",
|
||||||
|
url: "https://example.com/mcp",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test-token",
|
||||||
|
"X-Custom-Header": "custom-value",
|
||||||
|
},
|
||||||
|
}).catch(() => {})
|
||||||
|
|
||||||
|
// Both transports should have been created with headers
|
||||||
|
expect(transportCalls.length).toBeGreaterThanOrEqual(1)
|
||||||
|
|
||||||
|
for (const call of transportCalls) {
|
||||||
|
expect(call.options.requestInit).toBeDefined()
|
||||||
|
expect(call.options.requestInit?.headers).toEqual({
|
||||||
|
Authorization: "Bearer test-token",
|
||||||
|
"X-Custom-Header": "custom-value",
|
||||||
|
})
|
||||||
|
// OAuth should be enabled by default, so authProvider should exist
|
||||||
|
expect(call.options.authProvider).toBeDefined()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("headers are passed to transports when oauth is explicitly disabled", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
transportCalls.length = 0
|
||||||
|
|
||||||
|
await MCP.add("test-server-no-oauth", {
|
||||||
|
type: "remote",
|
||||||
|
url: "https://example.com/mcp",
|
||||||
|
oauth: false,
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test-token",
|
||||||
|
},
|
||||||
|
}).catch(() => {})
|
||||||
|
|
||||||
|
expect(transportCalls.length).toBeGreaterThanOrEqual(1)
|
||||||
|
|
||||||
|
for (const call of transportCalls) {
|
||||||
|
expect(call.options.requestInit).toBeDefined()
|
||||||
|
expect(call.options.requestInit?.headers).toEqual({
|
||||||
|
Authorization: "Bearer test-token",
|
||||||
|
})
|
||||||
|
// OAuth is disabled, so no authProvider
|
||||||
|
expect(call.options.authProvider).toBeUndefined()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("no requestInit when headers are not provided", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
transportCalls.length = 0
|
||||||
|
|
||||||
|
await MCP.add("test-server-no-headers", {
|
||||||
|
type: "remote",
|
||||||
|
url: "https://example.com/mcp",
|
||||||
|
}).catch(() => {})
|
||||||
|
|
||||||
|
expect(transportCalls.length).toBeGreaterThanOrEqual(1)
|
||||||
|
|
||||||
|
for (const call of transportCalls) {
|
||||||
|
// No headers means requestInit should be undefined
|
||||||
|
expect(call.options.requestInit).toBeUndefined()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { Project } from "../../src/project/project"
|
import { Project } from "../../src/project/project"
|
||||||
import { Log } from "../../src/util/log"
|
import { Log } from "../../src/util/log"
|
||||||
|
import { Storage } from "../../src/storage/storage"
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
@@ -18,7 +19,6 @@ describe("Project.fromDirectory", () => {
|
|||||||
expect(project.id).toBe("global")
|
expect(project.id).toBe("global")
|
||||||
expect(project.vcs).toBe("git")
|
expect(project.vcs).toBe("git")
|
||||||
expect(project.worktree).toBe(tmp.path)
|
expect(project.worktree).toBe(tmp.path)
|
||||||
expect(project.vcsDir).toBe(path.join(tmp.path, ".git"))
|
|
||||||
|
|
||||||
const opencodeFile = path.join(tmp.path, ".git", "opencode")
|
const opencodeFile = path.join(tmp.path, ".git", "opencode")
|
||||||
const fileExists = await Bun.file(opencodeFile).exists()
|
const fileExists = await Bun.file(opencodeFile).exists()
|
||||||
@@ -40,3 +40,33 @@ describe("Project.fromDirectory", () => {
|
|||||||
expect(fileExists).toBe(true)
|
expect(fileExists).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Project.discover", () => {
|
||||||
|
test("should discover favicon.png in root", async () => {
|
||||||
|
await using tmp = await tmpdir({ git: true })
|
||||||
|
const project = await Project.fromDirectory(tmp.path)
|
||||||
|
|
||||||
|
const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
|
||||||
|
await Bun.write(path.join(tmp.path, "favicon.png"), pngData)
|
||||||
|
|
||||||
|
await Project.discover(project)
|
||||||
|
|
||||||
|
const updated = await Storage.read<Project.Info>(["project", project.id])
|
||||||
|
expect(updated.icon).toBeDefined()
|
||||||
|
expect(updated.icon?.url).toStartWith("data:")
|
||||||
|
expect(updated.icon?.url).toContain("base64")
|
||||||
|
expect(updated.icon?.color).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should not discover non-image files", async () => {
|
||||||
|
await using tmp = await tmpdir({ git: true })
|
||||||
|
const project = await Project.fromDirectory(tmp.path)
|
||||||
|
|
||||||
|
await Bun.write(path.join(tmp.path, "favicon.txt"), "not an image")
|
||||||
|
|
||||||
|
await Project.discover(project)
|
||||||
|
|
||||||
|
const updated = await Storage.read<Project.Info>(["project", project.id])
|
||||||
|
expect(updated.icon).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
|||||||
toolcall: true,
|
toolcall: true,
|
||||||
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||||
|
interleaved: false,
|
||||||
},
|
},
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.001,
|
input: 0.001,
|
||||||
@@ -184,6 +185,7 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
|||||||
toolcall: true,
|
toolcall: true,
|
||||||
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||||
|
interleaved: false,
|
||||||
},
|
},
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.001,
|
input: 0.001,
|
||||||
@@ -236,6 +238,7 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
|||||||
toolcall: true,
|
toolcall: true,
|
||||||
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||||
|
interleaved: false,
|
||||||
},
|
},
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.001,
|
input: 0.001,
|
||||||
@@ -281,6 +284,7 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
|||||||
toolcall: true,
|
toolcall: true,
|
||||||
input: { text: true, audio: false, image: true, video: false, pdf: false },
|
input: { text: true, audio: false, image: true, video: false, pdf: false },
|
||||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||||
|
interleaved: false,
|
||||||
},
|
},
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.03,
|
input: 0.03,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
|
|||||||
+483
-1241
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ import type {
|
|||||||
PermissionRespondResponses,
|
PermissionRespondResponses,
|
||||||
ProjectCurrentResponses,
|
ProjectCurrentResponses,
|
||||||
ProjectListResponses,
|
ProjectListResponses,
|
||||||
|
ProjectUpdateErrors,
|
||||||
|
ProjectUpdateResponses,
|
||||||
ProviderAuthResponses,
|
ProviderAuthResponses,
|
||||||
ProviderListResponses,
|
ProviderListResponses,
|
||||||
ProviderOauthAuthorizeErrors,
|
ProviderOauthAuthorizeErrors,
|
||||||
@@ -231,6 +233,48 @@ export class Project extends HeyApiClient {
|
|||||||
...params,
|
...params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update project
|
||||||
|
*
|
||||||
|
* Update project properties such as name, icon and color.
|
||||||
|
*/
|
||||||
|
public update<ThrowOnError extends boolean = false>(
|
||||||
|
parameters: {
|
||||||
|
projectID: string
|
||||||
|
directory?: string
|
||||||
|
name?: string
|
||||||
|
icon?: {
|
||||||
|
url?: string
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "path", key: "projectID" },
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "body", key: "name" },
|
||||||
|
{ in: "body", key: "icon" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).patch<ProjectUpdateResponses, ProjectUpdateErrors, ThrowOnError>({
|
||||||
|
url: "/project/{projectID}",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...options?.headers,
|
||||||
|
...params.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Pty extends HeyApiClient {
|
export class Pty extends HeyApiClient {
|
||||||
|
|||||||
@@ -4,13 +4,6 @@ export type ClientOptions = {
|
|||||||
baseUrl: `${string}://${string}` | (string & {})
|
baseUrl: `${string}://${string}` | (string & {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EventServerInstanceDisposed = {
|
|
||||||
type: "server.instance.disposed"
|
|
||||||
properties: {
|
|
||||||
directory: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventInstallationUpdated = {
|
export type EventInstallationUpdated = {
|
||||||
type: "installation.updated"
|
type: "installation.updated"
|
||||||
properties: {
|
properties: {
|
||||||
@@ -25,6 +18,34 @@ export type EventInstallationUpdateAvailable = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Project = {
|
||||||
|
id: string
|
||||||
|
worktree: string
|
||||||
|
vcs?: "git"
|
||||||
|
name?: string
|
||||||
|
icon?: {
|
||||||
|
url?: string
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
time: {
|
||||||
|
created: number
|
||||||
|
updated: number
|
||||||
|
initialized?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventProjectUpdated = {
|
||||||
|
type: "project.updated"
|
||||||
|
properties: Project
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventServerInstanceDisposed = {
|
||||||
|
type: "server.instance.disposed"
|
||||||
|
properties: {
|
||||||
|
directory: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type EventLspClientDiagnostics = {
|
export type EventLspClientDiagnostics = {
|
||||||
type: "lsp.client.diagnostics"
|
type: "lsp.client.diagnostics"
|
||||||
properties: {
|
properties: {
|
||||||
@@ -704,9 +725,10 @@ export type EventServerConnected = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| EventServerInstanceDisposed
|
|
||||||
| EventInstallationUpdated
|
| EventInstallationUpdated
|
||||||
| EventInstallationUpdateAvailable
|
| EventInstallationUpdateAvailable
|
||||||
|
| EventProjectUpdated
|
||||||
|
| EventServerInstanceDisposed
|
||||||
| EventLspClientDiagnostics
|
| EventLspClientDiagnostics
|
||||||
| EventLspUpdated
|
| EventLspUpdated
|
||||||
| EventMessageUpdated
|
| EventMessageUpdated
|
||||||
@@ -742,17 +764,6 @@ export type GlobalEvent = {
|
|||||||
payload: Event
|
payload: Event
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Project = {
|
|
||||||
id: string
|
|
||||||
worktree: string
|
|
||||||
vcsDir?: string
|
|
||||||
vcs?: "git"
|
|
||||||
time: {
|
|
||||||
created: number
|
|
||||||
initialized?: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BadRequestError = {
|
export type BadRequestError = {
|
||||||
data: unknown
|
data: unknown
|
||||||
errors: Array<{
|
errors: Array<{
|
||||||
@@ -1033,6 +1044,11 @@ export type ProviderConfig = {
|
|||||||
reasoning?: boolean
|
reasoning?: boolean
|
||||||
temperature?: boolean
|
temperature?: boolean
|
||||||
tool_call?: boolean
|
tool_call?: boolean
|
||||||
|
interleaved?:
|
||||||
|
| true
|
||||||
|
| {
|
||||||
|
field: "reasoning_content" | "reasoning_details"
|
||||||
|
}
|
||||||
cost?: {
|
cost?: {
|
||||||
input: number
|
input: number
|
||||||
output: number
|
output: number
|
||||||
@@ -1468,6 +1484,11 @@ export type Model = {
|
|||||||
video: boolean
|
video: boolean
|
||||||
pdf: boolean
|
pdf: boolean
|
||||||
}
|
}
|
||||||
|
interleaved:
|
||||||
|
| boolean
|
||||||
|
| {
|
||||||
|
field: "reasoning_content" | "reasoning_details"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cost: {
|
cost: {
|
||||||
input: number
|
input: number
|
||||||
@@ -1715,6 +1736,45 @@ export type ProjectCurrentResponses = {
|
|||||||
|
|
||||||
export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses]
|
export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses]
|
||||||
|
|
||||||
|
export type ProjectUpdateData = {
|
||||||
|
body?: {
|
||||||
|
name?: string
|
||||||
|
icon?: {
|
||||||
|
url?: string
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path: {
|
||||||
|
projectID: string
|
||||||
|
}
|
||||||
|
query?: {
|
||||||
|
directory?: string
|
||||||
|
}
|
||||||
|
url: "/project/{projectID}"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProjectUpdateErrors = {
|
||||||
|
/**
|
||||||
|
* Bad request
|
||||||
|
*/
|
||||||
|
400: BadRequestError
|
||||||
|
/**
|
||||||
|
* Not found
|
||||||
|
*/
|
||||||
|
404: NotFoundError
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProjectUpdateError = ProjectUpdateErrors[keyof ProjectUpdateErrors]
|
||||||
|
|
||||||
|
export type ProjectUpdateResponses = {
|
||||||
|
/**
|
||||||
|
* Updated project information
|
||||||
|
*/
|
||||||
|
200: Project
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProjectUpdateResponse = ProjectUpdateResponses[keyof ProjectUpdateResponses]
|
||||||
|
|
||||||
export type PtyListData = {
|
export type PtyListData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
@@ -2976,6 +3036,11 @@ export type ProviderListResponses = {
|
|||||||
reasoning: boolean
|
reasoning: boolean
|
||||||
temperature: boolean
|
temperature: boolean
|
||||||
tool_call: boolean
|
tool_call: boolean
|
||||||
|
interleaved?:
|
||||||
|
| true
|
||||||
|
| {
|
||||||
|
field: "reasoning_content" | "reasoning_details"
|
||||||
|
}
|
||||||
cost?: {
|
cost?: {
|
||||||
input: number
|
input: number
|
||||||
output: number
|
output: number
|
||||||
|
|||||||
+429
-1242
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run src/index.ts",
|
"dev": "bun run src/index.ts",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/tauri",
|
"name": "@opencode-ai/tauri",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"predev": "bun ./scripts/predev.ts",
|
"predev": "bun ./scripts/predev.ts",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "bun run typecheck && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-dialog": "~2",
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
|
"@tauri-apps/plugin-process": "~2",
|
||||||
"@tauri-apps/plugin-shell": "~2",
|
"@tauri-apps/plugin-shell": "~2",
|
||||||
"@tauri-apps/plugin-updater": "~2",
|
"@tauri-apps/plugin-updater": "~2",
|
||||||
"solid-js": "catalog:"
|
"solid-js": "catalog:"
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
"@actions/artifact": "4.0.0",
|
"@actions/artifact": "4.0.0",
|
||||||
"@tauri-apps/cli": "^2",
|
"@tauri-apps/cli": "^2",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
|
"@typescript/native-preview": "catalog:",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "catalog:"
|
"vite": "catalog:"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+11
@@ -2511,6 +2511,7 @@ dependencies = [
|
|||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tauri-plugin-process",
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -4143,6 +4144,16 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-process"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a"
|
||||||
|
dependencies = [
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-shell"
|
name = "tauri-plugin-shell"
|
||||||
version = "2.3.3"
|
version = "2.3.3"
|
||||||
|
|||||||
@@ -28,3 +28,4 @@ serde = { version = "1", features = ["derive"] }
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tokio = "1.48.0"
|
tokio = "1.48.0"
|
||||||
listeners = "0.3"
|
listeners = "0.3"
|
||||||
|
tauri-plugin-process = "2"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
"shell:default",
|
"shell:default",
|
||||||
"updater:default",
|
"updater:default",
|
||||||
"dialog:default"
|
"dialog:default",
|
||||||
|
"process:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use tauri::{AppHandle, Manager, RunEvent, WebviewUrl, WebviewWindow};
|
|||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
|
||||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
||||||
use tauri_plugin_shell::ShellExt;
|
use tauri_plugin_shell::ShellExt;
|
||||||
use tauri_plugin_updater::UpdaterExt;
|
|
||||||
use tokio::net::TcpSocket;
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -102,14 +101,11 @@ pub fn run() {
|
|||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let app = app.handle().clone();
|
let app = app.handle().clone();
|
||||||
|
|
||||||
if updater_enabled {
|
|
||||||
tauri::async_runtime::spawn(run_updater(app.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let port = get_sidecar_port();
|
let port = get_sidecar_port();
|
||||||
let socket_connected = is_server_running(port).await;
|
let socket_connected = is_server_running(port).await;
|
||||||
@@ -141,7 +137,7 @@ pub fn run() {
|
|||||||
|
|
||||||
let timestamp = Instant::now();
|
let timestamp = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
if timestamp.elapsed() > Duration::from_secs(3) {
|
if timestamp.elapsed() > Duration::from_secs(7) {
|
||||||
todo!("Handle server spawn timeout");
|
todo!("Handle server spawn timeout");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +163,13 @@ pub fn run() {
|
|||||||
.title("OpenCode")
|
.title("OpenCode")
|
||||||
.inner_size(800.0, 600.0)
|
.inner_size(800.0, 600.0)
|
||||||
.decorations(true)
|
.decorations(true)
|
||||||
.zoom_hotkeys_enabled(true);
|
.zoom_hotkeys_enabled(true)
|
||||||
|
.initialization_script(format!(
|
||||||
|
r#"
|
||||||
|
window.__OPENCODE__ ??= {{}};
|
||||||
|
window.__OPENCODE__.updaterEnabled = {updater_enabled}
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
@@ -193,78 +195,24 @@ pub fn run() {
|
|||||||
if let RunEvent::Exit = event {
|
if let RunEvent::Exit = event {
|
||||||
println!("Received Exit");
|
println!("Received Exit");
|
||||||
|
|
||||||
let _ = app
|
let Some(server_state) = app.try_state::<ServerState>() else {
|
||||||
.state::<ServerState>()
|
println!("Server not running");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(server_state) = server_state
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.expect("Failed to acquire mutex lock")
|
.expect("Failed to acquire mutex lock")
|
||||||
.take()
|
.take()
|
||||||
.expect("State not found")
|
else {
|
||||||
.kill();
|
println!("Server state missing");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = server_state.kill();
|
||||||
|
|
||||||
println!("Killed server");
|
println!("Killed server");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_updater(app: AppHandle) {
|
|
||||||
let update = match app
|
|
||||||
.updater_builder()
|
|
||||||
.version_comparator(|v, r| {
|
|
||||||
dbg!(&v, &r);
|
|
||||||
r.version > v
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.check()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(u) => u,
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
app.dialog()
|
|
||||||
.message("Failed to check for updates")
|
|
||||||
.show(|_| {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
dbg!(update.is_some());
|
|
||||||
|
|
||||||
let Some(update) = update else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(update_bytes) = update.download(|_, _| {}, || {}).await else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let should_update = app
|
|
||||||
.dialog()
|
|
||||||
.message(format!(
|
|
||||||
"Version {} of OpenCode is available, would you like to install it?",
|
|
||||||
&update.version
|
|
||||||
))
|
|
||||||
.buttons(MessageDialogButtons::YesNo)
|
|
||||||
.blocking_show();
|
|
||||||
|
|
||||||
if !should_update {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if update.install(update_bytes).is_err() {
|
|
||||||
app.dialog()
|
|
||||||
.message("Failed to install update")
|
|
||||||
.blocking_show();
|
|
||||||
}
|
|
||||||
|
|
||||||
let should_restart = app
|
|
||||||
.dialog()
|
|
||||||
.message("Update installed successfully, would you like to restart OpenCode?")
|
|
||||||
.buttons(MessageDialogButtons::YesNo)
|
|
||||||
.blocking_show();
|
|
||||||
|
|
||||||
if should_restart {
|
|
||||||
app.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
// @refresh reload
|
// @refresh reload
|
||||||
import { render } from "solid-js/web"
|
import { render } from "solid-js/web"
|
||||||
import { DesktopInterface, PlatformProvider, Platform } from "@opencode-ai/desktop"
|
import { App, PlatformProvider, Platform } from "@opencode-ai/desktop"
|
||||||
|
import { runUpdater } from "./updater"
|
||||||
|
import { onMount } from "solid-js"
|
||||||
|
import { open, save } from "@tauri-apps/plugin-dialog"
|
||||||
|
import { open as shellOpen } from "@tauri-apps/plugin-shell"
|
||||||
|
|
||||||
const root = document.getElementById("root")
|
const root = document.getElementById("root")
|
||||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||||
@@ -9,13 +13,54 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const platform: Platform = {}
|
const platform: Platform = {
|
||||||
|
platform: "tauri",
|
||||||
|
|
||||||
render(
|
async openDirectoryPickerDialog(opts) {
|
||||||
() => (
|
const result = await open({
|
||||||
|
directory: true,
|
||||||
|
multiple: opts?.multiple ?? false,
|
||||||
|
title: opts?.title ?? "Choose a folder",
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
async openFilePickerDialog(opts) {
|
||||||
|
const result = await open({
|
||||||
|
directory: false,
|
||||||
|
multiple: opts?.multiple ?? false,
|
||||||
|
title: opts?.title ?? "Choose a file",
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveFilePickerDialog(opts) {
|
||||||
|
const result = await save({
|
||||||
|
title: opts?.title ?? "Save file",
|
||||||
|
defaultPath: opts?.defaultPath,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
openLink(url: string) {
|
||||||
|
shellOpen(url)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__OPENCODE__?: { updaterEnabled?: boolean }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(() => {
|
||||||
|
onMount(() => {
|
||||||
|
if (window.__OPENCODE__?.updaterEnabled) runUpdater()
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
<PlatformProvider value={platform}>
|
<PlatformProvider value={platform}>
|
||||||
<DesktopInterface />
|
<App />
|
||||||
</PlatformProvider>
|
</PlatformProvider>
|
||||||
),
|
)
|
||||||
root!,
|
}, root!)
|
||||||
)
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { check, DownloadEvent } from "@tauri-apps/plugin-updater"
|
||||||
|
import { relaunch } from "@tauri-apps/plugin-process"
|
||||||
|
import { ask, message } from "@tauri-apps/plugin-dialog"
|
||||||
|
|
||||||
|
export async function runUpdater(onDownloadEvent?: (progress: DownloadEvent) => void) {
|
||||||
|
let update
|
||||||
|
try {
|
||||||
|
update = await check()
|
||||||
|
} catch {
|
||||||
|
await message("Failed to check for updates")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!update) return
|
||||||
|
if (update.version <= update.currentVersion) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await update.download(onDownloadEvent)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldUpdate = await ask(`Version ${update.version} of OpenCode is available, would you like to install it?`)
|
||||||
|
if (!shouldUpdate) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await update.install()
|
||||||
|
} catch {
|
||||||
|
await message("Failed to install update")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldRestart = await ask("Update installed successfully, would you like to restart OpenCode?")
|
||||||
|
if (shouldRestart) await relaunch()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -1,26 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "../desktop/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"outDir": "ts-dist"
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
|
|
||||||
"jsx": "preserve",
|
|
||||||
"jsxImportSource": "solid-js"
|
|
||||||
},
|
},
|
||||||
|
"references": [{ "path": "../desktop" }],
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.0.137",
|
"version": "1.0.138",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
"./*": "./src/components/*.tsx",
|
"./*": "./src/components/*.tsx",
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
[data-component="avatar"] {
|
||||||
|
--avatar-bg: var(--color-surface-info-base);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--color-border-weak-base);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: var(--avatar-bg);
|
||||||
|
color: oklch(from var(--avatar-bg) calc(l * 0.72) calc(c * 8) h);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="avatar"][data-has-image] {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="avatar"][data-size="small"] {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="avatar"][data-size="normal"] {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="avatar"][data-size="large"] {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { type ComponentProps, splitProps, Show } from "solid-js"
|
||||||
|
|
||||||
|
export interface AvatarProps extends ComponentProps<"div"> {
|
||||||
|
fallback: string
|
||||||
|
src?: string
|
||||||
|
background?: string
|
||||||
|
size?: "small" | "normal" | "large"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Avatar(props: AvatarProps) {
|
||||||
|
const [split, rest] = splitProps(props, ["fallback", "src", "background", "size", "class", "classList", "style"])
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...rest}
|
||||||
|
data-component="avatar"
|
||||||
|
data-size={split.size || "normal"}
|
||||||
|
data-has-image={split.src ? "" : undefined}
|
||||||
|
classList={{
|
||||||
|
...(split.classList ?? {}),
|
||||||
|
[split.class ?? ""]: !!split.class,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
...(typeof split.style === "object" ? split.style : {}),
|
||||||
|
...(!split.src && split.background ? { "--avatar-bg": split.background } : {}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Show when={split.src} fallback={split.fallback?.[0]}>
|
||||||
|
{(src) => <img src={src()} draggable={false} class="size-full object-cover" />}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-md);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
@@ -54,6 +54,12 @@
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
&[data-selected="true"]:not(:disabled) {
|
||||||
|
background-color: var(--surface-raised-base-hover);
|
||||||
|
}
|
||||||
|
&[data-active="true"] {
|
||||||
|
background-color: var(--surface-raised-base-active);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-variant="secondary"] {
|
&[data-variant="secondary"] {
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
[data-component="dropdown-menu-content"],
|
||||||
|
[data-component="dropdown-menu-sub-content"] {
|
||||||
|
min-width: 8rem;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-weak-base);
|
||||||
|
background-color: var(--surface-raised-stronger-non-alpha);
|
||||||
|
padding: 4px;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
z-index: 50;
|
||||||
|
transform-origin: var(--kb-menu-content-transform-origin);
|
||||||
|
|
||||||
|
&[data-closed] {
|
||||||
|
animation: dropdown-menu-close 0.15s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-expanded] {
|
||||||
|
animation: dropdown-menu-open 0.15s ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="dropdown-menu-content"],
|
||||||
|
[data-component="dropdown-menu-sub-content"] {
|
||||||
|
[data-slot="dropdown-menu-item"],
|
||||||
|
[data-slot="dropdown-menu-checkbox-item"],
|
||||||
|
[data-slot="dropdown-menu-radio-item"],
|
||||||
|
[data-slot="dropdown-menu-sub-trigger"] {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
letter-spacing: var(--letter-spacing-normal);
|
||||||
|
color: var(--text-strong);
|
||||||
|
|
||||||
|
&[data-highlighted] {
|
||||||
|
background: var(--surface-raised-base-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-disabled] {
|
||||||
|
color: var(--text-weak);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="dropdown-menu-sub-trigger"] {
|
||||||
|
&[data-expanded] {
|
||||||
|
background: var(--surface-raised-base-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="dropdown-menu-item-indicator"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="dropdown-menu-item-label"] {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="dropdown-menu-item-description"] {
|
||||||
|
font-size: var(--font-size-x-small);
|
||||||
|
color: var(--text-weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="dropdown-menu-separator"] {
|
||||||
|
height: 1px;
|
||||||
|
margin: 4px -4px;
|
||||||
|
border-top-color: var(--border-weak-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="dropdown-menu-group-label"] {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-size: var(--font-size-x-small);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
letter-spacing: var(--letter-spacing-normal);
|
||||||
|
color: var(--text-weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="dropdown-menu-arrow"] {
|
||||||
|
fill: var(--surface-raised-stronger-non-alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dropdown-menu-open {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dropdown-menu-close {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
import { DropdownMenu as Kobalte } from "@kobalte/core/dropdown-menu"
|
||||||
|
import { splitProps } from "solid-js"
|
||||||
|
import type { ComponentProps, ParentProps } from "solid-js"
|
||||||
|
|
||||||
|
export interface DropdownMenuProps extends ComponentProps<typeof Kobalte> {}
|
||||||
|
export interface DropdownMenuTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
|
||||||
|
export interface DropdownMenuIconProps extends ComponentProps<typeof Kobalte.Icon> {}
|
||||||
|
export interface DropdownMenuPortalProps extends ComponentProps<typeof Kobalte.Portal> {}
|
||||||
|
export interface DropdownMenuContentProps extends ComponentProps<typeof Kobalte.Content> {}
|
||||||
|
export interface DropdownMenuArrowProps extends ComponentProps<typeof Kobalte.Arrow> {}
|
||||||
|
export interface DropdownMenuSeparatorProps extends ComponentProps<typeof Kobalte.Separator> {}
|
||||||
|
export interface DropdownMenuGroupProps extends ComponentProps<typeof Kobalte.Group> {}
|
||||||
|
export interface DropdownMenuGroupLabelProps extends ComponentProps<typeof Kobalte.GroupLabel> {}
|
||||||
|
export interface DropdownMenuItemProps extends ComponentProps<typeof Kobalte.Item> {}
|
||||||
|
export interface DropdownMenuItemLabelProps extends ComponentProps<typeof Kobalte.ItemLabel> {}
|
||||||
|
export interface DropdownMenuItemDescriptionProps extends ComponentProps<typeof Kobalte.ItemDescription> {}
|
||||||
|
export interface DropdownMenuItemIndicatorProps extends ComponentProps<typeof Kobalte.ItemIndicator> {}
|
||||||
|
export interface DropdownMenuRadioGroupProps extends ComponentProps<typeof Kobalte.RadioGroup> {}
|
||||||
|
export interface DropdownMenuRadioItemProps extends ComponentProps<typeof Kobalte.RadioItem> {}
|
||||||
|
export interface DropdownMenuCheckboxItemProps extends ComponentProps<typeof Kobalte.CheckboxItem> {}
|
||||||
|
export interface DropdownMenuSubProps extends ComponentProps<typeof Kobalte.Sub> {}
|
||||||
|
export interface DropdownMenuSubTriggerProps extends ComponentProps<typeof Kobalte.SubTrigger> {}
|
||||||
|
export interface DropdownMenuSubContentProps extends ComponentProps<typeof Kobalte.SubContent> {}
|
||||||
|
|
||||||
|
function DropdownMenuRoot(props: DropdownMenuProps) {
|
||||||
|
return <Kobalte {...props} data-component="dropdown-menu" />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuTrigger(props: ParentProps<DropdownMenuTriggerProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.Trigger
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-trigger"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.Trigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuIcon(props: ParentProps<DropdownMenuIconProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.Icon
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-icon"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.Icon>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuPortal(props: DropdownMenuPortalProps) {
|
||||||
|
return <Kobalte.Portal {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuContent(props: ParentProps<DropdownMenuContentProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.Content
|
||||||
|
{...rest}
|
||||||
|
data-component="dropdown-menu-content"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.Content>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuArrow(props: DropdownMenuArrowProps) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList"])
|
||||||
|
return (
|
||||||
|
<Kobalte.Arrow
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-arrow"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSeparator(props: DropdownMenuSeparatorProps) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList"])
|
||||||
|
return (
|
||||||
|
<Kobalte.Separator
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-separator"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuGroup(props: ParentProps<DropdownMenuGroupProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.Group
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-group"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuGroupLabel(props: ParentProps<DropdownMenuGroupLabelProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.GroupLabel
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-group-label"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.GroupLabel>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuItem(props: ParentProps<DropdownMenuItemProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.Item
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-item"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuItemLabel(props: ParentProps<DropdownMenuItemLabelProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.ItemLabel
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-item-label"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.ItemLabel>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuItemDescription(props: ParentProps<DropdownMenuItemDescriptionProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.ItemDescription
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-item-description"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.ItemDescription>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuItemIndicator(props: ParentProps<DropdownMenuItemIndicatorProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.ItemIndicator
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-item-indicator"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.ItemIndicator>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuRadioGroup(props: ParentProps<DropdownMenuRadioGroupProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.RadioGroup
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-radio-group"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.RadioGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuRadioItem(props: ParentProps<DropdownMenuRadioItemProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.RadioItem
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-radio-item"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.RadioItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuCheckboxItem(props: ParentProps<DropdownMenuCheckboxItemProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.CheckboxItem
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.CheckboxItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSub(props: DropdownMenuSubProps) {
|
||||||
|
return <Kobalte.Sub {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSubTrigger(props: ParentProps<DropdownMenuSubTriggerProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.SubTrigger
|
||||||
|
{...rest}
|
||||||
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.SubTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSubContent(props: ParentProps<DropdownMenuSubContentProps>) {
|
||||||
|
const [local, rest] = splitProps(props, ["class", "classList", "children"])
|
||||||
|
return (
|
||||||
|
<Kobalte.SubContent
|
||||||
|
{...rest}
|
||||||
|
data-component="dropdown-menu-sub-content"
|
||||||
|
classList={{
|
||||||
|
...(local.classList ?? {}),
|
||||||
|
[local.class ?? ""]: !!local.class,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</Kobalte.SubContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DropdownMenu = Object.assign(DropdownMenuRoot, {
|
||||||
|
Trigger: DropdownMenuTrigger,
|
||||||
|
Icon: DropdownMenuIcon,
|
||||||
|
Portal: DropdownMenuPortal,
|
||||||
|
Content: DropdownMenuContent,
|
||||||
|
Arrow: DropdownMenuArrow,
|
||||||
|
Separator: DropdownMenuSeparator,
|
||||||
|
Group: DropdownMenuGroup,
|
||||||
|
GroupLabel: DropdownMenuGroupLabel,
|
||||||
|
Item: DropdownMenuItem,
|
||||||
|
ItemLabel: DropdownMenuItemLabel,
|
||||||
|
ItemDescription: DropdownMenuItemDescription,
|
||||||
|
ItemIndicator: DropdownMenuItemIndicator,
|
||||||
|
RadioGroup: DropdownMenuRadioGroup,
|
||||||
|
RadioItem: DropdownMenuRadioItem,
|
||||||
|
CheckboxItem: DropdownMenuCheckboxItem,
|
||||||
|
Sub: DropdownMenuSub,
|
||||||
|
SubTrigger: DropdownMenuSubTrigger,
|
||||||
|
SubContent: DropdownMenuSubContent,
|
||||||
|
})
|
||||||
@@ -133,6 +133,7 @@ const newIcons = {
|
|||||||
"magnifying-glass": `<path d="M15.8332 15.8337L13.0819 13.0824M14.6143 9.39088C14.6143 12.2759 12.2755 14.6148 9.39039 14.6148C6.50532 14.6148 4.1665 12.2759 4.1665 9.39088C4.1665 6.5058 6.50532 4.16699 9.39039 4.16699C12.2755 4.16699 14.6143 6.5058 14.6143 9.39088Z" stroke="currentColor" stroke-linecap="square"/>`,
|
"magnifying-glass": `<path d="M15.8332 15.8337L13.0819 13.0824M14.6143 9.39088C14.6143 12.2759 12.2755 14.6148 9.39039 14.6148C6.50532 14.6148 4.1665 12.2759 4.1665 9.39088C4.1665 6.5058 6.50532 4.16699 9.39039 4.16699C12.2755 4.16699 14.6143 6.5058 14.6143 9.39088Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"plus-small": `<path d="M9.99984 5.41699V10.0003M9.99984 10.0003V14.5837M9.99984 10.0003H5.4165M9.99984 10.0003H14.5832" stroke="currentColor" stroke-linecap="square"/>`,
|
"plus-small": `<path d="M9.99984 5.41699V10.0003M9.99984 10.0003V14.5837M9.99984 10.0003H5.4165M9.99984 10.0003H14.5832" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`,
|
"chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
|
"chevron-right": `<path d="M8.33301 13.3327L11.6663 9.99935L8.33301 6.66602" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"arrow-up": `<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99991 2.24121L16.0921 8.33343L15.2083 9.21731L10.6249 4.63397V17.5001H9.37492V4.63398L4.7916 9.21731L3.90771 8.33343L9.99991 2.24121Z" fill="currentColor"/>`,
|
"arrow-up": `<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99991 2.24121L16.0921 8.33343L15.2083 9.21731L10.6249 4.63397V17.5001H9.37492V4.63398L4.7916 9.21731L3.90771 8.33343L9.99991 2.24121Z" fill="currentColor"/>`,
|
||||||
"check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`,
|
"check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"edit-small-2": `<path d="M17.0834 17.0833V17.5833H17.5834V17.0833H17.0834ZM2.91675 17.0833H2.41675V17.5833H2.91675V17.0833ZM2.91675 2.91659V2.41659H2.41675V2.91659H2.91675ZM9.58341 3.41659H10.0834V2.41659H9.58341V2.91659V3.41659ZM17.5834 10.4166V9.91659H16.5834V10.4166H17.0834H17.5834ZM10.4167 7.08325L10.0632 6.7297L9.91675 6.87615V7.08325H10.4167ZM10.4167 9.58325H9.91675V10.0833H10.4167V9.58325ZM12.9167 9.58325V10.0833H13.1239L13.2703 9.93681L12.9167 9.58325ZM15.4167 2.08325L15.7703 1.7297L15.4167 1.37615L15.0632 1.7297L15.4167 2.08325ZM17.9167 4.58325L18.2703 4.93681L18.6239 4.58325L18.2703 4.2297L17.9167 4.58325ZM17.0834 17.0833V16.5833H2.91675V17.0833V17.5833H17.0834V17.0833ZM2.91675 17.0833H3.41675V2.91659H2.91675H2.41675V17.0833H2.91675ZM2.91675 2.91659V3.41659H9.58341V2.91659V2.41659H2.91675V2.91659ZM17.0834 10.4166H16.5834V17.0833H17.0834H17.5834V10.4166H17.0834ZM10.4167 7.08325H9.91675V9.58325H10.4167H10.9167V7.08325H10.4167ZM10.4167 9.58325V10.0833H12.9167V9.58325V9.08325H10.4167V9.58325ZM10.4167 7.08325L10.7703 7.43681L15.7703 2.43681L15.4167 2.08325L15.0632 1.7297L10.0632 6.7297L10.4167 7.08325ZM15.4167 2.08325L15.0632 2.43681L17.5632 4.93681L17.9167 4.58325L18.2703 4.2297L15.7703 1.7297L15.4167 2.08325ZM17.9167 4.58325L17.5632 4.2297L12.5632 9.2297L12.9167 9.58325L13.2703 9.93681L18.2703 4.93681L17.9167 4.58325Z" fill="currentColor"/>`,
|
"edit-small-2": `<path d="M17.0834 17.0833V17.5833H17.5834V17.0833H17.0834ZM2.91675 17.0833H2.41675V17.5833H2.91675V17.0833ZM2.91675 2.91659V2.41659H2.41675V2.91659H2.91675ZM9.58341 3.41659H10.0834V2.41659H9.58341V2.91659V3.41659ZM17.5834 10.4166V9.91659H16.5834V10.4166H17.0834H17.5834ZM10.4167 7.08325L10.0632 6.7297L9.91675 6.87615V7.08325H10.4167ZM10.4167 9.58325H9.91675V10.0833H10.4167V9.58325ZM12.9167 9.58325V10.0833H13.1239L13.2703 9.93681L12.9167 9.58325ZM15.4167 2.08325L15.7703 1.7297L15.4167 1.37615L15.0632 1.7297L15.4167 2.08325ZM17.9167 4.58325L18.2703 4.93681L18.6239 4.58325L18.2703 4.2297L17.9167 4.58325ZM17.0834 17.0833V16.5833H2.91675V17.0833V17.5833H17.0834V17.0833ZM2.91675 17.0833H3.41675V2.91659H2.91675H2.41675V17.0833H2.91675ZM2.91675 2.91659V3.41659H9.58341V2.91659V2.41659H2.91675V2.91659ZM17.0834 10.4166H16.5834V17.0833H17.0834H17.5834V10.4166H17.0834ZM10.4167 7.08325H9.91675V9.58325H10.4167H10.9167V7.08325H10.4167ZM10.4167 9.58325V10.0833H12.9167V9.58325V9.08325H10.4167V9.58325ZM10.4167 7.08325L10.7703 7.43681L15.7703 2.43681L15.4167 2.08325L15.0632 1.7297L10.0632 6.7297L10.4167 7.08325ZM15.4167 2.08325L15.0632 2.43681L17.5632 4.93681L17.9167 4.58325L18.2703 4.2297L15.7703 1.7297L15.4167 2.08325ZM17.9167 4.58325L17.5632 4.2297L12.5632 9.2297L12.9167 9.58325L13.2703 9.93681L18.2703 4.93681L17.9167 4.58325Z" fill="currentColor"/>`,
|
||||||
@@ -170,6 +171,7 @@ const newIcons = {
|
|||||||
"layout-bottom": `<path d="M18.125 18.125L1.875 18.125L1.875 1.875L18.125 1.875L18.125 18.125ZM3.125 12.8308L3.125 16.875L16.875 16.875L16.875 12.8308L3.125 12.8308ZM3.125 3.125L3.125 11.5808L16.875 11.5808L16.875 3.125L3.125 3.125Z" fill="currentColor"/>`,
|
"layout-bottom": `<path d="M18.125 18.125L1.875 18.125L1.875 1.875L18.125 1.875L18.125 18.125ZM3.125 12.8308L3.125 16.875L16.875 16.875L16.875 12.8308L3.125 12.8308ZM3.125 3.125L3.125 11.5808L16.875 11.5808L16.875 3.125L3.125 3.125Z" fill="currentColor"/>`,
|
||||||
"layout-bottom-partial": `<path d="M2.5 17.5L2.5 12.2059L17.5 12.2059L17.5 17.5L2.5 17.5Z" fill="currentColor" fill-opacity="40%" /><path d="M2.5 17.5L2.5 2.5M2.5 17.5L17.5 17.5M2.5 17.5L2.5 12.2059M2.5 2.5L17.5 2.5M2.5 2.5L2.5 12.2059M17.5 2.5L17.5 17.5M17.5 2.5L17.5 12.2059M17.5 17.5L17.5 12.2059M17.5 12.2059L2.5 12.2059" stroke="currentColor" stroke-linecap="square"/>`,
|
"layout-bottom-partial": `<path d="M2.5 17.5L2.5 12.2059L17.5 12.2059L17.5 17.5L2.5 17.5Z" fill="currentColor" fill-opacity="40%" /><path d="M2.5 17.5L2.5 2.5M2.5 17.5L17.5 17.5M2.5 17.5L2.5 12.2059M2.5 2.5L17.5 2.5M2.5 2.5L2.5 12.2059M17.5 2.5L17.5 17.5M17.5 2.5L17.5 12.2059M17.5 17.5L17.5 12.2059M17.5 12.2059L2.5 12.2059" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
"layout-bottom-full": `<path d="M2.5 17.5L2.5 12.2059L17.5 12.2059L17.5 17.5L2.5 17.5Z" fill="currentColor"/><path d="M2.5 17.5L2.5 2.5M2.5 17.5L17.5 17.5M2.5 17.5L2.5 12.2059M2.5 2.5L17.5 2.5M2.5 2.5L2.5 12.2059M17.5 2.5L17.5 17.5M17.5 2.5L17.5 12.2059M17.5 17.5L17.5 12.2059M17.5 12.2059L2.5 12.2059" stroke="currentColor" stroke-linecap="square"/>`,
|
"layout-bottom-full": `<path d="M2.5 17.5L2.5 12.2059L17.5 12.2059L17.5 17.5L2.5 17.5Z" fill="currentColor"/><path d="M2.5 17.5L2.5 2.5M2.5 17.5L17.5 17.5M2.5 17.5L2.5 12.2059M2.5 2.5L17.5 2.5M2.5 2.5L2.5 12.2059M17.5 2.5L17.5 17.5M17.5 2.5L17.5 12.2059M17.5 17.5L17.5 12.2059M17.5 12.2059L2.5 12.2059" stroke="currentColor" stroke-linecap="square"/>`,
|
||||||
|
"dot-grid": `<path d="M2.08398 9.16602H3.75065V10.8327H2.08398V9.16602Z" fill="currentColor"/><path d="M10.834 9.16602H9.16732V10.8327H10.834V9.16602Z" fill="currentColor"/><path d="M16.2507 9.16602H17.9173V10.8327H16.2507V9.16602Z" fill="currentColor"/><path d="M2.08398 9.16602H3.75065V10.8327H2.08398V9.16602Z" stroke="currentColor"/><path d="M10.834 9.16602H9.16732V10.8327H10.834V9.16602Z" stroke="currentColor"/><path d="M16.2507 9.16602H17.9173V10.8327H16.2507V9.16602Z" stroke="currentColor"/>`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IconProps extends ComponentProps<"svg"> {
|
export interface IconProps extends ComponentProps<"svg"> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { UserMessage } from "@opencode-ai/sdk"
|
import { UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { ComponentProps, createMemo, For, Match, Show, splitProps, Switch } from "solid-js"
|
import { ComponentProps, createMemo, For, Match, Show, splitProps, Switch } from "solid-js"
|
||||||
import { DiffChanges } from "./diff-changes"
|
import { DiffChanges } from "./diff-changes"
|
||||||
import { Spinner } from "./spinner"
|
import { Spinner } from "./spinner"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user