mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-06 09:10:47 -04:00
Compare commits
217 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b3a841dd9 | |||
| aca32eaa1c | |||
| 3ae75d7031 | |||
| 4b5e447961 | |||
| 7a2b8eae76 | |||
| d983b9485d | |||
| 14836de276 | |||
| a0f032c9b9 | |||
| e6132fc6a4 | |||
| 950b608c4d | |||
| 3210df7428 | |||
| cdeb82e9ca | |||
| a9cae7b335 | |||
| 972c0893dd | |||
| e5d89ca567 | |||
| 4ae70d4b0d | |||
| 935cd7481b | |||
| 5553efea5e | |||
| 0ff73ed8a6 | |||
| 5e792d7ac5 | |||
| 4a77e94e3c | |||
| 4c563ea405 | |||
| 5875257462 | |||
| 9701891e94 | |||
| a2ab37c1b6 | |||
| 4d6e2d8efc | |||
| 4407d5d96f | |||
| 244945c0e7 | |||
| c652b2b4e8 | |||
| aabeeb1431 | |||
| 0fbedc5e19 | |||
| 12782fff14 | |||
| ca463a2346 | |||
| 7265cdf817 | |||
| 7baa751351 | |||
| 5b86fa9109 | |||
| aa7e008fe1 | |||
| 792664071c | |||
| a0541ba57a | |||
| 4994bf1b46 | |||
| 1e24514d61 | |||
| 4b1c6300a0 | |||
| db3fb9d316 | |||
| cd79676b42 | |||
| 09e7e0ab70 | |||
| 0e60f66604 | |||
| fc8db6cdf9 | |||
| 5cc37c4ea0 | |||
| 46ad456718 | |||
| 832ffd2303 | |||
| b261430880 | |||
| 545f345848 | |||
| 77ae0b527e | |||
| c1278109c9 | |||
| a7a88d01ef | |||
| 4e0ab6b634 | |||
| d36485b7af | |||
| 1da24f6adb | |||
| e29dd27632 | |||
| 37380e1f94 | |||
| 1309ca7a81 | |||
| c1515316f5 | |||
| b66e7b6fce | |||
| eb398f1951 | |||
| 643c22d21f | |||
| 74acd08ead | |||
| 49ea5aa2ad | |||
| ee1af0fe80 | |||
| dfebf40471 | |||
| 6af6a1295f | |||
| 22821744ef | |||
| 872c9467b2 | |||
| d8249f32a8 | |||
| 982954cc1b | |||
| 4caa458232 | |||
| 6fe8e3973c | |||
| 7816901713 | |||
| 71abca9571 | |||
| 7216a8c86d | |||
| e3e16e58c5 | |||
| a2951a2702 | |||
| 55453dc606 | |||
| 198d7f7e5f | |||
| e3e9fd7aa8 | |||
| 3c56dbcf58 | |||
| ee07ed2dc4 | |||
| 485e4520e7 | |||
| fc115ea367 | |||
| d03b79e61e | |||
| 0acae8211a | |||
| 0af4505756 | |||
| a606e1d2ec | |||
| 0e65700183 | |||
| e6301ca5d5 | |||
| b562863fcc | |||
| db85f01eff | |||
| 1a6fd018f6 | |||
| fdb5bae3c6 | |||
| a9624c0fff | |||
| 316d4c9197 | |||
| 5e886c35d5 | |||
| 5162268f9d | |||
| 0eb899a950 | |||
| 3241f6b8bb | |||
| 2c792f17e6 | |||
| 7d0c6860cd | |||
| c70e393c81 | |||
| 20963c4186 | |||
| 0a778a2789 | |||
| 42c1e61bf4 | |||
| 795b845782 | |||
| 2e434a459a | |||
| ae62bc8b1f | |||
| 187a5fe301 | |||
| fc2afdc92f | |||
| fe5e7cfd1b | |||
| 98d51dde6a | |||
| 5fec5ff424 | |||
| fea6a357bc | |||
| 6b82153263 | |||
| fa8e714d69 | |||
| 90515bc8c3 | |||
| e34042e17a | |||
| 6ff0ce8bc5 | |||
| e88b659545 | |||
| 74048ece2d | |||
| 6646f7264a | |||
| 18e549a474 | |||
| 82249754e7 | |||
| 5a0228897b | |||
| e2920c06a3 | |||
| 4da3aa2eb2 | |||
| efe7f01f41 | |||
| 9ae3d74adc | |||
| 477b6c584d | |||
| 86447b5764 | |||
| fe8f6d7a3e | |||
| 59b5f53509 | |||
| 3eb2db98ed | |||
| 35dec0649d | |||
| 78a7f79143 | |||
| 707ed72381 | |||
| 21880e199d | |||
| 736a85d427 | |||
| fb40dc6b20 | |||
| 483fcdaddb | |||
| 883b71ac36 | |||
| 3e574c71cb | |||
| 4cab66da6c | |||
| 7003efd2da | |||
| 06fe87b361 | |||
| 944fda45e6 | |||
| 343471b98d | |||
| 56528493dc | |||
| e66156c86e | |||
| 8b9b8ca15b | |||
| 50cc641288 | |||
| 4c90bf3e07 | |||
| 4216c1c2a9 | |||
| 4bd7646ccb | |||
| cee7106054 | |||
| f4dfae0bb0 | |||
| 9b5fe10df6 | |||
| b5f336c0ea | |||
| 913c3ae799 | |||
| a68111ca77 | |||
| 5f8a3a574e | |||
| d69e8e5528 | |||
| e5df43f9b7 | |||
| 3c7b229d8b | |||
| 9ab4414aef | |||
| c2cf6fb904 | |||
| 5e69bdbef4 | |||
| f81e28c673 | |||
| 61899d4fa7 | |||
| 7c7ebb0a9d | |||
| 9def7cff2d | |||
| c2ef930d2a | |||
| 3c3d2f5a6e | |||
| f435049d36 | |||
| 1f80de2fa6 | |||
| f194a784b0 | |||
| 89b703c387 | |||
| eff12cb484 | |||
| 593e89b4f4 | |||
| 4d3f703715 | |||
| 123dcc10cc | |||
| 28d8af48a0 | |||
| 10ff6e9830 | |||
| a7b43d82ab | |||
| 9005fd31ed | |||
| d2bded23c3 | |||
| c0cbc37f85 | |||
| 9df61055e2 | |||
| 074136b1e8 | |||
| 8db5951287 | |||
| 97c7e941eb | |||
| 354f5c3281 | |||
| 833706cda4 | |||
| 2a951cea38 | |||
| d9a8d2032a | |||
| d7cdabe8b7 | |||
| e7c74d13cc | |||
| 6ac5a447c2 | |||
| cb4670e6de | |||
| ca0f3902b7 | |||
| e9996342a7 | |||
| a84826061d | |||
| 7a20f77ebf | |||
| 731122bf99 | |||
| f9036734eb | |||
| a99bd3aa2c | |||
| 96efede846 | |||
| 2f66055d25 | |||
| 6995dab1dc | |||
| a0a09f421c | |||
| f3f21194ae |
@@ -0,0 +1,71 @@
|
||||
#
|
||||
# This file is intentionally in the wrong dir, will move and add later....
|
||||
#
|
||||
|
||||
# name: publish-python-sdk
|
||||
|
||||
# on:
|
||||
# release:
|
||||
# types: [published]
|
||||
# workflow_dispatch:
|
||||
|
||||
# jobs:
|
||||
# publish:
|
||||
# runs-on: ubuntu-latest
|
||||
# permissions:
|
||||
# contents: read
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
# - name: Setup Bun
|
||||
# uses: oven-sh/setup-bun@v1
|
||||
# with:
|
||||
# bun-version: 1.2.21
|
||||
|
||||
# - name: Install dependencies (JS/Bun)
|
||||
# run: bun install
|
||||
|
||||
# - name: Install uv
|
||||
# shell: bash
|
||||
# run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# - name: Generate Python SDK from OpenAPI (CLI)
|
||||
# shell: bash
|
||||
# run: |
|
||||
# ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source cli
|
||||
|
||||
# - name: Sync Python dependencies
|
||||
# shell: bash
|
||||
# run: |
|
||||
# ~/.local/bin/uv sync --dev --project packages/sdk/python
|
||||
|
||||
# - name: Set version from release tag
|
||||
# shell: bash
|
||||
# run: |
|
||||
# TAG="${GITHUB_REF_NAME:-}"
|
||||
# if [ -z "$TAG" ]; then
|
||||
# TAG="$(git describe --tags --abbrev=0 || echo 0.0.0)"
|
||||
# fi
|
||||
# echo "Using version: $TAG"
|
||||
# VERSION="$TAG" ~/.local/bin/uv run --project packages/sdk/python python - <<'PY'
|
||||
# import os, re, pathlib
|
||||
# root = pathlib.Path('packages/sdk/python')
|
||||
# pt = (root / 'pyproject.toml').read_text()
|
||||
# version = os.environ.get('VERSION','0.0.0').lstrip('v')
|
||||
# pt = re.sub(r'(?m)^(version\s*=\s*")[^"]+("\s*)$', f"\\1{version}\\2", pt)
|
||||
# (root / 'pyproject.toml').write_text(pt)
|
||||
# # Also update generator config override for consistency
|
||||
# cfgp = root / 'openapi-python-client.yaml'
|
||||
# if cfgp.exists():
|
||||
# cfg = cfgp.read_text()
|
||||
# cfg = re.sub(r'(?m)^(package_version_override:\s*)\S+$', f"\\1{version}", cfg)
|
||||
# cfgp.write_text(cfg)
|
||||
# PY
|
||||
|
||||
# - name: Build and publish to PyPI
|
||||
# env:
|
||||
# PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
# shell: bash
|
||||
# run: |
|
||||
# ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py
|
||||
@@ -24,6 +24,10 @@ jobs:
|
||||
- run: git fetch --force --tags
|
||||
- run: bun install -g @vscode/vsce
|
||||
|
||||
- name: Install extension dependencies
|
||||
run: bun install
|
||||
working-directory: ./sdks/vscode
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
./script/publish
|
||||
|
||||
@@ -53,13 +53,17 @@ jobs:
|
||||
- name: Install OpenCode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Setup npm auth
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
./script/publish.ts
|
||||
env:
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_CHANNEL: latest
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
|
||||
+3
-6
@@ -12,15 +12,12 @@ We want to make it easy for you to contribute to OpenCode. Here are the most com
|
||||
|
||||
However, any UI or core product feature must go through a design review with the core team before implementation.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> We do not accept PRs for core features.
|
||||
|
||||
Take a look at recent git history to understand what usually lands.
|
||||
|
||||
If you are unsure if a PR would be accepted, feel free to ask a maintainer or look for issues with either of the following labels:
|
||||
If you are unsure if a PR would be accepted, feel free to ask a maintainer or look for issues with any of the following labels:
|
||||
|
||||
- [`help wanted`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelp-wanted)
|
||||
- [`good first issue`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)
|
||||
- [`bug`](https://github.com/sst/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug)
|
||||
- [`perf`](https://github.com/sst/opencode/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22perf%22)
|
||||
|
||||
> [!NOTE]
|
||||
> PRs that ignore these guardrails will likely be closed.
|
||||
|
||||
@@ -27,7 +27,7 @@ curl -fsSL https://opencode.ai/install | bash
|
||||
# Package managers
|
||||
npm i -g opencode-ai@latest # or bun/pnpm/yarn
|
||||
scoop bucket add extras; scoop install extras/opencode # Windows
|
||||
winget install opencode # Windows
|
||||
choco install opencode # Windows
|
||||
brew install sst/tap/opencode # macOS and Linux
|
||||
paru -S opencode-bin # Arch Linux
|
||||
```
|
||||
|
||||
@@ -1,116 +1,126 @@
|
||||
# Download Stats
|
||||
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ----------------- | ----------------- | ------------------ |
|
||||
| 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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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) |
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ----------------- | ----------------- | ------------------- |
|
||||
| 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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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) |
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -64,7 +64,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -88,7 +88,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -109,12 +109,14 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/active-element": "2.1.3",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
@@ -139,6 +141,7 @@
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"opencode": "workspace:*",
|
||||
"typescript": "catalog:",
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-icons-spritesheet": "3.0.1",
|
||||
@@ -147,7 +150,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
@@ -163,13 +166,14 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@agentclientprotocol/sdk": "0.4.9",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@hono/standard-validator": "0.1.5",
|
||||
"@hono/zod-validator": "catalog:",
|
||||
@@ -181,6 +185,7 @@
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"ai": "catalog:",
|
||||
@@ -205,12 +210,17 @@
|
||||
"xdg-basedir": "5.1.0",
|
||||
"yargs": "18.0.0",
|
||||
"zod": "catalog:",
|
||||
"zod-to-json-schema": "3.24.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "2.2.10",
|
||||
"@ai-sdk/google-vertex": "3.0.16",
|
||||
"@octokit/webhooks-types": "7.6.1",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@tsconfig/bun": "catalog:",
|
||||
@@ -220,12 +230,11 @@
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vscode-languageserver-types": "3.17.5",
|
||||
"zod-to-json-schema": "3.24.5",
|
||||
},
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -245,7 +254,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.81.0",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -256,7 +265,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -269,10 +278,12 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"luxon": "catalog:",
|
||||
"remeda": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
@@ -290,7 +301,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -337,6 +348,7 @@
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.3.6",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
@@ -372,6 +384,8 @@
|
||||
|
||||
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
|
||||
|
||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.4.9", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-ExwH828LaTGoTTjxuw49l+fwOLA+Yx0+qkWn1TcHMOsY5mVI9CUfkj7ZDhv2klgZ7mJeT+lxX/Dn/KINv1AkNQ=="],
|
||||
|
||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
||||
|
||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
||||
@@ -402,7 +416,7 @@
|
||||
|
||||
"@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.2.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-smartypants": "^3.0.2", "shiki": "^3.0.0", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-c5F5gGrkczUaTVgmMW9g1YMJGzOtRvjjhw6IfGuxarM6ct09MpwysP10US729dy07gg8y+ofVifezvP3BNsWZg=="],
|
||||
|
||||
"@astrojs/mdx": ["@astrojs/mdx@4.3.7", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.8", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-5SRmvMyT/UMWaU2eoD+htnXtE2mUZZEH2K/nEzhuEy+iCsOSuS/DUry59WuKUJRQETi1mgJFdNR4dZLJHYVuRA=="],
|
||||
"@astrojs/mdx": ["@astrojs/mdx@4.3.6", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.7", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-jH04tYgaqLfq3To42+z1oEcXrXUzo3BxZ4fTkb+7BEmOJkQ9/c3iIixFEC+x0GgE8lJb4SuEDGldpAv7+1yY8A=="],
|
||||
|
||||
"@astrojs/prism": ["@astrojs/prism@3.2.0", "", { "dependencies": { "prismjs": "^1.29.0" } }, "sha512-GilTHKGCW6HMq7y3BUv9Ac7GMe/MO9gi9GW62GzKtth0SwukCu/qp2wLiGpEujhY+VVhaG9v7kv/5vFzvf4NYw=="],
|
||||
|
||||
@@ -540,17 +554,17 @@
|
||||
|
||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
|
||||
|
||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.7", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-HtZuh166y0Olbj9bqqySckz0Rw9uHjggJeoGbDx5x+sgezBXlxO6tQSig2RZw5tgObF8mWI8zaPvQMkQZtAODw=="],
|
||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.5", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250924.0" }, "optionalPeers": ["workerd"] }, "sha512-eB3UAIVhrvY+CMZrRXS/bAv5kWdNiH+dgwu+1M1S7keDeonxkfKIGVIrhcCLTkcqYlN30MPURPuVFUEzIWuuvg=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251008.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-yph0H+8mMOK5Z9oDwjb8rI96oTVt4no5lZ43aorcbzsWG9VUIaXSXlBBoB3von6p4YCRW+J3n36fBM9XZ6TLaA=="],
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250927.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-rFtXu/qhZziGOltjhHUCdlqP9wLUhf/CmnjJS0hXffGRAVxsCXhJw+7Vlr+hyRSHjHRhEV+gBFc4pHzT10Stzw=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251008.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Yc4lMGSbM4AEtYRpyDpmk77MsHb6X2BSwJgMgGsLVPmckM7ZHivZkJChfcNQjZ/MGR6nkhYc4iF6TcVS+UMEVw=="],
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250927.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BcNlLVfPyctLjFeIJENhK7OZFkfaysHVA6G6KT1lwum+BaVOutebweLo2zOrH7UQCMDYdpkQOeb5nLDctvs8YA=="],
|
||||
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20251008.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AjoQnylw4/5G6SmfhZRsli7EuIK7ZMhmbxtU0jkpciTlVV8H01OsFOgS1d8zaTXMfkWamEfMouy8oH/L7B9YcQ=="],
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250927.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3c+RuyMj3CkaFS9mmVJyX6nNUdTn2kdWgPrpPoj7VbtU2BEGkrH1a4VAgIAiUh/tYRGUeY3owrUhqCv6L7HmJQ=="],
|
||||
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20251008.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-hRy9yyvzVq1HsqHZUmFkAr0C8JGjAD/PeeVEGCKL3jln3M9sNCKIrbDXiL+efe+EwajJNNlDxpO+s30uVWVaRg=="],
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250927.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-/XtcZnIryAgLvums08r5xiSm5hYfRfUuj2iq/5Jl+Yysx1BmPjYLqjcIIXNATrzpKUrxf3AkvpSI75MBcePgpA=="],
|
||||
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251008.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Gm0RR+ehfNMsScn2pUcn3N9PDUpy7FyvV9ecHEyclKttvztyFOcmsF14bxEaSVv7iM4TxWEBn1rclmYHxDM4ow=="],
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250927.0", "", { "os": "win32", "cpu": "x64" }, "sha512-+m124IiM149QvvzAOrO766uTdILqXJZqzZjqTaMTaWXegjjsJwGSL6v9d71TSFntEwxeXnpJPBkVWyKZFjqrvg=="],
|
||||
|
||||
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="],
|
||||
|
||||
@@ -578,57 +592,57 @@
|
||||
|
||||
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="],
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="],
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="],
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="],
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="],
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="],
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="],
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="],
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="],
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="],
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="],
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="],
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="],
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="],
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="],
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="],
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="],
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="],
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="],
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="],
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="],
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="],
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="],
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="],
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="],
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="],
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="],
|
||||
|
||||
"@expressive-code/core": ["@expressive-code/core@0.41.3", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-9qzohqU7O0+JwMEEgQhnBPOw5DtsQRBXhW++5fvEywsuX44vCGGof1SL5OvPElvNgaWZ4pFZAFSlkNOkGyLwSQ=="],
|
||||
|
||||
@@ -702,7 +716,7 @@
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
|
||||
|
||||
"@internationalized/date": ["@internationalized/date@3.10.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw=="],
|
||||
"@internationalized/date": ["@internationalized/date@3.9.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg=="],
|
||||
|
||||
"@internationalized/number": ["@internationalized/number@3.6.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="],
|
||||
|
||||
@@ -926,7 +940,9 @@
|
||||
|
||||
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
|
||||
|
||||
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
|
||||
"@petamoriken/float16": ["@petamoriken/float16@3.9.2", "", {}, "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog=="],
|
||||
|
||||
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.3.6", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/transformers": "3.13.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.13.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-cKM3HcMmyr5wPFll0bHYcgHplcHgMlL6Dw4Pi4giL0jVt7ySlGwwVyXTRFW5Fva43stOL+EWB+9U5VBDSktBJA=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
@@ -1004,13 +1020,13 @@
|
||||
|
||||
"@rollup/plugin-alias": ["@rollup/plugin-alias@5.1.1", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ=="],
|
||||
|
||||
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.8", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-o1Ug9PxYsF61R7/NXO/GgMZZproLd/WH2XA53Tp9ppf6bU1lMlTtC/gUM6zM3mesi2E0rypk+PNtVrELREyWEQ=="],
|
||||
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.6", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw=="],
|
||||
|
||||
"@rollup/plugin-inject": ["@rollup/plugin-inject@5.0.5", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "estree-walker": "^2.0.2", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg=="],
|
||||
|
||||
"@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="],
|
||||
|
||||
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
|
||||
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.1", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA=="],
|
||||
|
||||
"@rollup/plugin-replace": ["@rollup/plugin-replace@6.0.2", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ=="],
|
||||
|
||||
@@ -1018,53 +1034,53 @@
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.4", "", { "os": "android", "cpu": "arm" }, "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA=="],
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.3", "", { "os": "android", "cpu": "arm" }, "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.4", "", { "os": "android", "cpu": "arm64" }, "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w=="],
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.3", "", { "os": "android", "cpu": "arm64" }, "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg=="],
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw=="],
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ=="],
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw=="],
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ=="],
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.3", "", { "os": "linux", "cpu": "arm" }, "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q=="],
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.3", "", { "os": "linux", "cpu": "arm" }, "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg=="],
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g=="],
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ=="],
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g=="],
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg=="],
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA=="],
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA=="],
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg=="],
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw=="],
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.3", "", { "os": "linux", "cpu": "x64" }, "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.4", "", { "os": "none", "cpu": "arm64" }, "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA=="],
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.3", "", { "os": "none", "cpu": "arm64" }, "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ=="],
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw=="],
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ=="],
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.3", "", { "os": "win32", "cpu": "x64" }, "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w=="],
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.3", "", { "os": "win32", "cpu": "x64" }, "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA=="],
|
||||
|
||||
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
||||
|
||||
"@shikijs/core": ["@shikijs/core@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA=="],
|
||||
"@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
|
||||
|
||||
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-kUTRVKPsB/28H5Ko6qEsyudBiWEDLst+Sfi+hwr59E0GLHV0h8RfgbQU7fdN5Lt9A8R1ulRiZyTvAizkROjwDA=="],
|
||||
|
||||
@@ -1082,7 +1098,7 @@
|
||||
|
||||
"@sindresorhus/is": ["@sindresorhus/is@7.1.0", "", {}, "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA=="],
|
||||
|
||||
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
|
||||
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="],
|
||||
|
||||
"@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="],
|
||||
|
||||
@@ -1096,81 +1112,81 @@
|
||||
|
||||
"@slack/web-api": ["@slack/web-api@6.13.0", "", { "dependencies": { "@slack/logger": "^3.0.0", "@slack/types": "^2.11.0", "@types/is-stream": "^1.1.0", "@types/node": ">=12.0.0", "axios": "^1.7.4", "eventemitter3": "^3.1.0", "form-data": "^2.5.0", "is-electron": "2.2.2", "is-stream": "^1.1.0", "p-queue": "^6.6.1", "p-retry": "^4.0.0" } }, "sha512-dv65crIgdh9ZYHrevLU6XFHTQwTyDmNqEqzuIrV+Vqe/vgiG6w37oex5ePDU1RGm2IJ90H8iOvHFvzdEO/vB+g=="],
|
||||
|
||||
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ=="],
|
||||
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg=="],
|
||||
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.3", "@smithy/types": "^4.8.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.3", "tslib": "^2.6.2" } }, "sha512-xSql8A1Bl41O9JvGU/CtgiLBlwkvpHTSKRlvz9zOBvBCPjXghZ6ZkcVzmV2f7FLAA+80+aqKmIOmy8pEDrtCaw=="],
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.3.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ=="],
|
||||
|
||||
"@smithy/core": ["@smithy/core@3.17.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.3", "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.3", "@smithy/util-stream": "^4.5.3", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Tir3DbfoTO97fEGUZjzGeoXgcQAUBRDTmuH9A8lxuP8ATrgezrAJ6cLuRvwdKN4ZbYNlHgKlBX69Hyu3THYhtg=="],
|
||||
"@smithy/core": ["@smithy/core@3.14.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.2.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-stream": "^4.4.0", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-XJ4z5FxvY/t0Dibms/+gLJrI5niRoY0BCmE02fwmPcRYFPI4KI876xaE79YGWIKnEslMbuQPsIEsoU/DXa0DoA=="],
|
||||
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.3", "@smithy/property-provider": "^4.2.3", "@smithy/types": "^4.8.0", "@smithy/url-parser": "^4.2.3", "tslib": "^2.6.2" } }, "sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw=="],
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big=="],
|
||||
|
||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-rcr0VH0uNoMrtgKuY7sMfyKqbHc4GQaQ6Yp4vwgm+Z6psPuOgL+i/Eo/QWdXRmMinL3EgFM0Z1vkfyPyfzLmjw=="],
|
||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.6.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-XE7CtKfyxYiNZ5vz7OvyTf1osrdbJfmUy+rbh+NLQmZumMGvY0mT0Cq1qKSfhrvLtRYzMsOBuRpi10dyI0EBPg=="],
|
||||
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.3", "@smithy/querystring-builder": "^4.2.3", "@smithy/types": "^4.8.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw=="],
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.0", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/querystring-builder": "^4.2.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-BG3KSmsx9A//KyIfw+sqNmWFr1YBUr+TwpxFT7yPqAk0yyDh7oSNgzfNH7pS6OC099EGx2ltOULvumCFe8bcgw=="],
|
||||
|
||||
"@smithy/hash-node": ["@smithy/hash-node@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g=="],
|
||||
"@smithy/hash-node": ["@smithy/hash-node@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA=="],
|
||||
|
||||
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q=="],
|
||||
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A=="],
|
||||
|
||||
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
|
||||
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.3", "", { "dependencies": { "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA=="],
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.0", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ=="],
|
||||
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.4", "", { "dependencies": { "@smithy/core": "^3.17.0", "@smithy/middleware-serde": "^4.2.3", "@smithy/node-config-provider": "^4.3.3", "@smithy/shared-ini-file-loader": "^4.3.3", "@smithy/types": "^4.8.0", "@smithy/url-parser": "^4.2.3", "@smithy/util-middleware": "^4.2.3", "tslib": "^2.6.2" } }, "sha512-/RJhpYkMOaUZoJEkddamGPPIYeKICKXOu/ojhn85dKDM0n5iDIhjvYAQLP3K5FPhgB203O3GpWzoK2OehEoIUw=="],
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.0", "", { "dependencies": { "@smithy/core": "^3.14.0", "@smithy/middleware-serde": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jFVjuQeV8TkxaRlcCNg0GFVgg98tscsmIrIwRFeC74TIUyLE3jmY9xgc1WXrPQYRjQNK3aRoaIk6fhFRGOIoGw=="],
|
||||
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.3", "@smithy/protocol-http": "^5.3.3", "@smithy/service-error-classification": "^4.2.3", "@smithy/smithy-client": "^4.9.0", "@smithy/types": "^4.8.0", "@smithy/util-middleware": "^4.2.3", "@smithy/util-retry": "^4.2.3", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-vSgABQAkuUHRO03AhR2rWxVQ1un284lkBn+NFawzdahmzksAoOeVMnXXsuPViL4GlhRHXqFaMlc8Mj04OfQk1w=="],
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/service-error-classification": "^4.2.0", "@smithy/smithy-client": "^4.7.0", "@smithy/types": "^4.6.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-yaVBR0vQnOnzex45zZ8ZrPzUnX73eUC8kVFaAAbn04+6V7lPtxn56vZEBBAhgS/eqD6Zm86o6sJs6FuQVoX5qg=="],
|
||||
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.3", "", { "dependencies": { "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ=="],
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.0", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw=="],
|
||||
|
||||
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA=="],
|
||||
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg=="],
|
||||
|
||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.3", "", { "dependencies": { "@smithy/property-provider": "^4.2.3", "@smithy/shared-ini-file-loader": "^4.3.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA=="],
|
||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.0", "", { "dependencies": { "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA=="],
|
||||
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.2", "", { "dependencies": { "@smithy/abort-controller": "^4.2.3", "@smithy/protocol-http": "^5.3.3", "@smithy/querystring-builder": "^4.2.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-MHFvTjts24cjGo1byXqhXrbqm7uznFD/ESFx8npHMWTFQVdBZjrT1hKottmp69LBTRm/JQzP/sn1vPt0/r6AYQ=="],
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.3.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/querystring-builder": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q=="],
|
||||
|
||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ=="],
|
||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw=="],
|
||||
|
||||
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw=="],
|
||||
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q=="],
|
||||
|
||||
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ=="],
|
||||
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A=="],
|
||||
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA=="],
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA=="],
|
||||
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0" } }, "sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g=="],
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0" } }, "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA=="],
|
||||
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ=="],
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ=="],
|
||||
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.3", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.3", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA=="],
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g=="],
|
||||
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.9.0", "", { "dependencies": { "@smithy/core": "^3.17.0", "@smithy/middleware-endpoint": "^4.3.4", "@smithy/middleware-stack": "^4.2.3", "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "@smithy/util-stream": "^4.5.3", "tslib": "^2.6.2" } }, "sha512-qz7RTd15GGdwJ3ZCeBKLDQuUQ88m+skh2hJwcpPm1VqLeKzgZvXf6SrNbxvx7uOqvvkjCMXqx3YB5PDJyk00ww=="],
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.7.0", "", { "dependencies": { "@smithy/core": "^3.14.0", "@smithy/middleware-endpoint": "^4.3.0", "@smithy/middleware-stack": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-stream": "^4.4.0", "tslib": "^2.6.2" } }, "sha512-3BDx/aCCPf+kkinYf5QQhdQ9UAGihgOVqI3QO5xQfSaIWvUE4KYLtiGRWsNe1SR7ijXC0QEPqofVp5Sb0zC8xQ=="],
|
||||
|
||||
"@smithy/types": ["@smithy/types@4.8.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ=="],
|
||||
"@smithy/types": ["@smithy/types@4.6.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA=="],
|
||||
|
||||
"@smithy/url-parser": ["@smithy/url-parser@4.2.3", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw=="],
|
||||
"@smithy/url-parser": ["@smithy/url-parser@4.2.0", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A=="],
|
||||
|
||||
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
|
||||
"@smithy/util-base64": ["@smithy/util-base64@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA=="],
|
||||
|
||||
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="],
|
||||
|
||||
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="],
|
||||
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-U8q1WsSZFjXijlD7a4wsDQOvOwV+72iHSfq1q7VD+V75xP/pdtm0WIGuaFJ3gcADDOKj2MIBn4+zisi140HEnQ=="],
|
||||
|
||||
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="],
|
||||
|
||||
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
|
||||
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.3", "", { "dependencies": { "@smithy/property-provider": "^4.2.3", "@smithy/smithy-client": "^4.9.0", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-vqHoybAuZXbFXZqgzquiUXtdY+UT/aU33sxa4GBPkiYklmR20LlCn+d3Wc3yA5ZM13gQ92SZe/D8xh6hkjx+IQ=="],
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.2.0", "", { "dependencies": { "@smithy/property-provider": "^4.2.0", "@smithy/smithy-client": "^4.7.0", "@smithy/types": "^4.6.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-qzHp7ZDk1Ba4LDwQVCNp90xPGqSu7kmL7y5toBpccuhi3AH7dcVBIT/pUxYcInK4jOy6FikrcTGq5wxcka8UaQ=="],
|
||||
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.4", "", { "dependencies": { "@smithy/config-resolver": "^4.3.3", "@smithy/credential-provider-imds": "^4.2.3", "@smithy/node-config-provider": "^4.3.3", "@smithy/property-provider": "^4.2.3", "@smithy/smithy-client": "^4.9.0", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-X5/xrPHedifo7hJUUWKlpxVb2oDOiqPUXlvsZv1EZSjILoutLiJyWva3coBpn00e/gPSpH8Rn2eIbgdwHQdW7Q=="],
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.0", "", { "dependencies": { "@smithy/config-resolver": "^4.3.0", "@smithy/credential-provider-imds": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/smithy-client": "^4.7.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-FxUHS3WXgx3bTWR6yQHNHHkQHZm/XKIi/CchTnKvBulN6obWpcbzJ6lDToXn+Wp0QlVKd7uYAz2/CTw1j7m+Kg=="],
|
||||
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ=="],
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg=="],
|
||||
|
||||
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
|
||||
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw=="],
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA=="],
|
||||
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.3", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg=="],
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.0", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg=="],
|
||||
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.5.3", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.4", "@smithy/node-http-handler": "^4.4.2", "@smithy/types": "^4.8.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-oZvn8a5bwwQBNYHT2eNo0EU8Kkby3jeIg1P2Lu9EQtqDxki1LIjGRJM6dJ5CZUig8QmLxWxqOKWvg3mVoOBs5A=="],
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.4.0", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.0", "@smithy/node-http-handler": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.2.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-vtO7ktbixEcrVzMRmpQDnw/Ehr9UWjBvSJ9fyAbadKkC4w5Cm/4lMO8cHz8Ysb8uflvQUNRcuux/oNHKPXkffg=="],
|
||||
|
||||
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
|
||||
|
||||
@@ -1178,6 +1194,8 @@
|
||||
|
||||
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
|
||||
|
||||
"@solid-primitives/active-element": ["@solid-primitives/active-element@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9t5K4aR2naVDj950XU8OjnLgOg94a8k5wr6JNOPK+N5ESLsJDq42c1ZP8UKpewi1R+wplMMxiM6OPKRzbxJY7A=="],
|
||||
|
||||
"@solid-primitives/event-bus": ["@solid-primitives/event-bus@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-l+n10/51neGcMaP3ypYt21bXfoeWh8IaC8k7fYuY3ww2a8S1Zv2N2a7FF5Qn+waTu86l0V8/nRHjkyqVIZBYwA=="],
|
||||
|
||||
"@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="],
|
||||
@@ -1252,7 +1270,7 @@
|
||||
|
||||
"@tanstack/directive-functions-plugin": ["@tanstack/directive-functions-plugin@1.121.21", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/router-utils": "^1.121.21", "babel-dead-code-elimination": "^1.0.10", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "vite": ">=6.0.0" } }, "sha512-B9z/HbF7gJBaRHieyX7f2uQ4LpLLAVAEutBZipH6w+CYD6RHRJvSVPzECGHF7icFhNWTiJQL2QR6K07s59yzEw=="],
|
||||
|
||||
"@tanstack/router-utils": ["@tanstack/router-utils@1.133.3", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.5", "@babel/preset-typescript": "^7.27.1", "ansis": "^4.1.0", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-miPFlt0aG6ID5VDolYuRXgLS7cofvbZGMvHwf2Wmyxjo6GLp/kxxpkQrfM4T1I5cwjwYZZAQmdUKbVHwFZz9sQ=="],
|
||||
"@tanstack/router-utils": ["@tanstack/router-utils@1.132.31", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.5", "@babel/preset-typescript": "^7.27.1", "ansis": "^4.1.0", "diff": "^8.0.2", "fast-glob": "^3.3.3", "pathe": "^2.0.3" } }, "sha512-uf8mQ3wV58K8TL5XXBoWhkYxmCV7LLWbbf6AvcxdhnCnBNmXBGlY+T8RdsRnXyI2Iyp2HfHaVZ+8H3CEQedXfw=="],
|
||||
|
||||
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.121.21", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@babel/core": "^7.26.8", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@tanstack/directive-functions-plugin": "1.121.21", "babel-dead-code-elimination": "^1.0.9", "tiny-invariant": "^1.3.3" } }, "sha512-a05fzK+jBGacsSAc1vE8an7lpBh4H0PyIEcivtEyHLomgSeElAJxm9E2It/0nYRZ5Lh23m0okbhzJNaYWZpAOg=="],
|
||||
|
||||
@@ -1336,7 +1354,7 @@
|
||||
|
||||
"@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
|
||||
|
||||
"@types/send": ["@types/send@1.2.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ=="],
|
||||
"@types/send": ["@types/send@0.17.5", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w=="],
|
||||
|
||||
"@types/serve-static": ["@types/serve-static@1.15.9", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA=="],
|
||||
|
||||
@@ -1370,7 +1388,7 @@
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"@vercel/nft": ["@vercel/nft@0.30.3", "", { "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", "@rollup/pluginutils": "^5.1.3", "acorn": "^8.6.0", "acorn-import-attributes": "^1.9.5", "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", "glob": "^10.4.5", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", "resolve-from": "^5.0.0" }, "bin": { "nft": "out/cli.js" } }, "sha512-UEq+eF0ocEf9WQCV1gktxKhha36KDs7jln5qii6UpPf5clMqDc0p3E7d9l2Smx0i9Pm1qpq4S4lLfNl97bbv6w=="],
|
||||
"@vercel/nft": ["@vercel/nft@0.30.2", "", { "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", "@rollup/pluginutils": "^5.1.3", "acorn": "^8.6.0", "acorn-import-attributes": "^1.9.5", "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", "glob": "^10.4.5", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", "resolve-from": "^5.0.0" }, "bin": { "nft": "out/cli.js" } }, "sha512-pquXF3XZFg/T3TBor08rUhIGgOhdSilbn7WQLVP/aVSSO+25Rs4H/m3nxNDQ2x3znX7Z3yYjryN8xaLwypcwQg=="],
|
||||
|
||||
"@vinxi/listhen": ["@vinxi/listhen@1.5.6", "", { "dependencies": { "@parcel/watcher": "^2.3.0", "@parcel/watcher-wasm": "2.3.0", "citty": "^0.1.5", "clipboardy": "^4.0.0", "consola": "^3.2.3", "defu": "^6.1.4", "get-port-please": "^3.1.2", "h3": "^1.10.0", "http-shutdown": "^1.2.2", "jiti": "^1.21.0", "mlly": "^1.5.0", "node-forge": "^1.3.1", "pathe": "^1.1.2", "std-env": "^3.7.0", "ufo": "^1.3.2", "untun": "^0.1.3", "uqr": "^0.1.2" }, "bin": { "listen": "bin/listhen.mjs", "listhen": "bin/listhen.mjs" } }, "sha512-WSN1z931BtasZJlgPp704zJFnQFRg7yzSjkm3MzAWQYe4uXFXlFr1hc5Ac2zae5/HDOz5x1/zDM5Cb54vTCnWw=="],
|
||||
|
||||
@@ -1488,9 +1506,9 @@
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"bare-events": ["bare-events@2.8.0", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-AOhh6Bg5QmFIXdViHbMc2tLDsBIRxdkIaIddPslJF9Z5De3APBScuqGP2uThXnIpqFrgoxMNC6km7uXNIMLHXA=="],
|
||||
"bare-events": ["bare-events@2.7.0", "", {}, "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA=="],
|
||||
|
||||
"bare-fs": ["bare-fs@4.4.11", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-Bejmm9zRMvMTRoHS+2adgmXw1ANZnCNx+B5dgZpGwlP1E3x6Yuxea8RToddHUbWtVV0iUMWqsgZr8+jcgUI2SA=="],
|
||||
"bare-fs": ["bare-fs@4.4.5", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-TCtu93KGLu6/aiGWzMr12TmSRS6nKdfhAnzTQRbXoSWxkbb9eRd53jQ51jG7g1gYjjtto3hbBrrhzg6djcgiKg=="],
|
||||
|
||||
"bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="],
|
||||
|
||||
@@ -1498,13 +1516,13 @@
|
||||
|
||||
"bare-stream": ["bare-stream@2.7.0", "", { "dependencies": { "streamx": "^2.21.0" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer", "bare-events"] }, "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A=="],
|
||||
|
||||
"bare-url": ["bare-url@2.3.0", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-c+RCqMSZbkz97Mw1LWR0gcOqwK82oyYKfLoHJ8k13ybi1+I80ffdDzUy0TdAburdrR/kI0/VuN8YgEnJqX+Nyw=="],
|
||||
"bare-url": ["bare-url@2.2.2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA=="],
|
||||
|
||||
"base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.17", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-j5zJcx6golJYTG6c05LUZ3Z8Gi+M62zRT/ycz4Xq4iCOdpcxwg7ngEYD4KA0eWZC7U17qh/Smq8bYbACJ0ipBA=="],
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA=="],
|
||||
|
||||
"bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="],
|
||||
|
||||
@@ -1568,7 +1586,7 @@
|
||||
|
||||
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001751", "", {}, "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001746", "", {}, "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
@@ -1590,7 +1608,7 @@
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
|
||||
"ci-info": ["ci-info@4.3.0", "", {}, "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ=="],
|
||||
|
||||
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
|
||||
|
||||
@@ -1732,13 +1750,13 @@
|
||||
|
||||
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
"detect-libc": ["detect-libc@2.1.1", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="],
|
||||
|
||||
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
||||
|
||||
"deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="],
|
||||
|
||||
"devalue": ["devalue@5.4.1", "", {}, "sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ=="],
|
||||
"devalue": ["devalue@5.3.2", "", {}, "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
@@ -1764,7 +1782,7 @@
|
||||
|
||||
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
|
||||
|
||||
"dot-prop": ["dot-prop@10.1.0", "", { "dependencies": { "type-fest": "^5.0.0" } }, "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q=="],
|
||||
"dot-prop": ["dot-prop@9.0.0", "", { "dependencies": { "type-fest": "^4.18.2" } }, "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ=="],
|
||||
|
||||
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||
|
||||
@@ -1786,9 +1804,9 @@
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.237", "", {}, "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.228", "", {}, "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
"emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="],
|
||||
|
||||
"emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="],
|
||||
|
||||
@@ -1828,7 +1846,7 @@
|
||||
|
||||
"esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
|
||||
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
|
||||
|
||||
"esbuild-plugin-copy": ["esbuild-plugin-copy@2.1.1", "", { "dependencies": { "chalk": "^4.1.2", "chokidar": "^3.5.3", "fs-extra": "^10.0.1", "globby": "^11.0.3" }, "peerDependencies": { "esbuild": ">= 0.14.0" } }, "sha512-Bk66jpevTcV8KMFzZI1P7MZKZ+uDcrZm2G2egZ2jNIvVnivDpodZI+/KnpL3Jnap0PBdIHU7HwFGB8r+vV5CVw=="],
|
||||
|
||||
@@ -1918,7 +1936,7 @@
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
||||
|
||||
"fontace": ["fontace@0.3.1", "", { "dependencies": { "@types/fontkit": "^2.0.8", "fontkit": "^2.0.4" } }, "sha512-9f5g4feWT1jWT8+SbL85aLIRLIXUaDygaM2xPXRmzPYxrOMNok79Lr3FGJoKVNKibE0WCunNiEVG2mwuE+2qEg=="],
|
||||
"fontace": ["fontace@0.3.0", "", { "dependencies": { "@types/fontkit": "^2.0.8", "fontkit": "^2.0.4" } }, "sha512-czoqATrcnxgWb/nAkfyIrRp6Q8biYj7nGnL6zfhTcX+JKKpWHFBnb8uNMw/kZr7u++3Y3wYSYoZgHkCcsuBpBg=="],
|
||||
|
||||
"fontkit": ["fontkit@2.0.4", "", { "dependencies": { "@swc/helpers": "^0.5.12", "brotli": "^1.3.2", "clone": "^2.1.2", "dfa": "^1.2.0", "fast-deep-equal": "^3.1.3", "restructure": "^3.0.0", "tiny-inflate": "^1.0.3", "unicode-properties": "^1.4.0", "unicode-trie": "^2.0.0" } }, "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g=="],
|
||||
|
||||
@@ -1980,7 +1998,7 @@
|
||||
|
||||
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.12.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw=="],
|
||||
"get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
|
||||
|
||||
"giget": ["giget@1.2.5", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="],
|
||||
|
||||
@@ -2134,7 +2152,7 @@
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"ioredis": ["ioredis@5.8.1", "", { "dependencies": { "@ioredis/commands": "1.4.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-Qho8TgIamqEPdgiMadJwzRMW3TudIg6vpg4YONokGDudy4eqRIJtDbVX72pfLBcWxvbn3qm/40TyGUObdW4tLQ=="],
|
||||
"ioredis": ["ioredis@5.8.0", "", { "dependencies": { "@ioredis/commands": "1.4.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-AUXbKn9gvo9hHKvk6LbZJQSKn/qIfkWXrnsyL9Yrf+oeXmla9Nmf6XEumOddyhM8neynpK5oAV6r9r99KBuwzA=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
@@ -2516,7 +2534,7 @@
|
||||
|
||||
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
||||
|
||||
"miniflare": ["miniflare@4.20251008.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251008.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-sKCNYNzXG6l8qg0Oo7y8WcDKcpbgw0qwZsxNpdZilFTR4EavRow2TlcwuPSVN99jqAjhz0M4VXvTdSGdtJ2VfQ=="],
|
||||
"miniflare": ["miniflare@4.20250927.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20250927.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-CP0Q9Ytipid/Q6fJ2gAsVJ3yIMdx1+GoivA+EON68/ZLt66QwUFtpFeqdOUOKDmMbf/NFzjsKsce6h/8KjjYXg=="],
|
||||
|
||||
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
|
||||
@@ -2554,13 +2572,13 @@
|
||||
|
||||
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
|
||||
|
||||
"nitropack": ["nitropack@2.12.7", "", { "dependencies": { "@cloudflare/kv-asset-handler": "^0.4.0", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.2", "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-terser": "^0.4.4", "@vercel/nft": "^0.30.2", "archiver": "^7.0.1", "c12": "^3.3.0", "chokidar": "^4.0.3", "citty": "^0.1.6", "compatx": "^0.2.0", "confbox": "^0.2.2", "consola": "^3.4.2", "cookie-es": "^2.0.0", "croner": "^9.1.0", "crossws": "^0.3.5", "db0": "^0.3.4", "defu": "^6.1.4", "destr": "^2.0.5", "dot-prop": "^10.1.0", "esbuild": "^0.25.10", "escape-string-regexp": "^5.0.0", "etag": "^1.8.1", "exsolve": "^1.0.7", "globby": "^15.0.0", "gzip-size": "^7.0.0", "h3": "^1.15.4", "hookable": "^5.5.3", "httpxy": "^0.1.7", "ioredis": "^5.8.1", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.2.0", "listhen": "^1.9.0", "magic-string": "^0.30.19", "magicast": "^0.3.5", "mime": "^4.1.0", "mlly": "^1.8.0", "node-fetch-native": "^1.6.7", "node-mock-http": "^1.0.3", "ofetch": "^1.4.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "pretty-bytes": "^7.1.0", "radix3": "^1.1.2", "rollup": "^4.52.4", "rollup-plugin-visualizer": "^6.0.4", "scule": "^1.3.0", "semver": "^7.7.2", "serve-placeholder": "^2.0.2", "serve-static": "^2.2.0", "source-map": "^0.7.6", "std-env": "^3.9.0", "ufo": "^1.6.1", "ultrahtml": "^1.6.0", "uncrypto": "^0.1.3", "unctx": "^2.4.1", "unenv": "^2.0.0-rc.21", "unimport": "^5.4.1", "unplugin-utils": "^0.3.1", "unstorage": "^1.17.1", "untyped": "^2.0.0", "unwasm": "^0.3.11", "youch": "4.1.0-beta.11", "youch-core": "^0.3.3" }, "peerDependencies": { "xml2js": "^0.6.2" }, "optionalPeers": ["xml2js"], "bin": { "nitro": "dist/cli/index.mjs", "nitropack": "dist/cli/index.mjs" } }, "sha512-HWyzMBj2d8b14J6Cfnxv97ztnuHIgXNcrGiWCruLfb2ZfKsp6OCbZYJm5T9sv/ZKl8LedhatrMKG66HWJux9Rg=="],
|
||||
"nitropack": ["nitropack@2.12.6", "", { "dependencies": { "@cloudflare/kv-asset-handler": "^0.4.0", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-terser": "^0.4.4", "@vercel/nft": "^0.30.1", "archiver": "^7.0.1", "c12": "^3.2.0", "chokidar": "^4.0.3", "citty": "^0.1.6", "compatx": "^0.2.0", "confbox": "^0.2.2", "consola": "^3.4.2", "cookie-es": "^2.0.0", "croner": "^9.1.0", "crossws": "^0.3.5", "db0": "^0.3.2", "defu": "^6.1.4", "destr": "^2.0.5", "dot-prop": "^9.0.0", "esbuild": "^0.25.9", "escape-string-regexp": "^5.0.0", "etag": "^1.8.1", "exsolve": "^1.0.7", "globby": "^14.1.0", "gzip-size": "^7.0.0", "h3": "^1.15.4", "hookable": "^5.5.3", "httpxy": "^0.1.7", "ioredis": "^5.7.0", "jiti": "^2.5.1", "klona": "^2.0.6", "knitwork": "^1.2.0", "listhen": "^1.9.0", "magic-string": "^0.30.19", "magicast": "^0.3.5", "mime": "^4.0.7", "mlly": "^1.8.0", "node-fetch-native": "^1.6.7", "node-mock-http": "^1.0.3", "ofetch": "^1.4.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "pretty-bytes": "^7.0.1", "radix3": "^1.1.2", "rollup": "^4.50.1", "rollup-plugin-visualizer": "^6.0.3", "scule": "^1.3.0", "semver": "^7.7.2", "serve-placeholder": "^2.0.2", "serve-static": "^2.2.0", "source-map": "^0.7.6", "std-env": "^3.9.0", "ufo": "^1.6.1", "ultrahtml": "^1.6.0", "uncrypto": "^0.1.3", "unctx": "^2.4.1", "unenv": "^2.0.0-rc.21", "unimport": "^5.2.0", "unplugin-utils": "^0.3.0", "unstorage": "^1.17.1", "untyped": "^2.0.0", "unwasm": "^0.3.11", "youch": "^4.1.0-beta.11", "youch-core": "^0.3.3" }, "peerDependencies": { "xml2js": "^0.6.2" }, "optionalPeers": ["xml2js"], "bin": { "nitro": "dist/cli/index.mjs", "nitropack": "dist/cli/index.mjs" } }, "sha512-DEq31s0SP4/Z5DIoVBRo9DbWFPWwIoYD4cQMEz7eE+iJMiAP+1k9A3B9kcc6Ihc0jDJmfUcHYyh6h2XlynCx6g=="],
|
||||
|
||||
"nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
|
||||
|
||||
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
|
||||
|
||||
"node-abi": ["node-abi@3.78.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ=="],
|
||||
"node-abi": ["node-abi@3.77.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ=="],
|
||||
|
||||
"node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="],
|
||||
|
||||
@@ -2576,7 +2594,7 @@
|
||||
|
||||
"node-mock-http": ["node-mock-http@1.0.3", "", {}, "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.25", "", {}, "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA=="],
|
||||
"node-releases": ["node-releases@2.0.21", "", {}, "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw=="],
|
||||
|
||||
"nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="],
|
||||
|
||||
@@ -2648,7 +2666,7 @@
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"package-manager-detector": ["package-manager-detector@1.4.1", "", {}, "sha512-dSMiVLBEA4XaNJ0PRb4N5cV/SEP4BWrWZKBmfF+OUm2pQTiZ6DDkKeWaltwu3JRhLoy59ayIkJ00cx9K9CaYTg=="],
|
||||
"package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="],
|
||||
|
||||
"pagefind": ["pagefind@1.4.0", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.4.0", "@pagefind/darwin-x64": "1.4.0", "@pagefind/freebsd-x64": "1.4.0", "@pagefind/linux-arm64": "1.4.0", "@pagefind/linux-x64": "1.4.0", "@pagefind/windows-x64": "1.4.0" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g=="],
|
||||
|
||||
@@ -2880,9 +2898,9 @@
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rollup": ["rollup@4.52.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.4", "@rollup/rollup-android-arm64": "4.52.4", "@rollup/rollup-darwin-arm64": "4.52.4", "@rollup/rollup-darwin-x64": "4.52.4", "@rollup/rollup-freebsd-arm64": "4.52.4", "@rollup/rollup-freebsd-x64": "4.52.4", "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", "@rollup/rollup-linux-arm-musleabihf": "4.52.4", "@rollup/rollup-linux-arm64-gnu": "4.52.4", "@rollup/rollup-linux-arm64-musl": "4.52.4", "@rollup/rollup-linux-loong64-gnu": "4.52.4", "@rollup/rollup-linux-ppc64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-musl": "4.52.4", "@rollup/rollup-linux-s390x-gnu": "4.52.4", "@rollup/rollup-linux-x64-gnu": "4.52.4", "@rollup/rollup-linux-x64-musl": "4.52.4", "@rollup/rollup-openharmony-arm64": "4.52.4", "@rollup/rollup-win32-arm64-msvc": "4.52.4", "@rollup/rollup-win32-ia32-msvc": "4.52.4", "@rollup/rollup-win32-x64-gnu": "4.52.4", "@rollup/rollup-win32-x64-msvc": "4.52.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ=="],
|
||||
"rollup": ["rollup@4.52.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.3", "@rollup/rollup-android-arm64": "4.52.3", "@rollup/rollup-darwin-arm64": "4.52.3", "@rollup/rollup-darwin-x64": "4.52.3", "@rollup/rollup-freebsd-arm64": "4.52.3", "@rollup/rollup-freebsd-x64": "4.52.3", "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", "@rollup/rollup-linux-arm-musleabihf": "4.52.3", "@rollup/rollup-linux-arm64-gnu": "4.52.3", "@rollup/rollup-linux-arm64-musl": "4.52.3", "@rollup/rollup-linux-loong64-gnu": "4.52.3", "@rollup/rollup-linux-ppc64-gnu": "4.52.3", "@rollup/rollup-linux-riscv64-gnu": "4.52.3", "@rollup/rollup-linux-riscv64-musl": "4.52.3", "@rollup/rollup-linux-s390x-gnu": "4.52.3", "@rollup/rollup-linux-x64-gnu": "4.52.3", "@rollup/rollup-linux-x64-musl": "4.52.3", "@rollup/rollup-openharmony-arm64": "4.52.3", "@rollup/rollup-win32-arm64-msvc": "4.52.3", "@rollup/rollup-win32-ia32-msvc": "4.52.3", "@rollup/rollup-win32-x64-gnu": "4.52.3", "@rollup/rollup-win32-x64-msvc": "4.52.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A=="],
|
||||
|
||||
"rollup-plugin-visualizer": ["rollup-plugin-visualizer@6.0.5", "", { "dependencies": { "open": "^8.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg=="],
|
||||
"rollup-plugin-visualizer": ["rollup-plugin-visualizer@6.0.3", "", { "dependencies": { "open": "^8.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
@@ -3022,7 +3040,7 @@
|
||||
|
||||
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||
|
||||
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
|
||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
@@ -3062,9 +3080,9 @@
|
||||
|
||||
"strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
|
||||
|
||||
"style-to-js": ["style-to-js@1.1.18", "", { "dependencies": { "style-to-object": "1.0.11" } }, "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg=="],
|
||||
"style-to-js": ["style-to-js@1.1.17", "", { "dependencies": { "style-to-object": "1.0.9" } }, "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.11", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow=="],
|
||||
"style-to-object": ["style-to-object@1.0.9", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
|
||||
|
||||
@@ -3076,11 +3094,9 @@
|
||||
|
||||
"system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="],
|
||||
|
||||
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
"tapable": ["tapable@2.2.3", "", {}, "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg=="],
|
||||
|
||||
"tar": ["tar@7.5.1", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g=="],
|
||||
|
||||
@@ -3134,6 +3150,8 @@
|
||||
|
||||
"tsscmp": ["tsscmp@1.0.6", "", {}, "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="],
|
||||
|
||||
"tsx": ["tsx@4.20.5", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw=="],
|
||||
|
||||
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
|
||||
|
||||
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
||||
@@ -3200,11 +3218,11 @@
|
||||
|
||||
"unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="],
|
||||
|
||||
"unimport": ["unimport@5.5.0", "", { "dependencies": { "acorn": "^8.15.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.19", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.10", "unplugin-utils": "^0.3.0" } }, "sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg=="],
|
||||
"unimport": ["unimport@5.4.1", "", { "dependencies": { "acorn": "^8.15.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.19", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.10", "unplugin-utils": "^0.3.0" } }, "sha512-wMZ2JKUCleCK2zfRHeWcbrUHKXOC3SVBYkyn/wTGzh0THX6sT4hSjuKXxKANN4/WMbT6ZPM4JzcDcnhD2x9Bpg=="],
|
||||
|
||||
"unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
||||
"unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="],
|
||||
|
||||
"unist-util-modify-children": ["unist-util-modify-children@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="],
|
||||
|
||||
@@ -3220,7 +3238,7 @@
|
||||
|
||||
"unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="],
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],
|
||||
|
||||
"universal-github-app-jwt": ["universal-github-app-jwt@2.2.2", "", {}, "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw=="],
|
||||
|
||||
@@ -3232,7 +3250,7 @@
|
||||
|
||||
"unplugin": ["unplugin@2.3.10", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw=="],
|
||||
|
||||
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
|
||||
"unplugin-utils": ["unplugin-utils@0.3.0", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg=="],
|
||||
|
||||
"unstorage": ["unstorage@1.17.1", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.4.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ=="],
|
||||
|
||||
@@ -3316,9 +3334,9 @@
|
||||
|
||||
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
||||
|
||||
"workerd": ["workerd@1.20251008.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251008.0", "@cloudflare/workerd-darwin-arm64": "1.20251008.0", "@cloudflare/workerd-linux-64": "1.20251008.0", "@cloudflare/workerd-linux-arm64": "1.20251008.0", "@cloudflare/workerd-windows-64": "1.20251008.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-HwaJmXO3M1r4S8x2ea2vy8Rw/y/38HRQuK/gNDRQ7w9cJXn6xSl1sIIqKCffULSUjul3wV3I3Nd/GfbmsRReEA=="],
|
||||
"workerd": ["workerd@1.20250927.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250927.0", "@cloudflare/workerd-darwin-arm64": "1.20250927.0", "@cloudflare/workerd-linux-64": "1.20250927.0", "@cloudflare/workerd-linux-arm64": "1.20250927.0", "@cloudflare/workerd-windows-64": "1.20250927.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-6kyAGPGYNvn5mbpCJJ48VebN7QGSrvU/WJXgd4EQz20PyqjJAxHcEGGAJ+0Da0u/ewrN1+6fuMKQ1ALLBPiTWg=="],
|
||||
|
||||
"wrangler": ["wrangler@4.43.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.7", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251008.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20251008.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251008.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-IBNqXlYHSUSCNNWj/tQN4hFiQy94l7fTxEnJWETXyW69+cjUyjQ7MfeoId3vIV9KBgY8y5M5uf2XulU95OikJg=="],
|
||||
"wrangler": ["wrangler@4.40.3", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.5", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250927.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20250927.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250927.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-Ltf/0EwyJ9yJeWuCCGHOZDrGGMfZhVECUsJRbeBt1JTV2g7Ebw6FYrXOJhFEEfj1Mr51Cbt3nYI07TMyfxhPwA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
@@ -3326,7 +3344,7 @@
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
|
||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||
|
||||
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
||||
|
||||
@@ -3376,6 +3394,8 @@
|
||||
|
||||
"@actions/github/@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="],
|
||||
|
||||
"@agentclientprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
||||
|
||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
||||
@@ -3390,15 +3410,19 @@
|
||||
|
||||
"@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.7", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA=="],
|
||||
|
||||
"@astrojs/cloudflare/vite": ["vite@6.4.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-oLnWs9Hak/LOlKjeSpOwD6JMks8BeICEdYMJBf6P4Lac/pO9tKiv/XhXnAM7nNfSkZahjlCZu9sS50zL8fSnsw=="],
|
||||
"@astrojs/cloudflare/@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251001.0", "", {}, "sha512-MXseDjmqL1hIdQCqwHDMG8SE60W4FdwqLsofZjo/KtLH9zFcoQfZkCYyQrdfEJINiSoNJjrup7WR6KsqiFUSsg=="],
|
||||
|
||||
"@astrojs/cloudflare/vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="],
|
||||
|
||||
"@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
|
||||
|
||||
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.8", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.4", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.13.0", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-uFNyFWadnULWK2cOw4n0hLKeu+xaVWeuECdP10cQ3K2fkybtTlhb7J7TcScdjmS8Yps7oje9S/ehYMfZrhrgCg=="],
|
||||
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.7", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.3", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.12.2", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-KXGdq6/BC18doBCYXp08alHlWChH0hdD2B1qv9wIyOHbvwI5K6I7FhSta8dq1hBQNdun8YkKPR013D/Hm8xd0g=="],
|
||||
|
||||
"@astrojs/sitemap/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@astrojs/solid-js/vite": ["vite@6.4.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-oLnWs9Hak/LOlKjeSpOwD6JMks8BeICEdYMJBf6P4Lac/pO9tKiv/XhXnAM7nNfSkZahjlCZu9sS50zL8fSnsw=="],
|
||||
"@astrojs/solid-js/vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="],
|
||||
|
||||
"@astrojs/solid-js/vite-plugin-solid": ["vite-plugin-solid@2.11.9", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-bTA6p+bspXZsuulSd2y6aTzegF8xGaJYcq1Uyh/mv+W4DQtzCgL9nN6n2fsTaxp/dMk+ZHHKgGndlNeooqHLKw=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
@@ -3494,6 +3518,10 @@
|
||||
|
||||
"@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
|
||||
|
||||
"@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/transformers@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/types": "3.13.0" } }, "sha512-833lcuVzcRiG+fXvgslWsM2f4gHpjEgui1ipIknSizRuTgMkNZupiXE5/TVJ6eSYfhNBFhBZKkReKWO2GgYmqA=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki": ["shiki@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="],
|
||||
|
||||
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||
|
||||
"@rollup/plugin-commonjs/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
@@ -3502,6 +3530,10 @@
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@shikijs/core/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
|
||||
|
||||
"@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA=="],
|
||||
|
||||
"@slack/bolt/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||
|
||||
"@slack/oauth/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="],
|
||||
@@ -3510,6 +3542,8 @@
|
||||
|
||||
"@slack/socket-mode/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
|
||||
|
||||
"@slack/socket-mode/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
|
||||
|
||||
"@slack/web-api/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="],
|
||||
|
||||
"@slack/web-api/eventemitter3": ["eventemitter3@3.1.2", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="],
|
||||
@@ -3518,6 +3552,8 @@
|
||||
|
||||
"@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
|
||||
|
||||
"@solidjs/start/vite-plugin-solid": ["vite-plugin-solid@2.11.9", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-bTA6p+bspXZsuulSd2y6aTzegF8xGaJYcq1Uyh/mv+W4DQtzCgL9nN6n2fsTaxp/dMk+ZHHKgGndlNeooqHLKw=="],
|
||||
|
||||
"@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||
@@ -3534,14 +3570,10 @@
|
||||
|
||||
"@tanstack/router-utils/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"@types/serve-static/@types/send": ["@types/send@0.17.5", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w=="],
|
||||
|
||||
"@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@vercel/nft/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
|
||||
"@vinxi/listhen/h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="],
|
||||
|
||||
"@vinxi/plugin-directives/magicast": ["magicast@0.2.11", "", { "dependencies": { "@babel/parser": "^7.22.16", "@babel/types": "^7.22.17", "recast": "^0.23.4" } }, "sha512-6saXbRDA1HMkqbsvHOU6HBjCVgZT460qheRkLhJQHWAbhXoWESI3Kn/dGGXyKs15FFKR85jsUqFx2sMK0wy/5g=="],
|
||||
|
||||
"@vinxi/server-components/magicast": ["magicast@0.2.11", "", { "dependencies": { "@babel/parser": "^7.22.16", "@babel/types": "^7.22.17", "recast": "^0.23.4" } }, "sha512-6saXbRDA1HMkqbsvHOU6HBjCVgZT460qheRkLhJQHWAbhXoWESI3Kn/dGGXyKs15FFKR85jsUqFx2sMK0wy/5g=="],
|
||||
@@ -3564,7 +3596,7 @@
|
||||
|
||||
"astro/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
||||
|
||||
"astro/vite": ["vite@6.4.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-oLnWs9Hak/LOlKjeSpOwD6JMks8BeICEdYMJBf6P4Lac/pO9tKiv/XhXnAM7nNfSkZahjlCZu9sS50zL8fSnsw=="],
|
||||
"astro/vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="],
|
||||
|
||||
"astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
@@ -3606,8 +3638,6 @@
|
||||
|
||||
"dir-glob/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||
|
||||
"dot-prop/type-fest": ["type-fest@5.1.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg=="],
|
||||
|
||||
"drizzle-kit/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
|
||||
|
||||
"editorconfig/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
|
||||
@@ -3678,8 +3708,6 @@
|
||||
|
||||
"miniflare/undici": ["undici@7.14.0", "", {}, "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ=="],
|
||||
|
||||
"miniflare/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||
|
||||
"miniflare/youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
|
||||
|
||||
"miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
|
||||
@@ -3692,7 +3720,7 @@
|
||||
|
||||
"nitropack/c12": ["c12@3.3.1", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-LcWQ01LT9tkoUINHgpIOv3mMs+Abv7oVCrtpMRi1PaapVEpWoMga5WuT7/DqFTu7URP9ftbOmimNw1KNIGh9DQ=="],
|
||||
|
||||
"nitropack/globby": ["globby@15.0.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", "ignore": "^7.0.5", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw=="],
|
||||
"nitropack/globby": ["globby@14.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", "ignore": "^7.0.3", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="],
|
||||
|
||||
"nitropack/h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="],
|
||||
|
||||
@@ -3770,6 +3798,8 @@
|
||||
|
||||
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
||||
|
||||
"shiki/@shikijs/core": ["@shikijs/core@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA=="],
|
||||
|
||||
"sitemap/sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
|
||||
|
||||
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
@@ -3820,7 +3850,7 @@
|
||||
|
||||
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"vinxi/vite": ["vite@6.4.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-oLnWs9Hak/LOlKjeSpOwD6JMks8BeICEdYMJBf6P4Lac/pO9tKiv/XhXnAM7nNfSkZahjlCZu9sS50zL8fSnsw=="],
|
||||
"vinxi/vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="],
|
||||
|
||||
"vinxi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
@@ -3866,7 +3896,7 @@
|
||||
|
||||
"@actions/github/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
||||
|
||||
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.4", "", {}, "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw=="],
|
||||
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.3", "", {}, "sha512-6Pl0bQEIChuW5wqN7jdKrzWfCscW2rG/Cz+fzt4PhSQX2ivBpnhXgFUCs0M3DCYvjYHnPVG2W36X5rmFjZ62sw=="],
|
||||
|
||||
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
|
||||
|
||||
@@ -3924,8 +3954,6 @@
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||
|
||||
"@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
|
||||
|
||||
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
|
||||
|
||||
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
|
||||
@@ -4018,6 +4046,8 @@
|
||||
|
||||
"@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"@octokit/auth-oauth-app/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||
@@ -4058,6 +4088,18 @@
|
||||
|
||||
"@opencode-ai/web/shiki/@shikijs/types": ["@shikijs/types@3.4.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg=="],
|
||||
|
||||
"@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="],
|
||||
|
||||
"@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
|
||||
|
||||
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
||||
|
||||
"@slack/web-api/p-queue/p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
|
||||
@@ -4082,8 +4124,6 @@
|
||||
|
||||
"@vercel/nft/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"@vinxi/listhen/h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
|
||||
|
||||
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
@@ -4310,8 +4350,6 @@
|
||||
|
||||
"@actions/github/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||
|
||||
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
|
||||
|
||||
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
|
||||
|
||||
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
|
||||
@@ -4400,6 +4438,8 @@
|
||||
|
||||
"nitropack/serve-static/send/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"nitropack/serve-static/send/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
|
||||
@@ -4420,6 +4460,8 @@
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"prebuild-install/tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
+2
-2
@@ -104,7 +104,7 @@ To test locally:
|
||||
- `MODEL`: The model used by opencode. Same as the `MODEL` defined in the GitHub workflow.
|
||||
- `ANTHROPIC_API_KEY`: Your model provider API key. Same as the keys defined in the GitHub workflow.
|
||||
- `GITHUB_RUN_ID`: Dummy value to emulate GitHub action environment.
|
||||
- `MOCK_TOKEN`: A GitHub persontal access token. This token is used to verify you have `admin` or `write` access to the test repo. Generate a token [here](https://github.com/settings/personal-access-tokens).
|
||||
- `MOCK_TOKEN`: A GitHub personal access token. This token is used to verify you have `admin` or `write` access to the test repo. Generate a token [here](https://github.com/settings/personal-access-tokens).
|
||||
- `MOCK_EVENT`: Mock GitHub event payload (see templates below).
|
||||
- `/path/to/opencode`: Path to your cloned opencode repo. `bun /path/to/opencode/github/index.ts` runs your local version of `opencode`.
|
||||
|
||||
@@ -118,7 +118,7 @@ Replace:
|
||||
|
||||
- `"owner":"sst"` with repo owner
|
||||
- `"repo":"hello-world"` with repo name
|
||||
- `"actor":"fwang"` with the GitHub username of commentor
|
||||
- `"actor":"fwang"` with the GitHub username of commenter
|
||||
- `"number":4` with the GitHub issue id
|
||||
- `"body":"hey opencode, summarize thread"` with comment body
|
||||
|
||||
|
||||
+4
-3
@@ -25,6 +25,7 @@
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.3.6",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
@@ -52,8 +53,8 @@
|
||||
"turbo": "2.5.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*"
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -62,7 +63,7 @@
|
||||
"license": "MIT",
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 120
|
||||
"printWidth": 100
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"esbuild",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||
"start": "vinxi start",
|
||||
"version": "0.15.9"
|
||||
"version": "0.15.29"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
|
||||
@@ -36,6 +36,9 @@ export function Header(props: { zen?: boolean }) {
|
||||
<li>
|
||||
<a href="/docs">Docs</a>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/enterprise">Enterprise</A>
|
||||
</li>
|
||||
<li>
|
||||
<Switch>
|
||||
<Match when={props.zen}>
|
||||
@@ -107,6 +110,9 @@ export function Header(props: { zen?: boolean }) {
|
||||
<li>
|
||||
<a href="/docs">Docs</a>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/enterprise">Enterprise</A>
|
||||
</li>
|
||||
<li>
|
||||
<Switch>
|
||||
<Match when={props.zen}>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { AWS } from "@opencode-ai/console-core/aws.js"
|
||||
|
||||
interface EnterpriseFormData {
|
||||
name: string
|
||||
role: string
|
||||
email: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export async function POST(event: APIEvent) {
|
||||
try {
|
||||
const body = (await event.request.json()) as EnterpriseFormData
|
||||
|
||||
// Validate required fields
|
||||
if (!body.name || !body.role || !body.email || !body.message) {
|
||||
return Response.json({ error: "All fields are required" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(body.email)) {
|
||||
return Response.json({ error: "Invalid email format" }, { status: 400 })
|
||||
}
|
||||
|
||||
// Create email content
|
||||
const emailContent = `
|
||||
${body.message}<br><br>
|
||||
--
|
||||
${body.name}<br>
|
||||
${body.role}<br>
|
||||
${body.email}`.trim()
|
||||
|
||||
// Send email using AWS SES
|
||||
await AWS.sendEmail({
|
||||
to: "contact@anoma.ly",
|
||||
subject: `Enterprise Inquiry from ${body.name}`,
|
||||
body: emailContent,
|
||||
})
|
||||
|
||||
return Response.json({ success: true, message: "Form submitted successfully" }, { status: 200 })
|
||||
} catch (error) {
|
||||
console.error("Error processing enterprise form:", error)
|
||||
return Response.json({ error: "Internal server error" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
::selection {
|
||||
background: var(--color-background-interactive);
|
||||
color: var(--color-text-strong);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background: var(--color-background-interactive);
|
||||
color: var(--color-text-inverted);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[data-page="enterprise"] {
|
||||
--color-background: hsl(0, 20%, 99%);
|
||||
--color-background-weak: hsl(0, 8%, 97%);
|
||||
--color-background-weak-hover: hsl(0, 8%, 94%);
|
||||
--color-background-strong: hsl(0, 5%, 12%);
|
||||
--color-background-strong-hover: hsl(0, 5%, 18%);
|
||||
--color-background-interactive: hsl(62, 84%, 88%);
|
||||
--color-background-interactive-weaker: hsl(64, 74%, 95%);
|
||||
|
||||
--color-text: hsl(0, 1%, 39%);
|
||||
--color-text-weak: hsl(0, 1%, 60%);
|
||||
--color-text-weaker: hsl(30, 2%, 81%);
|
||||
--color-text-strong: hsl(0, 5%, 12%);
|
||||
--color-text-inverted: hsl(0, 20%, 99%);
|
||||
--color-text-success: hsl(119, 100%, 35%);
|
||||
|
||||
--color-border: hsl(30, 2%, 81%);
|
||||
--color-border-weak: hsl(0, 1%, 85%);
|
||||
|
||||
--color-icon: hsl(0, 1%, 55%);
|
||||
--color-success: hsl(142, 76%, 36%);
|
||||
|
||||
background: var(--color-background);
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text);
|
||||
padding-bottom: 5rem;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--color-background: hsl(0, 9%, 7%);
|
||||
--color-background-weak: hsl(0, 6%, 10%);
|
||||
--color-background-weak-hover: hsl(0, 6%, 15%);
|
||||
--color-background-strong: hsl(0, 15%, 94%);
|
||||
--color-background-strong-hover: hsl(0, 15%, 97%);
|
||||
--color-background-interactive: hsl(62, 100%, 90%);
|
||||
--color-background-interactive-weaker: hsl(60, 20%, 8%);
|
||||
|
||||
--color-text: hsl(0, 4%, 71%);
|
||||
--color-text-weak: hsl(0, 2%, 49%);
|
||||
--color-text-weaker: hsl(0, 3%, 28%);
|
||||
--color-text-strong: hsl(0, 15%, 94%);
|
||||
--color-text-inverted: hsl(0, 9%, 7%);
|
||||
--color-text-success: hsl(119, 60%, 72%);
|
||||
|
||||
|
||||
--color-border: hsl(0, 3%, 28%);
|
||||
--color-border-weak: hsl(0, 4%, 23%);
|
||||
|
||||
--color-icon: hsl(10, 3%, 43%);
|
||||
--color-success: hsl(142, 76%, 46%);
|
||||
}
|
||||
|
||||
/* Header and Footer styles - copied from index.css */
|
||||
[data-component="top"] {
|
||||
padding: 24px 5rem;
|
||||
height: 80px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--color-background);
|
||||
border-bottom: 1px solid var(--color-border-weak);
|
||||
z-index: 10;
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
padding: 24px 1.5rem;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 34px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
[data-component="nav-desktop"] {
|
||||
ul {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 48px;
|
||||
li {
|
||||
display: inline-block;
|
||||
a {
|
||||
text-decoration: none;
|
||||
span {
|
||||
color: var(--color-text-weak);
|
||||
}
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
text-decoration-thickness: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="nav-mobile"] {
|
||||
button > svg {
|
||||
color: var(--color-icon);
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="nav-mobile-toggle"] {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
cursor: pointer;
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
[data-component="nav-mobile-toggle"]:hover {
|
||||
background: var(--color-background-weak);
|
||||
}
|
||||
|
||||
[data-component="nav-mobile"] {
|
||||
display: none;
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
display: block;
|
||||
|
||||
[data-component="nav-mobile-icon"] {
|
||||
cursor: pointer;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
[data-component="nav-mobile-menu-list"] {
|
||||
position: fixed;
|
||||
background: var(--color-background);
|
||||
top: 80px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100vh;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 20px 0;
|
||||
|
||||
li {
|
||||
a {
|
||||
text-decoration: none;
|
||||
padding: 20px;
|
||||
display: block;
|
||||
|
||||
span {
|
||||
color: var(--color-text-weak);
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: var(--color-background-weak);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="logo dark"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
[data-slot="logo light"] {
|
||||
display: none;
|
||||
}
|
||||
[data-slot="logo dark"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="footer"] {
|
||||
border-top: 1px solid var(--color-border-weak);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@media (max-width: 65rem) {
|
||||
border-bottom: 1px solid var(--color-border-weak);
|
||||
}
|
||||
|
||||
[data-slot="cell"] {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
display: block;
|
||||
|
||||
span {
|
||||
color: var(--color-text-weak);
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: var(--color-background-weak);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
text-decoration-thickness: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="cell"] + [data-slot="cell"] {
|
||||
border-left: 1px solid var(--color-border-weak);
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile: third column on its own row */
|
||||
@media (max-width: 25rem) {
|
||||
flex-wrap: wrap;
|
||||
|
||||
[data-slot="cell"] {
|
||||
flex: 1 0 100%;
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--color-border-weak);
|
||||
}
|
||||
|
||||
[data-slot="cell"]:nth-child(1) {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="container"] {
|
||||
max-width: 67.5rem;
|
||||
margin: 0 auto;
|
||||
border: 1px solid var(--color-border-weak);
|
||||
border-top: none;
|
||||
|
||||
@media (max-width: 65rem) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="content"] {
|
||||
}
|
||||
|
||||
[data-component="enterprise-content"] {
|
||||
padding: 4rem 0;
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="enterprise-columns"] {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4rem;
|
||||
padding: 4rem 5rem;
|
||||
|
||||
@media (max-width: 80rem) {
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 3rem;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="enterprise-column-1"] {
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-strong);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-strong);
|
||||
margin: 2rem 0 1rem 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
[data-component="testimonial"] {
|
||||
margin-top: 4rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-strong);
|
||||
|
||||
[data-component="quotation"] {
|
||||
svg {
|
||||
margin-bottom: 1rem;
|
||||
opacity: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="testimonial-logo"] {
|
||||
svg {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="enterprise-column-2"] {
|
||||
[data-component="enterprise-form"] {
|
||||
padding: 0;
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-strong);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
[data-component="form-group"] {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-weak);
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
input:-webkit-autofill:active {
|
||||
transition: background-color 5000000s ease-in-out 0s;
|
||||
}
|
||||
|
||||
input:-webkit-autofill {
|
||||
-webkit-text-fill-color: var(--color-text-strong) !important;
|
||||
}
|
||||
|
||||
input:-moz-autofill {
|
||||
-moz-text-fill-color: var(--color-text-strong) !important;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--color-border-weak);
|
||||
border-radius: 4px;
|
||||
background: var(--color-background-weak);
|
||||
color: var(--color-text-strong);
|
||||
font-family: inherit;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-weak);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: var(--color-background-interactive-weaker);
|
||||
outline: none;
|
||||
border: none;
|
||||
color: var(--color-text-strong);
|
||||
border: 1px solid var(--color-background-strong);
|
||||
box-shadow: 0 0 0 3px var(--color-background-interactive);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--color-background-interactive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="submit-button"] {
|
||||
padding: 0.5rem 1.5rem;
|
||||
background: var(--color-background-strong);
|
||||
color: var(--color-text-inverted);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--color-background-strong-hover);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="success-message"] {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem 0;
|
||||
color: var(--color-text-success);
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="faq"] {
|
||||
border-top: 1px solid var(--color-border-weak);
|
||||
padding: 4rem 5rem;
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
[data-slot="section-title"] {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-strong);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 12px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
margin-bottom: 24px;
|
||||
line-height: 200%;
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
line-height: 180%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="faq-question"] {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 8px;
|
||||
color: var(--color-text-strong);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
[data-slot="faq-icon-plus"] {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-weak);
|
||||
margin-top: 2px;
|
||||
|
||||
[data-closed] & {
|
||||
display: block;
|
||||
}
|
||||
[data-expanded] & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
[data-slot="faq-icon-minus"] {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-weak);
|
||||
margin-top: 2px;
|
||||
|
||||
[data-closed] & {
|
||||
display: none;
|
||||
}
|
||||
[data-expanded] & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
[data-slot="faq-question-text"] {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="faq-answer"] {
|
||||
margin-left: 40px;
|
||||
margin-bottom: 32px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="legal"] {
|
||||
color: var(--color-text-weak);
|
||||
text-align: center;
|
||||
padding: 2rem 5rem;
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text-weak);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text-strong);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
text-decoration-thickness: 1px;
|
||||
|
||||
&:hover {
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
import "./index.css"
|
||||
import { Title, Meta } from "@solidjs/meta"
|
||||
import { createSignal, Show } from "solid-js"
|
||||
import { Header } from "~/component/header"
|
||||
import { Footer } from "~/component/footer"
|
||||
import { Legal } from "~/component/legal"
|
||||
import { Faq } from "~/component/faq"
|
||||
|
||||
export default function Enterprise() {
|
||||
const [formData, setFormData] = createSignal({
|
||||
name: "",
|
||||
role: "",
|
||||
email: "",
|
||||
message: "",
|
||||
})
|
||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||
const [showSuccess, setShowSuccess] = createSignal(false)
|
||||
|
||||
const handleInputChange = (field: string) => (e: Event) => {
|
||||
const target = e.target as HTMLInputElement | HTMLTextAreaElement
|
||||
setFormData((prev) => ({ ...prev, [field]: target.value }))
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: Event) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/enterprise", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData()),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
setShowSuccess(true)
|
||||
setFormData({
|
||||
name: "",
|
||||
role: "",
|
||||
email: "",
|
||||
message: "",
|
||||
})
|
||||
setTimeout(() => setShowSuccess(false), 5000)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to submit form:", error)
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main data-page="enterprise">
|
||||
<Title>OpenCode | Enterprise solutions for your organisation</Title>
|
||||
<Meta name="description" content="Contact OpenCode for enterprise solutions" />
|
||||
<div data-component="container">
|
||||
<Header />
|
||||
|
||||
<div data-component="content">
|
||||
<section data-component="enterprise-content">
|
||||
<div data-component="enterprise-columns">
|
||||
<div data-component="enterprise-column-1">
|
||||
<h2>Your code is yours</h2>
|
||||
<p>
|
||||
OpenCode operates securely inside your organization with no data or context stored
|
||||
and no licensing restrictions or ownership claims. Start a trial with your team
|
||||
, then deploy it across your organization by integrating it with your SSO and internal AI gateway.
|
||||
</p>
|
||||
<p>Let us know and how we can help.</p>
|
||||
|
||||
<Show when={false}>
|
||||
<div data-component="testimonial">
|
||||
<div data-component="quotation">
|
||||
<svg
|
||||
width="20"
|
||||
height="17"
|
||||
viewBox="0 0 20 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.4118 0L16.5882 9.20833H20V17H12.2353V10.0938L16 0H19.4118ZM7.17647 0L4.35294 9.20833H7.76471V17H0V10.0938L3.76471 0H7.17647Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
Thanks to OpenCode, we found a way to create software to track all our assets —
|
||||
even the imaginary ones.
|
||||
<div data-component="testimonial-logo">
|
||||
<svg
|
||||
width="80"
|
||||
height="79"
|
||||
viewBox="0 0 80 79"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M0 39.3087L10.0579 29.251L15.6862 34.7868L13.7488 36.7248L10.3345 33.2186L8.48897 35.0639L11.8111 38.4781L9.96557 40.4156L6.55181 37.0018L4.06028 39.4928L7.56674 42.9991L5.62884 44.845L0 39.3087Z"
|
||||
fill="#0083C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M17.7182 36.8164L20.2094 39.4003L16.6108 46.9666L22.2393 41.3374L24.3615 43.46L14.2118 53.5179L11.9047 51.1187L15.4112 43.3677L9.78254 49.0888L7.66016 46.9666L17.7182 36.8164Z"
|
||||
fill="#0083C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M42.8139 61.915L45.3055 64.4064L41.6145 71.9731L47.243 66.3441L49.3652 68.4663L39.3077 78.5244L36.9088 76.1252L40.5072 68.374L34.7866 74.0953L32.6641 71.9731L42.8139 61.915Z"
|
||||
fill="#0083C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M16.4258 55.7324L26.4833 45.582L28.6061 47.7042C31.0049 50.1034 32.3892 51.9497 30.1746 54.1642C28.7902 55.548 27.6831 56.0094 26.1145 54.9016L26.0222 54.994C27.2218 56.1941 26.9448 57.1162 25.4688 58.5931L23.9 60.1615C23.4383 60.6232 22.8847 61.2693 22.7927 62.0067L20.6705 59.8845C20.7625 59.146 21.3161 58.5008 21.778 58.1316L23.5307 56.3788C24.269 55.6403 23.715 54.2555 23.254 53.8872L22.8847 53.4256L18.548 57.7623L16.4258 55.7324ZM24.3611 51.9495C25.4689 53.0563 26.4833 53.3332 27.4984 52.3178C28.5134 51.3957 28.2367 50.3802 27.1295 49.1812L24.3611 51.9495Z"
|
||||
fill="#0083C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M33.4952 66.9899C31.096 69.3891 28.8815 68.4659 27.4047 66.9899C26.021 65.6062 25.0978 63.3907 27.4972 60.9003L31.8336 56.6548C34.2333 54.2556 36.4478 55.0864 37.9241 56.5635C39.308 58.0396 40.2311 60.2541 37.8315 62.6531L33.4952 66.9899ZM29.0659 63.5752C28.6048 64.0369 28.6048 64.7753 29.1583 65.3292C29.6196 65.8821 30.4502 65.7897 30.8194 65.4215L36.2633 59.9769C36.7246 59.6076 36.7246 58.7779 36.171 58.3164C35.7097 57.7626 34.8791 57.7626 34.5101 58.2241L29.0659 63.5752Z"
|
||||
fill="#0083C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M78.5267 39.308L68.2845 29.0654L47.5231 49.735L49.6453 51.8572L68.2845 33.2179L74.3746 39.308L47.2461 66.3435L49.3683 68.4657L78.5267 39.308Z"
|
||||
fill="#0083C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M49.6443 51.8577L43.3695 45.4902L64.0386 24.8215L53.7969 14.4873L33.0352 35.2482L35.1574 37.3705L53.7969 18.7315L59.7947 24.8215L39.1251 45.4902L47.5221 53.9799L49.6443 51.8577Z"
|
||||
fill="#2D9C5C"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M35.1564 37.3706L28.7896 31.0038L49.5515 10.3347L39.3088 0L10.0586 29.2507L12.1804 31.2804L39.3088 4.24476L45.3066 10.3347L24.6377 31.0038L33.0342 39.4008L35.1564 37.3706Z"
|
||||
fill="#E92A35"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M77.2332 52.4105C76.0336 52.4105 75.111 51.4884 75.111 50.196C75.111 48.9046 76.0336 47.9814 77.2332 47.9814C78.3405 47.9814 79.263 48.9046 79.263 50.196C79.263 51.4884 78.3405 52.4105 77.2332 52.4105ZM77.2332 52.9643C78.7098 52.9643 80.0015 51.6729 80.0015 50.196C80.0015 48.6276 78.7096 47.4287 77.2332 47.4287C75.6644 47.4287 74.4648 48.6278 74.4648 50.196C74.4647 51.6731 75.6643 52.9643 77.2332 52.9643ZM76.1259 51.7653H76.6797V50.3804H77.0485L77.8788 51.7653H78.4332L77.6023 50.3804C78.1558 50.2881 78.4332 50.0122 78.4332 49.5507C78.4332 48.9046 78.0633 48.6276 77.3253 48.6276H76.1257V51.7653H76.1259ZM76.6797 49.0892H77.2332C77.5102 49.0892 77.8788 49.0892 77.8788 49.4586C77.8788 49.9202 77.6023 49.9202 77.2332 49.9202H76.6797V49.0892Z"
|
||||
fill="#0083C6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div data-component="enterprise-column-2">
|
||||
<div data-component="enterprise-form">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div data-component="form-group">
|
||||
<label for="name">Full name</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
required
|
||||
value={formData().name}
|
||||
onInput={handleInputChange("name")}
|
||||
placeholder="Jeff Bezos"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-component="form-group">
|
||||
<label for="role">Role</label>
|
||||
<input
|
||||
id="role"
|
||||
type="text"
|
||||
required
|
||||
value={formData().role}
|
||||
onInput={handleInputChange("role")}
|
||||
placeholder="Executive Chairman"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-component="form-group">
|
||||
<label for="email">Company email</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
required
|
||||
value={formData().email}
|
||||
onInput={handleInputChange("email")}
|
||||
placeholder="jeff@amazon.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-component="form-group">
|
||||
<label for="message">What problem are you trying to solve?</label>
|
||||
<textarea
|
||||
id="message"
|
||||
required
|
||||
rows={5}
|
||||
value={formData().message}
|
||||
onInput={handleInputChange("message")}
|
||||
placeholder="We need help with..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled={isSubmitting()} data-component="submit-button">
|
||||
{isSubmitting() ? "Sending..." : "Send"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{showSuccess() && (
|
||||
<div data-component="success-message">
|
||||
Message sent, we'll be in touch soon.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-component="faq">
|
||||
<div data-slot="section-title">
|
||||
<h3>FAQ</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
<Faq question="What is OpenCode Enterprise?">
|
||||
OpenCode Enterprise is for organizations that want to ensure that their code and
|
||||
data never leaves their infrastructure. It can do this by using a centralized
|
||||
config that integrates with your SSO and internal AI gateway.
|
||||
</Faq>
|
||||
</li>
|
||||
<li>
|
||||
<Faq question="How do I get started with OpenCode Enterprise?">
|
||||
Simply start with an internal trial with your team. OpenCode by default does not
|
||||
store your code or context data, making it easy to get started. Then contact us to
|
||||
discuss pricing and implementation options.
|
||||
</Faq>
|
||||
</li>
|
||||
<li>
|
||||
<Faq question="How does enterprise pricing work?">
|
||||
We offer per-seat enterprise pricing. If you have your own LLM gateway, we do not
|
||||
charge for tokens used. For further details, contact us for a custom quote based
|
||||
on your organization's needs.
|
||||
</Faq>
|
||||
</li>
|
||||
<li>
|
||||
<Faq question="Is my data secure with OpenCode Enterprise?">
|
||||
Yes. OpenCode does not store your code or context data. All processing happens
|
||||
locally or through direct API calls to your AI provider. With central config and
|
||||
SSO integration, your data remains secure within your organization's
|
||||
infrastructure.
|
||||
</Faq>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
<Legal />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -827,12 +827,8 @@ body {
|
||||
outline: none;
|
||||
border: none;
|
||||
color: var(--color-text-strong);
|
||||
|
||||
border: 1px solid var(--color-background-strong); /* Tailwind blue-600 as example */
|
||||
|
||||
/* Tailwind-style ring */
|
||||
border: 1px solid var(--color-background-strong);
|
||||
box-shadow: 0 0 0 3px var(--color-background-interactive);
|
||||
/* mimics "ring-2 ring-blue-600/50" */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: none;
|
||||
|
||||
@@ -16,7 +16,7 @@ export function formatDateForTable(date: Date) {
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
}
|
||||
return date.toLocaleDateString("en-GB", options).replace(",", ",")
|
||||
return date.toLocaleDateString(undefined, options).replace(",", ",")
|
||||
}
|
||||
|
||||
export function formatDateUTC(date: Date) {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export class AuthError extends Error {}
|
||||
export class CreditsError extends Error {}
|
||||
export class MonthlyLimitError extends Error {}
|
||||
export class UserLimitError extends Error {}
|
||||
export class ModelError extends Error {}
|
||||
@@ -0,0 +1 @@
|
||||
export type Format = "anthropic" | "openai" | "oa-compat"
|
||||
+65
-60
@@ -1,67 +1,41 @@
|
||||
import { z } from "zod"
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import path from "node:path"
|
||||
import { and, Database, eq, isNull, lt, or, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
||||
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
|
||||
import { BillingTable, UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
|
||||
import { Identifier } from "@opencode-ai/console-core/identifier.js"
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
import { Billing } from "../../../../core/src/billing"
|
||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
||||
import { ZenData } from "@opencode-ai/console-core/model.js"
|
||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
|
||||
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
|
||||
import { logger } from "./logger"
|
||||
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError } from "./error"
|
||||
import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider"
|
||||
import { Format } from "./format"
|
||||
import { anthropicHelper } from "./provider/anthropic"
|
||||
import { openaiHelper } from "./provider/openai"
|
||||
import { oaCompatHelper } from "./provider/openai-compatible"
|
||||
|
||||
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
||||
type Model = ZenData["models"][string]
|
||||
|
||||
export async function handler(
|
||||
input: APIEvent,
|
||||
opts: {
|
||||
modifyBody?: (body: any) => any
|
||||
setAuthHeader: (headers: Headers, apiKey: string) => void
|
||||
format: Format
|
||||
parseApiKey: (headers: Headers) => string | undefined
|
||||
onStreamPart: (chunk: string) => void
|
||||
getStreamUsage: () => any
|
||||
normalizeUsage: (body: any) => {
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
reasoningTokens?: number
|
||||
cacheReadTokens?: number
|
||||
cacheWrite5mTokens?: number
|
||||
cacheWrite1hTokens?: number
|
||||
}
|
||||
},
|
||||
) {
|
||||
class AuthError extends Error {}
|
||||
class CreditsError extends Error {}
|
||||
class MonthlyLimitError extends Error {}
|
||||
class UserLimitError extends Error {}
|
||||
class ModelError extends Error {}
|
||||
|
||||
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
|
||||
type Model = ZenData["models"][string]
|
||||
|
||||
const FREE_WORKSPACES = [
|
||||
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
|
||||
"wrk_01K6W1A3VE0KMNVSCQT43BG2SX", // opencode bench
|
||||
]
|
||||
|
||||
const logger = {
|
||||
metric: (values: Record<string, any>) => {
|
||||
console.log(`_metric:${JSON.stringify(values)}`)
|
||||
},
|
||||
log: console.log,
|
||||
debug: (message: string) => {
|
||||
if (Resource.App.stage === "production") return
|
||||
console.debug(message)
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(input.request.url)
|
||||
const body = await input.request.json()
|
||||
logger.debug(JSON.stringify(body))
|
||||
logger.metric({
|
||||
is_tream: !!body.stream,
|
||||
session: input.request.headers.get("x-opencode-session"),
|
||||
@@ -69,7 +43,7 @@ export async function handler(
|
||||
})
|
||||
const zenData = ZenData.list()
|
||||
const modelInfo = validateModel(zenData, body.model)
|
||||
const providerInfo = selectProvider(zenData, modelInfo)
|
||||
const providerInfo = selectProvider(zenData, modelInfo, input.request.headers.get("x-real-ip") ?? "")
|
||||
const authInfo = await authenticate(modelInfo, providerInfo)
|
||||
validateBilling(modelInfo, authInfo)
|
||||
validateModelSettings(authInfo)
|
||||
@@ -78,22 +52,28 @@ export async function handler(
|
||||
|
||||
// Request to model provider
|
||||
const startTimestamp = Date.now()
|
||||
const res = await fetch(path.posix.join(providerInfo.api, url.pathname.replace(/^\/zen\/v1/, "") + url.search), {
|
||||
const reqUrl = providerInfo.modifyUrl(providerInfo.api)
|
||||
const reqBody = JSON.stringify(
|
||||
providerInfo.modifyBody({
|
||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||
model: providerInfo.model,
|
||||
}),
|
||||
)
|
||||
logger.debug("REQUEST URL: " + reqUrl)
|
||||
logger.debug("REQUEST: " + reqBody)
|
||||
const res = await fetch(reqUrl, {
|
||||
method: "POST",
|
||||
headers: (() => {
|
||||
const headers = input.request.headers
|
||||
headers.delete("host")
|
||||
headers.delete("content-length")
|
||||
opts.setAuthHeader(headers, providerInfo.apiKey)
|
||||
providerInfo.modifyHeaders(headers, body, providerInfo.apiKey)
|
||||
Object.entries(providerInfo.headerMappings ?? {}).forEach(([k, v]) => {
|
||||
headers.set(k, headers.get(v)!)
|
||||
})
|
||||
return headers
|
||||
})(),
|
||||
body: JSON.stringify({
|
||||
...(opts.modifyBody?.(body) ?? body),
|
||||
model: providerInfo.model,
|
||||
}),
|
||||
body: reqBody,
|
||||
})
|
||||
|
||||
// Scrub response headers
|
||||
@@ -104,14 +84,19 @@ export async function handler(
|
||||
resHeaders.set(k, v)
|
||||
}
|
||||
}
|
||||
logger.debug("STATUS: " + res.status + " " + res.statusText)
|
||||
if (res.status === 400 || res.status === 503) {
|
||||
logger.debug("RESPONSE: " + (await res.text()))
|
||||
}
|
||||
|
||||
// Handle non-streaming response
|
||||
if (!body.stream) {
|
||||
const responseConverter = createResponseConverter(providerInfo.format, opts.format)
|
||||
const json = await res.json()
|
||||
const body = JSON.stringify(json)
|
||||
const body = JSON.stringify(responseConverter(json))
|
||||
logger.metric({ response_length: body.length })
|
||||
logger.debug(body)
|
||||
await trackUsage(authInfo, modelInfo, providerInfo.id, json.usage)
|
||||
logger.debug("RESPONSE: " + body)
|
||||
await trackUsage(authInfo, modelInfo, providerInfo, json.usage)
|
||||
await reload(authInfo)
|
||||
return new Response(body, {
|
||||
status: res.status,
|
||||
@@ -121,10 +106,13 @@ export async function handler(
|
||||
}
|
||||
|
||||
// Handle streaming response
|
||||
const streamConverter = createStreamPartConverter(providerInfo.format, opts.format)
|
||||
const usageParser = providerInfo.createUsageParser()
|
||||
const stream = new ReadableStream({
|
||||
start(c) {
|
||||
const reader = res.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
const encoder = new TextEncoder()
|
||||
let buffer = ""
|
||||
let responseLength = 0
|
||||
|
||||
@@ -136,9 +124,9 @@ export async function handler(
|
||||
response_length: responseLength,
|
||||
"timestamp.last_byte": Date.now(),
|
||||
})
|
||||
const usage = opts.getStreamUsage()
|
||||
const usage = usageParser.retrieve()
|
||||
if (usage) {
|
||||
await trackUsage(authInfo, modelInfo, providerInfo.id, usage)
|
||||
await trackUsage(authInfo, modelInfo, providerInfo, usage)
|
||||
await reload(authInfo)
|
||||
}
|
||||
c.close()
|
||||
@@ -158,12 +146,21 @@ export async function handler(
|
||||
const parts = buffer.split("\n\n")
|
||||
buffer = parts.pop() ?? ""
|
||||
|
||||
for (const part of parts) {
|
||||
logger.debug(part)
|
||||
opts.onStreamPart(part.trim())
|
||||
for (let part of parts) {
|
||||
logger.debug("PART: " + part)
|
||||
|
||||
part = part.trim()
|
||||
usageParser.parse(part)
|
||||
|
||||
if (providerInfo.format !== opts.format) {
|
||||
part = streamConverter(part)
|
||||
c.enqueue(encoder.encode(part + "\n\n"))
|
||||
}
|
||||
}
|
||||
|
||||
c.enqueue(value)
|
||||
if (providerInfo.format === opts.format) {
|
||||
c.enqueue(value)
|
||||
}
|
||||
|
||||
return pump()
|
||||
}) || Promise.resolve()
|
||||
@@ -225,17 +222,25 @@ export async function handler(
|
||||
return { id: modelId, ...modelData }
|
||||
}
|
||||
|
||||
function selectProvider(zenData: ZenData, model: Awaited<ReturnType<typeof validateModel>>) {
|
||||
function selectProvider(zenData: ZenData, model: Awaited<ReturnType<typeof validateModel>>, ip: string) {
|
||||
const providers = model.providers
|
||||
.filter((provider) => !provider.disabled)
|
||||
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
||||
const provider = providers[Math.floor(Math.random() * providers.length)]
|
||||
|
||||
// Use last character of IP address to select a provider
|
||||
const lastChar = ip.charCodeAt(ip.length - 1) || 0
|
||||
const index = lastChar % providers.length
|
||||
const provider = providers[index]
|
||||
|
||||
if (!(provider.id in zenData.providers)) {
|
||||
throw new ModelError(`Provider ${provider.id} not supported`)
|
||||
}
|
||||
|
||||
return { ...provider, ...zenData.providers[provider.id] }
|
||||
return {
|
||||
...provider,
|
||||
...zenData.providers[provider.id],
|
||||
...(provider.id === "anthropic" ? anthropicHelper : provider.id === "openai" ? openaiHelper : oaCompatHelper),
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticate(
|
||||
@@ -356,11 +361,11 @@ export async function handler(
|
||||
async function trackUsage(
|
||||
authInfo: Awaited<ReturnType<typeof authenticate>>,
|
||||
modelInfo: ReturnType<typeof validateModel>,
|
||||
providerId: string,
|
||||
providerInfo: Awaited<ReturnType<typeof selectProvider>>,
|
||||
usage: any,
|
||||
) {
|
||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
||||
opts.normalizeUsage(usage)
|
||||
providerInfo.normalizeUsage(usage)
|
||||
|
||||
const modelCost =
|
||||
modelInfo.cost200K &&
|
||||
@@ -421,7 +426,7 @@ export async function handler(
|
||||
workspaceID: authInfo.workspaceID,
|
||||
id: Identifier.create("usage"),
|
||||
model: modelInfo.id,
|
||||
provider: providerId,
|
||||
provider: providerInfo.id,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
reasoningTokens,
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
|
||||
export const logger = {
|
||||
metric: (values: Record<string, any>) => {
|
||||
console.log(`_metric:${JSON.stringify(values)}`)
|
||||
},
|
||||
log: console.log,
|
||||
debug: (message: string) => {
|
||||
if (Resource.App.stage === "production") return
|
||||
console.debug(message)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,622 @@
|
||||
import { ProviderHelper, CommonRequest, CommonResponse, CommonChunk } from "./provider"
|
||||
|
||||
type Usage = {
|
||||
cache_creation?: {
|
||||
ephemeral_5m_input_tokens?: number
|
||||
ephemeral_1h_input_tokens?: number
|
||||
}
|
||||
cache_creation_input_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
input_tokens?: number
|
||||
output_tokens?: number
|
||||
server_tool_use?: {
|
||||
web_search_requests?: number
|
||||
}
|
||||
}
|
||||
|
||||
export const anthropicHelper = {
|
||||
format: "anthropic",
|
||||
modifyUrl: (providerApi: string) => providerApi + "/messages",
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||
headers.set("x-api-key", apiKey)
|
||||
headers.set("anthropic-version", headers.get("anthropic-version") ?? "2023-06-01")
|
||||
if (body.model.startsWith("claude-sonnet-")) {
|
||||
headers.set("anthropic-beta", "context-1m-2025-08-07")
|
||||
}
|
||||
},
|
||||
modifyBody: (body: Record<string, any>) => {
|
||||
return {
|
||||
...body,
|
||||
service_tier: "standard_only",
|
||||
}
|
||||
},
|
||||
createUsageParser: () => {
|
||||
let usage: Usage
|
||||
|
||||
return {
|
||||
parse: (chunk: string) => {
|
||||
const data = chunk.split("\n")[1]
|
||||
if (!data.startsWith("data: ")) return
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(data.slice(6))
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
const usageUpdate = json.usage ?? json.message?.usage
|
||||
if (!usageUpdate) return
|
||||
usage = {
|
||||
...usage,
|
||||
...usageUpdate,
|
||||
cache_creation: {
|
||||
...usage?.cache_creation,
|
||||
...usageUpdate.cache_creation,
|
||||
},
|
||||
server_tool_use: {
|
||||
...usage?.server_tool_use,
|
||||
...usageUpdate.server_tool_use,
|
||||
},
|
||||
}
|
||||
},
|
||||
retrieve: () => usage,
|
||||
}
|
||||
},
|
||||
normalizeUsage: (usage: Usage) => ({
|
||||
inputTokens: usage.input_tokens ?? 0,
|
||||
outputTokens: usage.output_tokens ?? 0,
|
||||
reasoningTokens: undefined,
|
||||
cacheReadTokens: usage.cache_read_input_tokens ?? undefined,
|
||||
cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens ?? undefined,
|
||||
cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens ?? undefined,
|
||||
}),
|
||||
} satisfies ProviderHelper
|
||||
|
||||
export function fromAnthropicRequest(body: any): CommonRequest {
|
||||
if (!body || typeof body !== "object") return body
|
||||
|
||||
const msgs: any[] = []
|
||||
|
||||
const sys = Array.isArray(body.system) ? body.system : undefined
|
||||
if (sys && sys.length > 0) {
|
||||
for (const s of sys) {
|
||||
if (!s) continue
|
||||
if ((s as any).type !== "text") continue
|
||||
if (typeof (s as any).text !== "string") continue
|
||||
if ((s as any).text.length === 0) continue
|
||||
msgs.push({ role: "system", content: (s as any).text })
|
||||
}
|
||||
}
|
||||
|
||||
const toImg = (src: any) => {
|
||||
if (!src || typeof src !== "object") return undefined
|
||||
if ((src as any).type === "url" && typeof (src as any).url === "string")
|
||||
return { type: "image_url", image_url: { url: (src as any).url } }
|
||||
if (
|
||||
(src as any).type === "base64" &&
|
||||
typeof (src as any).media_type === "string" &&
|
||||
typeof (src as any).data === "string"
|
||||
)
|
||||
return { type: "image_url", image_url: { url: `data:${(src as any).media_type};base64,${(src as any).data}` } }
|
||||
return undefined
|
||||
}
|
||||
|
||||
const inMsgs = Array.isArray(body.messages) ? body.messages : []
|
||||
for (const m of inMsgs) {
|
||||
if (!m || !(m as any).role) continue
|
||||
|
||||
if ((m as any).role === "user") {
|
||||
const partsIn = Array.isArray((m as any).content) ? (m as any).content : []
|
||||
const partsOut: any[] = []
|
||||
for (const p of partsIn) {
|
||||
if (!p || !(p as any).type) continue
|
||||
if ((p as any).type === "text" && typeof (p as any).text === "string")
|
||||
partsOut.push({ type: "text", text: (p as any).text })
|
||||
if ((p as any).type === "image") {
|
||||
const ip = toImg((p as any).source)
|
||||
if (ip) partsOut.push(ip)
|
||||
}
|
||||
if ((p as any).type === "tool_result") {
|
||||
const id = (p as any).tool_use_id
|
||||
const content =
|
||||
typeof (p as any).content === "string" ? (p as any).content : JSON.stringify((p as any).content)
|
||||
msgs.push({ role: "tool", tool_call_id: id, content })
|
||||
}
|
||||
}
|
||||
if (partsOut.length > 0) {
|
||||
if (partsOut.length === 1 && partsOut[0].type === "text") msgs.push({ role: "user", content: partsOut[0].text })
|
||||
else msgs.push({ role: "user", content: partsOut })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "assistant") {
|
||||
const partsIn = Array.isArray((m as any).content) ? (m as any).content : []
|
||||
const texts: string[] = []
|
||||
const tcs: any[] = []
|
||||
for (const p of partsIn) {
|
||||
if (!p || !(p as any).type) continue
|
||||
if ((p as any).type === "text" && typeof (p as any).text === "string") texts.push((p as any).text)
|
||||
if ((p as any).type === "tool_use") {
|
||||
const name = (p as any).name
|
||||
const id = (p as any).id
|
||||
const inp = (p as any).input
|
||||
const input = (() => {
|
||||
if (typeof inp === "string") return inp
|
||||
try {
|
||||
return JSON.stringify(inp ?? {})
|
||||
} catch {
|
||||
return String(inp ?? "")
|
||||
}
|
||||
})()
|
||||
tcs.push({ id, type: "function", function: { name, arguments: input } })
|
||||
}
|
||||
}
|
||||
const out: any = { role: "assistant", content: texts.join("") }
|
||||
if (tcs.length > 0) out.tool_calls = tcs
|
||||
msgs.push(out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const tools = Array.isArray(body.tools)
|
||||
? body.tools
|
||||
.filter((t: any) => t && typeof t === "object" && "input_schema" in t)
|
||||
.map((t: any) => ({
|
||||
type: "function",
|
||||
function: { name: (t as any).name, description: (t as any).description, parameters: (t as any).input_schema },
|
||||
}))
|
||||
: undefined
|
||||
|
||||
const tcin = body.tool_choice
|
||||
const tc = (() => {
|
||||
if (!tcin) return undefined
|
||||
if ((tcin as any).type === "auto") return "auto"
|
||||
if ((tcin as any).type === "any") return "required"
|
||||
if ((tcin as any).type === "tool" && typeof (tcin as any).name === "string")
|
||||
return { type: "function" as const, function: { name: (tcin as any).name } }
|
||||
return undefined
|
||||
})()
|
||||
|
||||
const stop = (() => {
|
||||
const v = body.stop_sequences
|
||||
if (!v) return undefined
|
||||
if (Array.isArray(v)) return v.length === 1 ? v[0] : v
|
||||
if (typeof v === "string") return v
|
||||
return undefined
|
||||
})()
|
||||
|
||||
return {
|
||||
model: body.model,
|
||||
max_tokens: body.max_tokens,
|
||||
temperature: body.temperature,
|
||||
top_p: body.top_p,
|
||||
stop,
|
||||
messages: msgs,
|
||||
stream: !!body.stream,
|
||||
tools,
|
||||
tool_choice: tc,
|
||||
}
|
||||
}
|
||||
|
||||
export function toAnthropicRequest(body: CommonRequest) {
|
||||
if (!body || typeof body !== "object") return body
|
||||
|
||||
const sysIn = Array.isArray(body.messages) ? body.messages.filter((m: any) => m && m.role === "system") : []
|
||||
let ccCount = 0
|
||||
const cc = () => {
|
||||
ccCount++
|
||||
return ccCount <= 4 ? { cache_control: { type: "ephemeral" } } : {}
|
||||
}
|
||||
const system = sysIn
|
||||
.filter((m: any) => typeof m.content === "string" && m.content.length > 0)
|
||||
.map((m: any) => ({ type: "text", text: m.content, ...cc() }))
|
||||
|
||||
const msgsIn = Array.isArray(body.messages) ? body.messages : []
|
||||
const msgsOut: any[] = []
|
||||
|
||||
const toSrc = (p: any) => {
|
||||
if (!p || typeof p !== "object") return undefined
|
||||
if ((p as any).type === "image_url" && (p as any).image_url) {
|
||||
const u = (p as any).image_url.url ?? (p as any).image_url
|
||||
if (typeof u === "string" && u.startsWith("data:")) {
|
||||
const m = u.match(/^data:([^;]+);base64,(.*)$/)
|
||||
if (m) return { type: "base64", media_type: m[1], data: m[2] }
|
||||
}
|
||||
if (typeof u === "string") return { type: "url", url: u }
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
for (const m of msgsIn) {
|
||||
if (!m || !(m as any).role) continue
|
||||
|
||||
if ((m as any).role === "user") {
|
||||
if (typeof (m as any).content === "string") {
|
||||
msgsOut.push({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: (m as any).content, ...cc() }],
|
||||
})
|
||||
} else if (Array.isArray((m as any).content)) {
|
||||
const parts: any[] = []
|
||||
for (const p of (m as any).content) {
|
||||
if (!p || !(p as any).type) continue
|
||||
if ((p as any).type === "text" && typeof (p as any).text === "string")
|
||||
parts.push({ type: "text", text: (p as any).text, ...cc() })
|
||||
if ((p as any).type === "image_url") {
|
||||
const s = toSrc(p)
|
||||
if (s) parts.push({ type: "image", source: s, ...cc() })
|
||||
}
|
||||
}
|
||||
if (parts.length > 0) msgsOut.push({ role: "user", content: parts })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "assistant") {
|
||||
const out: any = { role: "assistant", content: [] as any[] }
|
||||
if (typeof (m as any).content === "string" && (m as any).content.length > 0) {
|
||||
;(out.content as any[]).push({ type: "text", text: (m as any).content, ...cc() })
|
||||
}
|
||||
if (Array.isArray((m as any).tool_calls)) {
|
||||
for (const tc of (m as any).tool_calls) {
|
||||
if ((tc as any).type === "function" && (tc as any).function) {
|
||||
let input: any
|
||||
const a = (tc as any).function.arguments
|
||||
if (typeof a === "string") {
|
||||
try {
|
||||
input = JSON.parse(a)
|
||||
} catch {
|
||||
input = a
|
||||
}
|
||||
} else input = a
|
||||
const id = (tc as any).id || `toolu_${Math.random().toString(36).slice(2)}`
|
||||
;(out.content as any[]).push({
|
||||
type: "tool_use",
|
||||
id,
|
||||
name: (tc as any).function.name,
|
||||
input,
|
||||
...cc(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((out.content as any[]).length > 0) msgsOut.push(out)
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "tool") {
|
||||
msgsOut.push({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: (m as any).tool_call_id,
|
||||
content: (m as any).content,
|
||||
...cc(),
|
||||
},
|
||||
],
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const tools = Array.isArray(body.tools)
|
||||
? body.tools
|
||||
.filter((t: any) => t && typeof t === "object" && (t as any).type === "function")
|
||||
.map((t: any) => ({
|
||||
name: (t as any).function.name,
|
||||
description: (t as any).function.description,
|
||||
input_schema: (t as any).function.parameters,
|
||||
...cc(),
|
||||
}))
|
||||
: undefined
|
||||
|
||||
const tcIn = body.tool_choice
|
||||
const tool_choice = (() => {
|
||||
if (!tcIn) return undefined
|
||||
if (tcIn === "auto") return { type: "auto" }
|
||||
if (tcIn === "required") return { type: "any" }
|
||||
if ((tcIn as any).type === "function" && (tcIn as any).function?.name)
|
||||
return { type: "tool", name: (tcIn as any).function.name }
|
||||
return undefined
|
||||
})()
|
||||
|
||||
const stop_sequences = (() => {
|
||||
const v = body.stop
|
||||
if (!v) return undefined
|
||||
if (Array.isArray(v)) return v
|
||||
if (typeof v === "string") return [v]
|
||||
return undefined
|
||||
})()
|
||||
|
||||
return {
|
||||
max_tokens: body.max_tokens ?? 32_000,
|
||||
temperature: body.temperature,
|
||||
top_p: body.top_p,
|
||||
system: system.length > 0 ? system : undefined,
|
||||
messages: msgsOut,
|
||||
stream: !!body.stream,
|
||||
tools,
|
||||
tool_choice,
|
||||
stop_sequences,
|
||||
}
|
||||
}
|
||||
|
||||
export function fromAnthropicResponse(resp: any): CommonResponse {
|
||||
if (!resp || typeof resp !== "object") return resp
|
||||
|
||||
if (Array.isArray((resp as any).choices)) return resp
|
||||
|
||||
const isAnthropic = typeof (resp as any).type === "string" && (resp as any).type === "message"
|
||||
if (!isAnthropic) return resp
|
||||
|
||||
const idIn = (resp as any).id
|
||||
const id =
|
||||
typeof idIn === "string" ? idIn.replace(/^msg_/, "chatcmpl_") : `chatcmpl_${Math.random().toString(36).slice(2)}`
|
||||
const model = (resp as any).model
|
||||
|
||||
const blocks: any[] = Array.isArray((resp as any).content) ? (resp as any).content : []
|
||||
const text = blocks
|
||||
.filter((b) => b && b.type === "text" && typeof (b as any).text === "string")
|
||||
.map((b: any) => b.text)
|
||||
.join("")
|
||||
const tcs = blocks
|
||||
.filter((b) => b && b.type === "tool_use")
|
||||
.map((b: any) => {
|
||||
const name = (b as any).name
|
||||
const args = (() => {
|
||||
const inp = (b as any).input
|
||||
if (typeof inp === "string") return inp
|
||||
try {
|
||||
return JSON.stringify(inp ?? {})
|
||||
} catch {
|
||||
return String(inp ?? "")
|
||||
}
|
||||
})()
|
||||
const tid =
|
||||
typeof (b as any).id === "string" && (b as any).id.length > 0
|
||||
? (b as any).id
|
||||
: `toolu_${Math.random().toString(36).slice(2)}`
|
||||
return { id: tid, type: "function" as const, function: { name, arguments: args } }
|
||||
})
|
||||
|
||||
const finish = (r: string | null) => {
|
||||
if (r === "end_turn") return "stop"
|
||||
if (r === "tool_use") return "tool_calls"
|
||||
if (r === "max_tokens") return "length"
|
||||
if (r === "content_filter") return "content_filter"
|
||||
return null
|
||||
}
|
||||
|
||||
const u = (resp as any).usage
|
||||
const usage = (() => {
|
||||
if (!u) return undefined as any
|
||||
const pt = typeof (u as any).input_tokens === "number" ? (u as any).input_tokens : undefined
|
||||
const ct = typeof (u as any).output_tokens === "number" ? (u as any).output_tokens : undefined
|
||||
const total = pt != null && ct != null ? pt + ct : undefined
|
||||
const cached =
|
||||
typeof (u as any).cache_read_input_tokens === "number" ? (u as any).cache_read_input_tokens : undefined
|
||||
const details = cached != null ? { cached_tokens: cached } : undefined
|
||||
return {
|
||||
prompt_tokens: pt,
|
||||
completion_tokens: ct,
|
||||
total_tokens: total,
|
||||
...(details ? { prompt_tokens_details: details } : {}),
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
id,
|
||||
object: "chat.completion",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant",
|
||||
...(text && text.length > 0 ? { content: text } : {}),
|
||||
...(tcs.length > 0 ? { tool_calls: tcs } : {}),
|
||||
},
|
||||
finish_reason: finish((resp as any).stop_reason ?? null),
|
||||
},
|
||||
],
|
||||
...(usage ? { usage } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function toAnthropicResponse(resp: CommonResponse) {
|
||||
if (!resp || typeof resp !== "object") return resp
|
||||
|
||||
if (!Array.isArray((resp as any).choices)) return resp
|
||||
|
||||
const choice = (resp as any).choices[0]
|
||||
if (!choice) return resp
|
||||
|
||||
const message = choice.message
|
||||
if (!message) return resp
|
||||
|
||||
const content: any[] = []
|
||||
|
||||
if (typeof message.content === "string" && message.content.length > 0)
|
||||
content.push({ type: "text", text: message.content })
|
||||
|
||||
if (Array.isArray(message.tool_calls)) {
|
||||
for (const tc of message.tool_calls) {
|
||||
if ((tc as any).type === "function" && (tc as any).function) {
|
||||
let input: any
|
||||
try {
|
||||
input = JSON.parse((tc as any).function.arguments)
|
||||
} catch {
|
||||
input = (tc as any).function.arguments
|
||||
}
|
||||
content.push({ type: "tool_use", id: (tc as any).id, name: (tc as any).function.name, input })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stop_reason = (() => {
|
||||
const r = choice.finish_reason
|
||||
if (r === "stop") return "end_turn"
|
||||
if (r === "tool_calls") return "tool_use"
|
||||
if (r === "length") return "max_tokens"
|
||||
if (r === "content_filter") return "content_filter"
|
||||
return null
|
||||
})()
|
||||
|
||||
const usage = (() => {
|
||||
const u = (resp as any).usage
|
||||
if (!u) return undefined
|
||||
return {
|
||||
input_tokens: u.prompt_tokens,
|
||||
output_tokens: u.completion_tokens,
|
||||
cache_read_input_tokens: u.prompt_tokens_details?.cached_tokens,
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
id: (resp as any).id,
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: content.length > 0 ? content : [{ type: "text", text: "" }],
|
||||
model: (resp as any).model,
|
||||
stop_reason,
|
||||
usage,
|
||||
}
|
||||
}
|
||||
|
||||
export function fromAnthropicChunk(chunk: string): CommonChunk | string {
|
||||
// Anthropic sends two lines per part: "event: <type>\n" + "data: <json>"
|
||||
const lines = chunk.split("\n")
|
||||
const dataLine = lines.find((l) => l.startsWith("data: "))
|
||||
if (!dataLine) return chunk
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(dataLine.slice(6))
|
||||
} catch {
|
||||
return chunk
|
||||
}
|
||||
|
||||
const out: CommonChunk = {
|
||||
id: json.id ?? json.message?.id ?? "",
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: json.model ?? json.message?.model ?? "",
|
||||
choices: [],
|
||||
}
|
||||
|
||||
if (json.type === "content_block_start") {
|
||||
const cb = json.content_block
|
||||
if (cb?.type === "text") {
|
||||
out.choices.push({ index: json.index ?? 0, delta: { role: "assistant", content: "" }, finish_reason: null })
|
||||
} else if (cb?.type === "tool_use") {
|
||||
out.choices.push({
|
||||
index: json.index ?? 0,
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{ index: json.index ?? 0, id: cb.id, type: "function", function: { name: cb.name, arguments: "" } },
|
||||
],
|
||||
},
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (json.type === "content_block_delta") {
|
||||
const d = json.delta
|
||||
if (d?.type === "text_delta") {
|
||||
out.choices.push({ index: json.index ?? 0, delta: { content: d.text }, finish_reason: null })
|
||||
} else if (d?.type === "input_json_delta") {
|
||||
out.choices.push({
|
||||
index: json.index ?? 0,
|
||||
delta: { tool_calls: [{ index: json.index ?? 0, function: { arguments: d.partial_json } }] },
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (json.type === "message_delta") {
|
||||
const d = json.delta
|
||||
const finish_reason = (() => {
|
||||
const r = d?.stop_reason
|
||||
if (r === "end_turn") return "stop"
|
||||
if (r === "tool_use") return "tool_calls"
|
||||
if (r === "max_tokens") return "length"
|
||||
if (r === "content_filter") return "content_filter"
|
||||
return null
|
||||
})()
|
||||
|
||||
out.choices.push({ index: 0, delta: {}, finish_reason })
|
||||
}
|
||||
|
||||
if (json.usage) {
|
||||
const u = json.usage
|
||||
out.usage = {
|
||||
prompt_tokens: u.input_tokens,
|
||||
completion_tokens: u.output_tokens,
|
||||
total_tokens: (u.input_tokens || 0) + (u.output_tokens || 0),
|
||||
...(u.cache_read_input_tokens ? { prompt_tokens_details: { cached_tokens: u.cache_read_input_tokens } } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
export function toAnthropicChunk(chunk: CommonChunk): string {
|
||||
if (!chunk.choices || !Array.isArray(chunk.choices) || chunk.choices.length === 0) {
|
||||
return JSON.stringify({})
|
||||
}
|
||||
|
||||
const choice = chunk.choices[0]
|
||||
const delta = choice.delta
|
||||
if (!delta) return JSON.stringify({})
|
||||
|
||||
const result: any = {}
|
||||
|
||||
if (delta.content) {
|
||||
result.type = "content_block_delta"
|
||||
result.index = 0
|
||||
result.delta = { type: "text_delta", text: delta.content }
|
||||
}
|
||||
|
||||
if (delta.tool_calls) {
|
||||
for (const tc of delta.tool_calls) {
|
||||
if (tc.function?.name) {
|
||||
result.type = "content_block_start"
|
||||
result.index = tc.index ?? 0
|
||||
result.content_block = { type: "tool_use", id: tc.id, name: tc.function.name, input: {} }
|
||||
} else if (tc.function?.arguments) {
|
||||
result.type = "content_block_delta"
|
||||
result.index = tc.index ?? 0
|
||||
result.delta = { type: "input_json_delta", partial_json: tc.function.arguments }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (choice.finish_reason) {
|
||||
const stop_reason = (() => {
|
||||
const r = choice.finish_reason
|
||||
if (r === "stop") return "end_turn"
|
||||
if (r === "tool_calls") return "tool_use"
|
||||
if (r === "length") return "max_tokens"
|
||||
if (r === "content_filter") return "content_filter"
|
||||
return null
|
||||
})()
|
||||
result.type = "message_delta"
|
||||
result.delta = { stop_reason, stop_sequence: null }
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
const u = chunk.usage
|
||||
result.usage = {
|
||||
input_tokens: u.prompt_tokens,
|
||||
output_tokens: u.completion_tokens,
|
||||
cache_read_input_tokens: u.prompt_tokens_details?.cached_tokens,
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(result)
|
||||
}
|
||||
@@ -0,0 +1,542 @@
|
||||
import { ProviderHelper, CommonRequest, CommonResponse, CommonChunk } from "./provider"
|
||||
|
||||
type Usage = {
|
||||
prompt_tokens?: number
|
||||
completion_tokens?: number
|
||||
total_tokens?: number
|
||||
// used by moonshot
|
||||
cached_tokens?: number
|
||||
// used by xai
|
||||
prompt_tokens_details?: {
|
||||
text_tokens?: number
|
||||
audio_tokens?: number
|
||||
image_tokens?: number
|
||||
cached_tokens?: number
|
||||
}
|
||||
completion_tokens_details?: {
|
||||
reasoning_tokens?: number
|
||||
audio_tokens?: number
|
||||
accepted_prediction_tokens?: number
|
||||
rejected_prediction_tokens?: number
|
||||
}
|
||||
}
|
||||
|
||||
export const oaCompatHelper = {
|
||||
format: "oa-compat",
|
||||
modifyUrl: (providerApi: string) => providerApi + "/chat/completions",
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||
headers.set("authorization", `Bearer ${apiKey}`)
|
||||
},
|
||||
modifyBody: (body: Record<string, any>) => {
|
||||
return {
|
||||
...body,
|
||||
...(body.stream ? { stream_options: { include_usage: true } } : {}),
|
||||
}
|
||||
},
|
||||
createUsageParser: () => {
|
||||
let usage: Usage
|
||||
|
||||
return {
|
||||
parse: (chunk: string) => {
|
||||
if (!chunk.startsWith("data: ")) return
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(chunk.slice(6)) as { usage?: Usage }
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!json.usage) return
|
||||
usage = json.usage
|
||||
},
|
||||
retrieve: () => usage,
|
||||
}
|
||||
},
|
||||
normalizeUsage: (usage: Usage) => {
|
||||
const inputTokens = usage.prompt_tokens ?? 0
|
||||
const outputTokens = usage.completion_tokens ?? 0
|
||||
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? undefined
|
||||
const cacheReadTokens = usage.cached_tokens ?? usage.prompt_tokens_details?.cached_tokens ?? undefined
|
||||
return {
|
||||
inputTokens: inputTokens - (cacheReadTokens ?? 0),
|
||||
outputTokens,
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
cacheWrite5mTokens: undefined,
|
||||
cacheWrite1hTokens: undefined,
|
||||
}
|
||||
},
|
||||
} satisfies ProviderHelper
|
||||
|
||||
export function fromOaCompatibleRequest(body: any): CommonRequest {
|
||||
if (!body || typeof body !== "object") return body
|
||||
|
||||
const msgsIn = Array.isArray(body.messages) ? body.messages : []
|
||||
const msgsOut: any[] = []
|
||||
|
||||
for (const m of msgsIn) {
|
||||
if (!m || !m.role) continue
|
||||
|
||||
if (m.role === "system") {
|
||||
if (typeof m.content === "string" && m.content.length > 0) msgsOut.push({ role: "system", content: m.content })
|
||||
continue
|
||||
}
|
||||
|
||||
if (m.role === "user") {
|
||||
if (typeof m.content === "string") {
|
||||
msgsOut.push({ role: "user", content: m.content })
|
||||
} else if (Array.isArray(m.content)) {
|
||||
const parts: any[] = []
|
||||
for (const p of m.content) {
|
||||
if (!p || !p.type) continue
|
||||
if (p.type === "text" && typeof p.text === "string") parts.push({ type: "text", text: p.text })
|
||||
if (p.type === "image_url") parts.push({ type: "image_url", image_url: p.image_url })
|
||||
}
|
||||
if (parts.length === 1 && parts[0].type === "text") msgsOut.push({ role: "user", content: parts[0].text })
|
||||
else if (parts.length > 0) msgsOut.push({ role: "user", content: parts })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (m.role === "assistant") {
|
||||
const out: any = { role: "assistant" }
|
||||
if (typeof m.content === "string") out.content = m.content
|
||||
if (Array.isArray(m.tool_calls)) out.tool_calls = m.tool_calls
|
||||
msgsOut.push(out)
|
||||
continue
|
||||
}
|
||||
|
||||
if (m.role === "tool") {
|
||||
msgsOut.push({ role: "tool", tool_call_id: m.tool_call_id, content: m.content })
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
model: body.model,
|
||||
max_tokens: body.max_tokens,
|
||||
temperature: body.temperature,
|
||||
top_p: body.top_p,
|
||||
stop: body.stop,
|
||||
messages: msgsOut,
|
||||
stream: !!body.stream,
|
||||
tools: Array.isArray(body.tools) ? body.tools : undefined,
|
||||
tool_choice: body.tool_choice,
|
||||
}
|
||||
}
|
||||
|
||||
export function toOaCompatibleRequest(body: CommonRequest) {
|
||||
if (!body || typeof body !== "object") return body
|
||||
|
||||
const msgsIn = Array.isArray(body.messages) ? body.messages : []
|
||||
const msgsOut: any[] = []
|
||||
|
||||
const toImg = (p: any) => {
|
||||
if (!p || typeof p !== "object") return undefined
|
||||
if (p.type === "image_url" && p.image_url) return { type: "image_url", image_url: p.image_url }
|
||||
const s = (p as any).source
|
||||
if (!s || typeof s !== "object") return undefined
|
||||
if (s.type === "url" && typeof s.url === "string") return { type: "image_url", image_url: { url: s.url } }
|
||||
if (s.type === "base64" && typeof s.media_type === "string" && typeof s.data === "string")
|
||||
return { type: "image_url", image_url: { url: `data:${s.media_type};base64,${s.data}` } }
|
||||
return undefined
|
||||
}
|
||||
|
||||
for (const m of msgsIn) {
|
||||
if (!m || !m.role) continue
|
||||
|
||||
if (m.role === "system") {
|
||||
if (typeof m.content === "string" && m.content.length > 0) msgsOut.push({ role: "system", content: m.content })
|
||||
continue
|
||||
}
|
||||
|
||||
if (m.role === "user") {
|
||||
if (typeof m.content === "string") {
|
||||
msgsOut.push({ role: "user", content: m.content })
|
||||
continue
|
||||
}
|
||||
if (Array.isArray(m.content)) {
|
||||
const parts: any[] = []
|
||||
for (const p of m.content) {
|
||||
if (!p || !p.type) continue
|
||||
if (p.type === "text" && typeof p.text === "string") parts.push({ type: "text", text: p.text })
|
||||
const ip = toImg(p)
|
||||
if (ip) parts.push(ip)
|
||||
}
|
||||
if (parts.length === 1 && parts[0].type === "text") msgsOut.push({ role: "user", content: parts[0].text })
|
||||
else if (parts.length > 0) msgsOut.push({ role: "user", content: parts })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (m.role === "assistant") {
|
||||
const out: any = { role: "assistant" }
|
||||
if (typeof m.content === "string") out.content = m.content
|
||||
if (Array.isArray(m.tool_calls)) out.tool_calls = m.tool_calls
|
||||
msgsOut.push(out)
|
||||
continue
|
||||
}
|
||||
|
||||
if (m.role === "tool") {
|
||||
msgsOut.push({ role: "tool", tool_call_id: m.tool_call_id, content: m.content })
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const tools = Array.isArray(body.tools)
|
||||
? body.tools.map((tool: any) => ({
|
||||
type: "function",
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
parameters: tool.parameters,
|
||||
},
|
||||
}))
|
||||
: undefined
|
||||
|
||||
return {
|
||||
model: body.model,
|
||||
max_tokens: body.max_tokens,
|
||||
temperature: body.temperature,
|
||||
top_p: body.top_p,
|
||||
stop: body.stop,
|
||||
messages: msgsOut,
|
||||
stream: !!body.stream,
|
||||
tools,
|
||||
tool_choice: body.tool_choice,
|
||||
response_format: (body as any).response_format,
|
||||
}
|
||||
}
|
||||
|
||||
export function fromOaCompatibleResponse(resp: any): CommonResponse {
|
||||
if (!resp || typeof resp !== "object") return resp
|
||||
|
||||
if (!Array.isArray((resp as any).choices)) return resp
|
||||
|
||||
const choice = (resp as any).choices[0]
|
||||
if (!choice) return resp
|
||||
|
||||
const message = choice.message
|
||||
if (!message) return resp
|
||||
|
||||
const content: any[] = []
|
||||
|
||||
if (typeof message.content === "string" && message.content.length > 0) {
|
||||
content.push({ type: "text", text: message.content })
|
||||
}
|
||||
|
||||
if (Array.isArray(message.tool_calls)) {
|
||||
for (const toolCall of message.tool_calls) {
|
||||
if (toolCall.type === "function" && toolCall.function) {
|
||||
let input
|
||||
try {
|
||||
input = JSON.parse(toolCall.function.arguments)
|
||||
} catch {
|
||||
input = toolCall.function.arguments
|
||||
}
|
||||
content.push({
|
||||
type: "tool_use",
|
||||
id: toolCall.id,
|
||||
name: toolCall.function.name,
|
||||
input,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stopReason = (() => {
|
||||
const reason = choice.finish_reason
|
||||
if (reason === "stop") return "stop"
|
||||
if (reason === "tool_calls") return "tool_calls"
|
||||
if (reason === "length") return "length"
|
||||
if (reason === "content_filter") return "content_filter"
|
||||
return null
|
||||
})()
|
||||
|
||||
const usage = (() => {
|
||||
const u = (resp as any).usage
|
||||
if (!u) return undefined
|
||||
return {
|
||||
prompt_tokens: u.prompt_tokens,
|
||||
completion_tokens: u.completion_tokens,
|
||||
total_tokens: u.total_tokens,
|
||||
...(u.prompt_tokens_details?.cached_tokens
|
||||
? { prompt_tokens_details: { cached_tokens: u.prompt_tokens_details.cached_tokens } }
|
||||
: {}),
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
id: (resp as any).id,
|
||||
object: "chat.completion" as const,
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: (resp as any).model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant" as const,
|
||||
...(content.length > 0 && content.some((c) => c.type === "text")
|
||||
? {
|
||||
content: content
|
||||
.filter((c) => c.type === "text")
|
||||
.map((c: any) => c.text)
|
||||
.join(""),
|
||||
}
|
||||
: {}),
|
||||
...(content.length > 0 && content.some((c) => c.type === "tool_use")
|
||||
? {
|
||||
tool_calls: content
|
||||
.filter((c) => c.type === "tool_use")
|
||||
.map((c: any) => ({
|
||||
id: c.id,
|
||||
type: "function" as const,
|
||||
function: {
|
||||
name: c.name,
|
||||
arguments: typeof c.input === "string" ? c.input : JSON.stringify(c.input),
|
||||
},
|
||||
})),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
finish_reason: stopReason,
|
||||
},
|
||||
],
|
||||
...(usage ? { usage } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function toOaCompatibleResponse(resp: CommonResponse) {
|
||||
if (!resp || typeof resp !== "object") return resp
|
||||
|
||||
if (Array.isArray((resp as any).choices)) return resp
|
||||
|
||||
const isAnthropic = typeof (resp as any).type === "string" && (resp as any).type === "message"
|
||||
if (!isAnthropic) return resp
|
||||
|
||||
const idIn = (resp as any).id
|
||||
const id =
|
||||
typeof idIn === "string" ? idIn.replace(/^msg_/, "chatcmpl_") : `chatcmpl_${Math.random().toString(36).slice(2)}`
|
||||
const model = (resp as any).model
|
||||
|
||||
const blocks: any[] = Array.isArray((resp as any).content) ? (resp as any).content : []
|
||||
const text = blocks
|
||||
.filter((b) => b && b.type === "text" && typeof b.text === "string")
|
||||
.map((b) => b.text)
|
||||
.join("")
|
||||
const tcs = blocks
|
||||
.filter((b) => b && b.type === "tool_use")
|
||||
.map((b) => {
|
||||
const name = (b as any).name
|
||||
const args = (() => {
|
||||
const inp = (b as any).input
|
||||
if (typeof inp === "string") return inp
|
||||
try {
|
||||
return JSON.stringify(inp ?? {})
|
||||
} catch {
|
||||
return String(inp ?? "")
|
||||
}
|
||||
})()
|
||||
const tid =
|
||||
typeof (b as any).id === "string" && (b as any).id.length > 0
|
||||
? (b as any).id
|
||||
: `toolu_${Math.random().toString(36).slice(2)}`
|
||||
return { id: tid, type: "function" as const, function: { name, arguments: args } }
|
||||
})
|
||||
|
||||
const finish = (r: string | null) => {
|
||||
if (r === "end_turn") return "stop"
|
||||
if (r === "tool_use") return "tool_calls"
|
||||
if (r === "max_tokens") return "length"
|
||||
if (r === "content_filter") return "content_filter"
|
||||
return null
|
||||
}
|
||||
|
||||
const u = (resp as any).usage
|
||||
const usage = (() => {
|
||||
if (!u) return undefined as any
|
||||
const pt = typeof u.input_tokens === "number" ? u.input_tokens : undefined
|
||||
const ct = typeof u.output_tokens === "number" ? u.output_tokens : undefined
|
||||
const total = pt != null && ct != null ? pt + ct : undefined
|
||||
const cached = typeof u.cache_read_input_tokens === "number" ? u.cache_read_input_tokens : undefined
|
||||
const details = cached != null ? { cached_tokens: cached } : undefined
|
||||
return {
|
||||
prompt_tokens: pt,
|
||||
completion_tokens: ct,
|
||||
total_tokens: total,
|
||||
...(details ? { prompt_tokens_details: details } : {}),
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
id,
|
||||
object: "chat.completion",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant",
|
||||
...(text && text.length > 0 ? { content: text } : {}),
|
||||
...(tcs.length > 0 ? { tool_calls: tcs } : {}),
|
||||
},
|
||||
finish_reason: finish((resp as any).stop_reason ?? null),
|
||||
},
|
||||
],
|
||||
...(usage ? { usage } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function fromOaCompatibleChunk(chunk: string): CommonChunk | string {
|
||||
if (!chunk.startsWith("data: ")) return chunk
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(chunk.slice(6))
|
||||
} catch {
|
||||
return chunk
|
||||
}
|
||||
|
||||
if (!json.choices || !Array.isArray(json.choices) || json.choices.length === 0) {
|
||||
return chunk
|
||||
}
|
||||
|
||||
const choice = json.choices[0]
|
||||
const delta = choice.delta
|
||||
|
||||
if (!delta) return chunk
|
||||
|
||||
const result: CommonChunk = {
|
||||
id: json.id ?? "",
|
||||
object: "chat.completion.chunk",
|
||||
created: json.created ?? Math.floor(Date.now() / 1000),
|
||||
model: json.model ?? "",
|
||||
choices: [],
|
||||
}
|
||||
|
||||
if (delta.content) {
|
||||
result.choices.push({
|
||||
index: choice.index ?? 0,
|
||||
delta: { content: delta.content },
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
|
||||
if (delta.tool_calls) {
|
||||
for (const toolCall of delta.tool_calls) {
|
||||
result.choices.push({
|
||||
index: choice.index ?? 0,
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: toolCall.index ?? 0,
|
||||
id: toolCall.id,
|
||||
type: toolCall.type ?? "function",
|
||||
function: toolCall.function,
|
||||
},
|
||||
],
|
||||
},
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (choice.finish_reason) {
|
||||
result.choices.push({
|
||||
index: choice.index ?? 0,
|
||||
delta: {},
|
||||
finish_reason: choice.finish_reason,
|
||||
})
|
||||
}
|
||||
|
||||
if (json.usage) {
|
||||
const usage = json.usage
|
||||
result.usage = {
|
||||
prompt_tokens: usage.prompt_tokens,
|
||||
completion_tokens: usage.completion_tokens,
|
||||
total_tokens: usage.total_tokens,
|
||||
...(usage.prompt_tokens_details?.cached_tokens
|
||||
? { prompt_tokens_details: { cached_tokens: usage.prompt_tokens_details.cached_tokens } }
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function toOaCompatibleChunk(chunk: CommonChunk): string {
|
||||
const result: any = {
|
||||
id: chunk.id,
|
||||
object: "chat.completion.chunk",
|
||||
created: chunk.created,
|
||||
model: chunk.model,
|
||||
choices: [],
|
||||
}
|
||||
|
||||
if (!chunk.choices || chunk.choices.length === 0) {
|
||||
return `data: ${JSON.stringify(result)}`
|
||||
}
|
||||
|
||||
const choice = chunk.choices[0]
|
||||
const delta = choice.delta
|
||||
|
||||
if (delta?.role) {
|
||||
result.choices.push({
|
||||
index: choice.index,
|
||||
delta: { role: delta.role },
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
|
||||
if (delta?.content) {
|
||||
result.choices.push({
|
||||
index: choice.index,
|
||||
delta: { content: delta.content },
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
for (const tc of delta.tool_calls) {
|
||||
result.choices.push({
|
||||
index: choice.index,
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: tc.index,
|
||||
id: tc.id,
|
||||
type: tc.type,
|
||||
function: tc.function,
|
||||
},
|
||||
],
|
||||
},
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (choice.finish_reason) {
|
||||
result.choices.push({
|
||||
index: choice.index,
|
||||
delta: {},
|
||||
finish_reason: choice.finish_reason,
|
||||
})
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
result.usage = {
|
||||
prompt_tokens: chunk.usage.prompt_tokens,
|
||||
completion_tokens: chunk.usage.completion_tokens,
|
||||
total_tokens: chunk.usage.total_tokens,
|
||||
...(chunk.usage.prompt_tokens_details?.cached_tokens
|
||||
? {
|
||||
prompt_tokens_details: { cached_tokens: chunk.usage.prompt_tokens_details.cached_tokens },
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
return `data: ${JSON.stringify(result)}`
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
import { ProviderHelper, CommonRequest, CommonResponse, CommonChunk } from "./provider"
|
||||
|
||||
type Usage = {
|
||||
input_tokens?: number
|
||||
input_tokens_details?: {
|
||||
cached_tokens?: number
|
||||
}
|
||||
output_tokens?: number
|
||||
output_tokens_details?: {
|
||||
reasoning_tokens?: number
|
||||
}
|
||||
total_tokens?: number
|
||||
}
|
||||
|
||||
export const openaiHelper = {
|
||||
format: "openai",
|
||||
modifyUrl: (providerApi: string) => providerApi + "/responses",
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||
headers.set("authorization", `Bearer ${apiKey}`)
|
||||
},
|
||||
modifyBody: (body: Record<string, any>) => {
|
||||
return body
|
||||
},
|
||||
createUsageParser: () => {
|
||||
let usage: Usage
|
||||
|
||||
return {
|
||||
parse: (chunk: string) => {
|
||||
const [event, data] = chunk.split("\n")
|
||||
if (event !== "event: response.completed") return
|
||||
if (!data.startsWith("data: ")) return
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(data.slice(6)) as { response?: { usage?: Usage } }
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!json.response?.usage) return
|
||||
usage = json.response.usage
|
||||
},
|
||||
retrieve: () => usage,
|
||||
}
|
||||
},
|
||||
normalizeUsage: (usage: Usage) => {
|
||||
const inputTokens = usage.input_tokens ?? 0
|
||||
const outputTokens = usage.output_tokens ?? 0
|
||||
const reasoningTokens = usage.output_tokens_details?.reasoning_tokens ?? undefined
|
||||
const cacheReadTokens = usage.input_tokens_details?.cached_tokens ?? undefined
|
||||
return {
|
||||
inputTokens: inputTokens - (cacheReadTokens ?? 0),
|
||||
outputTokens: outputTokens - (reasoningTokens ?? 0),
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
cacheWrite5mTokens: undefined,
|
||||
cacheWrite1hTokens: undefined,
|
||||
}
|
||||
},
|
||||
} satisfies ProviderHelper
|
||||
|
||||
export function fromOpenaiRequest(body: any): CommonRequest {
|
||||
if (!body || typeof body !== "object") return body
|
||||
|
||||
const toImg = (p: any) => {
|
||||
if (!p || typeof p !== "object") return undefined
|
||||
if ((p as any).type === "image_url" && (p as any).image_url)
|
||||
return { type: "image_url", image_url: (p as any).image_url }
|
||||
if ((p as any).type === "input_image" && (p as any).image_url)
|
||||
return { type: "image_url", image_url: (p as any).image_url }
|
||||
const s = (p as any).source
|
||||
if (!s || typeof s !== "object") return undefined
|
||||
if ((s as any).type === "url" && typeof (s as any).url === "string")
|
||||
return { type: "image_url", image_url: { url: (s as any).url } }
|
||||
if (
|
||||
(s as any).type === "base64" &&
|
||||
typeof (s as any).media_type === "string" &&
|
||||
typeof (s as any).data === "string"
|
||||
)
|
||||
return { type: "image_url", image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` } }
|
||||
return undefined
|
||||
}
|
||||
|
||||
const msgs: any[] = []
|
||||
|
||||
const inMsgs = Array.isArray(body.input) ? body.input : Array.isArray(body.messages) ? body.messages : []
|
||||
|
||||
for (const m of inMsgs) {
|
||||
if (!m) continue
|
||||
|
||||
// Responses API items without role:
|
||||
if (!(m as any).role && (m as any).type) {
|
||||
if ((m as any).type === "function_call") {
|
||||
const name = (m as any).name
|
||||
const a = (m as any).arguments
|
||||
const args = typeof a === "string" ? a : JSON.stringify(a ?? {})
|
||||
msgs.push({
|
||||
role: "assistant",
|
||||
tool_calls: [{ id: (m as any).id, type: "function", function: { name, arguments: args } }],
|
||||
})
|
||||
}
|
||||
if ((m as any).type === "function_call_output") {
|
||||
const id = (m as any).call_id
|
||||
const out = (m as any).output
|
||||
const content = typeof out === "string" ? out : JSON.stringify(out)
|
||||
msgs.push({ role: "tool", tool_call_id: id, content })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "system" || (m as any).role === "developer") {
|
||||
const c = (m as any).content
|
||||
if (typeof c === "string" && c.length > 0) msgs.push({ role: "system", content: c })
|
||||
if (Array.isArray(c)) {
|
||||
const t = c.find((p: any) => p && typeof p.text === "string")
|
||||
if (t && typeof t.text === "string" && t.text.length > 0) msgs.push({ role: "system", content: t.text })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "user") {
|
||||
const c = (m as any).content
|
||||
if (typeof c === "string") {
|
||||
msgs.push({ role: "user", content: c })
|
||||
} else if (Array.isArray(c)) {
|
||||
const parts: any[] = []
|
||||
for (const p of c) {
|
||||
if (!p || !(p as any).type) continue
|
||||
if (((p as any).type === "text" || (p as any).type === "input_text") && typeof (p as any).text === "string")
|
||||
parts.push({ type: "text", text: (p as any).text })
|
||||
const ip = toImg(p)
|
||||
if (ip) parts.push(ip)
|
||||
if ((p as any).type === "tool_result") {
|
||||
const id = (p as any).tool_call_id
|
||||
const content =
|
||||
typeof (p as any).content === "string" ? (p as any).content : JSON.stringify((p as any).content)
|
||||
msgs.push({ role: "tool", tool_call_id: id, content })
|
||||
}
|
||||
}
|
||||
if (parts.length === 1 && parts[0].type === "text") msgs.push({ role: "user", content: parts[0].text })
|
||||
else if (parts.length > 0) msgs.push({ role: "user", content: parts })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "assistant") {
|
||||
const c = (m as any).content
|
||||
const out: any = { role: "assistant" }
|
||||
if (typeof c === "string" && c.length > 0) out.content = c
|
||||
if (Array.isArray((m as any).tool_calls)) out.tool_calls = (m as any).tool_calls
|
||||
msgs.push(out)
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "tool") {
|
||||
msgs.push({ role: "tool", tool_call_id: (m as any).tool_call_id, content: (m as any).content })
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const tcIn = body.tool_choice
|
||||
const tc = (() => {
|
||||
if (!tcIn) return undefined
|
||||
if (tcIn === "auto") return "auto"
|
||||
if (tcIn === "required") return "required"
|
||||
if ((tcIn as any).type === "function" && (tcIn as any).function?.name)
|
||||
return { type: "function" as const, function: { name: (tcIn as any).function.name } }
|
||||
return undefined
|
||||
})()
|
||||
|
||||
const stop = (() => {
|
||||
const v = body.stop_sequences ?? body.stop
|
||||
if (!v) return undefined
|
||||
if (Array.isArray(v)) return v.length === 1 ? v[0] : v
|
||||
if (typeof v === "string") return v
|
||||
return undefined
|
||||
})()
|
||||
|
||||
return {
|
||||
model: body.model,
|
||||
max_tokens: body.max_output_tokens ?? body.max_tokens,
|
||||
temperature: body.temperature,
|
||||
top_p: body.top_p,
|
||||
stop,
|
||||
messages: msgs,
|
||||
stream: !!body.stream,
|
||||
tools: Array.isArray(body.tools) ? body.tools : undefined,
|
||||
tool_choice: tc,
|
||||
}
|
||||
}
|
||||
|
||||
export function toOpenaiRequest(body: CommonRequest) {
|
||||
if (!body || typeof body !== "object") return body
|
||||
|
||||
const msgsIn = Array.isArray(body.messages) ? body.messages : []
|
||||
const input: any[] = []
|
||||
|
||||
const toPart = (p: any) => {
|
||||
if (!p || typeof p !== "object") return undefined
|
||||
if ((p as any).type === "text" && typeof (p as any).text === "string")
|
||||
return { type: "input_text", text: (p as any).text }
|
||||
if ((p as any).type === "image_url" && (p as any).image_url)
|
||||
return { type: "input_image", image_url: (p as any).image_url }
|
||||
const s = (p as any).source
|
||||
if (!s || typeof s !== "object") return undefined
|
||||
if ((s as any).type === "url" && typeof (s as any).url === "string")
|
||||
return { type: "input_image", image_url: { url: (s as any).url } }
|
||||
if (
|
||||
(s as any).type === "base64" &&
|
||||
typeof (s as any).media_type === "string" &&
|
||||
typeof (s as any).data === "string"
|
||||
)
|
||||
return { type: "input_image", image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` } }
|
||||
return undefined
|
||||
}
|
||||
|
||||
for (const m of msgsIn) {
|
||||
if (!m || !(m as any).role) continue
|
||||
|
||||
if ((m as any).role === "system") {
|
||||
const c = (m as any).content
|
||||
if (typeof c === "string") input.push({ role: "system", content: c })
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "user") {
|
||||
const c = (m as any).content
|
||||
if (typeof c === "string") {
|
||||
input.push({ role: "user", content: [{ type: "input_text", text: c }] })
|
||||
} else if (Array.isArray(c)) {
|
||||
const parts: any[] = []
|
||||
for (const p of c) {
|
||||
const op = toPart(p)
|
||||
if (op) parts.push(op)
|
||||
}
|
||||
if (parts.length > 0) input.push({ role: "user", content: parts })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "assistant") {
|
||||
const c = (m as any).content
|
||||
if (typeof c === "string" && c.length > 0) {
|
||||
input.push({ role: "assistant", content: [{ type: "output_text", text: c }] })
|
||||
}
|
||||
if (Array.isArray((m as any).tool_calls)) {
|
||||
for (const tc of (m as any).tool_calls) {
|
||||
if ((tc as any).type === "function" && (tc as any).function) {
|
||||
const name = (tc as any).function.name
|
||||
const a = (tc as any).function.arguments
|
||||
const args = typeof a === "string" ? a : JSON.stringify(a)
|
||||
input.push({ type: "function_call", call_id: (tc as any).id, name, arguments: args })
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ((m as any).role === "tool") {
|
||||
const out = typeof (m as any).content === "string" ? (m as any).content : JSON.stringify((m as any).content)
|
||||
input.push({ type: "function_call_output", call_id: (m as any).tool_call_id, output: out })
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const stop_sequences = (() => {
|
||||
const v = body.stop
|
||||
if (!v) return undefined
|
||||
if (Array.isArray(v)) return v
|
||||
if (typeof v === "string") return [v]
|
||||
return undefined
|
||||
})()
|
||||
|
||||
const tcIn = body.tool_choice
|
||||
const tool_choice = (() => {
|
||||
if (!tcIn) return undefined
|
||||
if (tcIn === "auto") return "auto"
|
||||
if (tcIn === "required") return "required"
|
||||
if ((tcIn as any).type === "function" && (tcIn as any).function?.name)
|
||||
return { type: "function", function: { name: (tcIn as any).function.name } }
|
||||
return undefined
|
||||
})()
|
||||
|
||||
const tools = (() => {
|
||||
if (!Array.isArray(body.tools)) return undefined
|
||||
return body.tools.map((tool: any) => {
|
||||
if (tool.type === "function") {
|
||||
return {
|
||||
type: "function",
|
||||
name: tool.function?.name,
|
||||
description: tool.function?.description,
|
||||
parameters: tool.function?.parameters,
|
||||
strict: tool.function?.strict,
|
||||
}
|
||||
}
|
||||
return tool
|
||||
})
|
||||
})()
|
||||
|
||||
return {
|
||||
model: body.model,
|
||||
input,
|
||||
max_output_tokens: body.max_tokens,
|
||||
top_p: body.top_p,
|
||||
stop_sequences,
|
||||
stream: !!body.stream,
|
||||
tools,
|
||||
tool_choice,
|
||||
include: Array.isArray((body as any).include) ? (body as any).include : undefined,
|
||||
truncation: (body as any).truncation,
|
||||
metadata: (body as any).metadata,
|
||||
store: (body as any).store,
|
||||
user: (body as any).user,
|
||||
text: { verbosity: body.model === "gpt-5-codex" ? "medium" : "low" },
|
||||
reasoning: { effort: "medium" },
|
||||
}
|
||||
}
|
||||
|
||||
export function fromOpenaiResponse(resp: any): CommonResponse {
|
||||
if (!resp || typeof resp !== "object") return resp
|
||||
if (Array.isArray((resp as any).choices)) return resp
|
||||
|
||||
const r = (resp as any).response ?? resp
|
||||
if (!r || typeof r !== "object") return resp
|
||||
|
||||
const idIn = (r as any).id
|
||||
const id =
|
||||
typeof idIn === "string" ? idIn.replace(/^resp_/, "chatcmpl_") : `chatcmpl_${Math.random().toString(36).slice(2)}`
|
||||
const model = (r as any).model ?? (resp as any).model
|
||||
|
||||
const out = Array.isArray((r as any).output) ? (r as any).output : []
|
||||
const text = out
|
||||
.filter((o: any) => o && o.type === "message" && Array.isArray((o as any).content))
|
||||
.flatMap((o: any) => (o as any).content)
|
||||
.filter((p: any) => p && p.type === "output_text" && typeof p.text === "string")
|
||||
.map((p: any) => p.text)
|
||||
.join("")
|
||||
|
||||
const tcs = out
|
||||
.filter((o: any) => o && o.type === "function_call")
|
||||
.map((o: any) => {
|
||||
const name = (o as any).name
|
||||
const a = (o as any).arguments
|
||||
const args = typeof a === "string" ? a : JSON.stringify(a ?? {})
|
||||
const tid =
|
||||
typeof (o as any).id === "string" && (o as any).id.length > 0
|
||||
? (o as any).id
|
||||
: `toolu_${Math.random().toString(36).slice(2)}`
|
||||
return { id: tid, type: "function" as const, function: { name, arguments: args } }
|
||||
})
|
||||
|
||||
const finish = (r: string | null) => {
|
||||
if (r === "stop") return "stop"
|
||||
if (r === "tool_call" || r === "tool_calls") return "tool_calls"
|
||||
if (r === "length" || r === "max_output_tokens") return "length"
|
||||
if (r === "content_filter") return "content_filter"
|
||||
return null
|
||||
}
|
||||
|
||||
const u = (r as any).usage ?? (resp as any).usage
|
||||
const usage = (() => {
|
||||
if (!u) return undefined as any
|
||||
const pt = typeof (u as any).input_tokens === "number" ? (u as any).input_tokens : undefined
|
||||
const ct = typeof (u as any).output_tokens === "number" ? (u as any).output_tokens : undefined
|
||||
const total = pt != null && ct != null ? pt + ct : undefined
|
||||
const cached = (u as any).input_tokens_details?.cached_tokens
|
||||
const details = typeof cached === "number" ? { cached_tokens: cached } : undefined
|
||||
return {
|
||||
prompt_tokens: pt,
|
||||
completion_tokens: ct,
|
||||
total_tokens: total,
|
||||
...(details ? { prompt_tokens_details: details } : {}),
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
id,
|
||||
object: "chat.completion",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant",
|
||||
...(text && text.length > 0 ? { content: text } : {}),
|
||||
...(tcs.length > 0 ? { tool_calls: tcs } : {}),
|
||||
},
|
||||
finish_reason: finish((r as any).stop_reason ?? null),
|
||||
},
|
||||
],
|
||||
...(usage ? { usage } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
export function toOpenaiResponse(resp: CommonResponse) {
|
||||
if (!resp || typeof resp !== "object") return resp
|
||||
if (!Array.isArray((resp as any).choices)) return resp
|
||||
|
||||
const choice = (resp as any).choices[0]
|
||||
if (!choice) return resp
|
||||
|
||||
const msg = choice.message
|
||||
if (!msg) return resp
|
||||
|
||||
const outputItems: any[] = []
|
||||
|
||||
if (typeof msg.content === "string" && msg.content.length > 0) {
|
||||
outputItems.push({
|
||||
id: `msg_${Math.random().toString(36).slice(2)}`,
|
||||
type: "message",
|
||||
status: "completed",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: msg.content, annotations: [], logprobs: [] }],
|
||||
})
|
||||
}
|
||||
|
||||
if (Array.isArray(msg.tool_calls)) {
|
||||
for (const tc of msg.tool_calls) {
|
||||
if ((tc as any).type === "function" && (tc as any).function) {
|
||||
outputItems.push({
|
||||
id: (tc as any).id,
|
||||
type: "function_call",
|
||||
name: (tc as any).function.name,
|
||||
call_id: (tc as any).id,
|
||||
arguments: (tc as any).function.arguments,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stop_reason = (() => {
|
||||
const r = choice.finish_reason
|
||||
if (r === "stop") return "stop"
|
||||
if (r === "tool_calls") return "tool_call"
|
||||
if (r === "length") return "max_output_tokens"
|
||||
if (r === "content_filter") return "content_filter"
|
||||
return null
|
||||
})()
|
||||
|
||||
const usage = (() => {
|
||||
const u = (resp as any).usage
|
||||
if (!u) return undefined
|
||||
return {
|
||||
input_tokens: u.prompt_tokens,
|
||||
output_tokens: u.completion_tokens,
|
||||
total_tokens: u.total_tokens,
|
||||
...(u.prompt_tokens_details?.cached_tokens
|
||||
? { input_tokens_details: { cached_tokens: u.prompt_tokens_details.cached_tokens } }
|
||||
: {}),
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
id: (resp as any).id?.replace(/^chatcmpl_/, "resp_") ?? `resp_${Math.random().toString(36).slice(2)}`,
|
||||
object: "response",
|
||||
model: (resp as any).model,
|
||||
output: outputItems,
|
||||
stop_reason,
|
||||
usage,
|
||||
}
|
||||
}
|
||||
|
||||
export function fromOpenaiChunk(chunk: string): CommonChunk | string {
|
||||
const lines = chunk.split("\n")
|
||||
const ev = lines[0]
|
||||
const dl = lines[1]
|
||||
if (!ev || !dl || !dl.startsWith("data: ")) return chunk
|
||||
|
||||
let json: any
|
||||
try {
|
||||
json = JSON.parse(dl.slice(6))
|
||||
} catch {
|
||||
return chunk
|
||||
}
|
||||
|
||||
const respObj = json.response ?? {}
|
||||
|
||||
const out: CommonChunk = {
|
||||
id: respObj.id ?? json.id ?? "",
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: respObj.model ?? json.model ?? "",
|
||||
choices: [],
|
||||
}
|
||||
|
||||
const e = ev.replace("event: ", "").trim()
|
||||
|
||||
if (e === "response.output_text.delta") {
|
||||
const d = (json as any).delta ?? (json as any).text ?? (json as any).output_text_delta
|
||||
if (typeof d === "string" && d.length > 0)
|
||||
out.choices.push({ index: 0, delta: { content: d }, finish_reason: null })
|
||||
}
|
||||
|
||||
if (e === "response.output_item.added" && (json as any).item?.type === "function_call") {
|
||||
const name = (json as any).item?.name
|
||||
const id = (json as any).item?.id
|
||||
if (typeof name === "string" && name.length > 0) {
|
||||
out.choices.push({
|
||||
index: 0,
|
||||
delta: { tool_calls: [{ index: 0, id, type: "function", function: { name, arguments: "" } }] },
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (e === "response.function_call_arguments.delta") {
|
||||
const a = (json as any).delta ?? (json as any).arguments_delta
|
||||
if (typeof a === "string" && a.length > 0) {
|
||||
out.choices.push({
|
||||
index: 0,
|
||||
delta: { tool_calls: [{ index: 0, function: { arguments: a } }] },
|
||||
finish_reason: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (e === "response.completed") {
|
||||
const fr = (() => {
|
||||
const sr = (respObj as any).stop_reason ?? (json as any).stop_reason
|
||||
if (sr === "stop") return "stop"
|
||||
if (sr === "tool_call" || sr === "tool_calls") return "tool_calls"
|
||||
if (sr === "length" || sr === "max_output_tokens") return "length"
|
||||
if (sr === "content_filter") return "content_filter"
|
||||
return null
|
||||
})()
|
||||
out.choices.push({ index: 0, delta: {}, finish_reason: fr })
|
||||
|
||||
const u = (respObj as any).usage ?? (json as any).response?.usage
|
||||
if (u) {
|
||||
out.usage = {
|
||||
prompt_tokens: u.input_tokens,
|
||||
completion_tokens: u.output_tokens,
|
||||
total_tokens: (u.input_tokens || 0) + (u.output_tokens || 0),
|
||||
...(u.input_tokens_details?.cached_tokens
|
||||
? { prompt_tokens_details: { cached_tokens: u.input_tokens_details.cached_tokens } }
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
export function toOpenaiChunk(chunk: CommonChunk): string {
|
||||
if (!chunk.choices || !Array.isArray(chunk.choices) || chunk.choices.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const choice = chunk.choices[0]
|
||||
const d = choice.delta
|
||||
if (!d) return ""
|
||||
|
||||
const id = chunk.id
|
||||
const model = chunk.model
|
||||
|
||||
if (d.content) {
|
||||
const data = { id, type: "response.output_text.delta", delta: d.content, response: { id, model } }
|
||||
return `event: response.output_text.delta\ndata: ${JSON.stringify(data)}`
|
||||
}
|
||||
|
||||
if (d.tool_calls) {
|
||||
for (const tc of d.tool_calls) {
|
||||
if (tc.function?.name) {
|
||||
const data = {
|
||||
type: "response.output_item.added",
|
||||
output_index: 0,
|
||||
item: { id: tc.id, type: "function_call", name: tc.function.name, call_id: tc.id, arguments: "" },
|
||||
}
|
||||
return `event: response.output_item.added\ndata: ${JSON.stringify(data)}`
|
||||
}
|
||||
if (tc.function?.arguments) {
|
||||
const data = {
|
||||
type: "response.function_call_arguments.delta",
|
||||
output_index: 0,
|
||||
delta: tc.function.arguments,
|
||||
}
|
||||
return `event: response.function_call_arguments.delta\ndata: ${JSON.stringify(data)}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (choice.finish_reason) {
|
||||
const u = chunk.usage
|
||||
const usage = u
|
||||
? {
|
||||
input_tokens: u.prompt_tokens,
|
||||
output_tokens: u.completion_tokens,
|
||||
total_tokens: u.total_tokens,
|
||||
...(u.prompt_tokens_details?.cached_tokens
|
||||
? { input_tokens_details: { cached_tokens: u.prompt_tokens_details.cached_tokens } }
|
||||
: {}),
|
||||
}
|
||||
: undefined
|
||||
|
||||
const data: any = { id, type: "response.completed", response: { id, model, ...(usage ? { usage } : {}) } }
|
||||
return `event: response.completed\ndata: ${JSON.stringify(data)}`
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
import { Format } from "../format"
|
||||
|
||||
import {
|
||||
fromAnthropicChunk,
|
||||
fromAnthropicRequest,
|
||||
fromAnthropicResponse,
|
||||
toAnthropicChunk,
|
||||
toAnthropicRequest,
|
||||
toAnthropicResponse,
|
||||
} from "./anthropic"
|
||||
import {
|
||||
fromOpenaiChunk,
|
||||
fromOpenaiRequest,
|
||||
fromOpenaiResponse,
|
||||
toOpenaiChunk,
|
||||
toOpenaiRequest,
|
||||
toOpenaiResponse,
|
||||
} from "./openai"
|
||||
import {
|
||||
fromOaCompatibleChunk,
|
||||
fromOaCompatibleRequest,
|
||||
fromOaCompatibleResponse,
|
||||
toOaCompatibleChunk,
|
||||
toOaCompatibleRequest,
|
||||
toOaCompatibleResponse,
|
||||
} from "./openai-compatible"
|
||||
|
||||
export type ProviderHelper = {
|
||||
format: Format
|
||||
modifyUrl: (providerApi: string) => string
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => void
|
||||
modifyBody: (body: Record<string, any>) => Record<string, any>
|
||||
createUsageParser: () => {
|
||||
parse: (chunk: string) => void
|
||||
retrieve: () => any
|
||||
}
|
||||
normalizeUsage: (usage: any) => {
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
reasoningTokens?: number
|
||||
cacheReadTokens?: number
|
||||
cacheWrite5mTokens?: number
|
||||
cacheWrite1hTokens?: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommonMessage {
|
||||
role: "system" | "user" | "assistant" | "tool"
|
||||
content?: string | Array<CommonContentPart>
|
||||
tool_call_id?: string
|
||||
tool_calls?: CommonToolCall[]
|
||||
}
|
||||
|
||||
export interface CommonContentPart {
|
||||
type: "text" | "image_url"
|
||||
text?: string
|
||||
image_url?: { url: string }
|
||||
}
|
||||
|
||||
export interface CommonToolCall {
|
||||
id: string
|
||||
type: "function"
|
||||
function: {
|
||||
name: string
|
||||
arguments: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommonTool {
|
||||
type: "function"
|
||||
function: {
|
||||
name: string
|
||||
description?: string
|
||||
parameters?: Record<string, any>
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommonUsage {
|
||||
input_tokens?: number
|
||||
output_tokens?: number
|
||||
total_tokens?: number
|
||||
prompt_tokens?: number
|
||||
completion_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
cache_creation?: {
|
||||
ephemeral_5m_input_tokens?: number
|
||||
ephemeral_1h_input_tokens?: number
|
||||
}
|
||||
input_tokens_details?: {
|
||||
cached_tokens?: number
|
||||
}
|
||||
output_tokens_details?: {
|
||||
reasoning_tokens?: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommonRequest {
|
||||
model: string
|
||||
max_tokens?: number
|
||||
temperature?: number
|
||||
top_p?: number
|
||||
stop?: string | string[]
|
||||
messages: CommonMessage[]
|
||||
stream?: boolean
|
||||
tools?: CommonTool[]
|
||||
tool_choice?: "auto" | "required" | { type: "function"; function: { name: string } }
|
||||
}
|
||||
|
||||
export interface CommonResponse {
|
||||
id: string
|
||||
object: "chat.completion"
|
||||
created: number
|
||||
model: string
|
||||
choices: Array<{
|
||||
index: number
|
||||
message: {
|
||||
role: "assistant"
|
||||
content?: string
|
||||
tool_calls?: CommonToolCall[]
|
||||
}
|
||||
finish_reason: "stop" | "tool_calls" | "length" | "content_filter" | null
|
||||
}>
|
||||
usage?: {
|
||||
prompt_tokens?: number
|
||||
completion_tokens?: number
|
||||
total_tokens?: number
|
||||
prompt_tokens_details?: { cached_tokens?: number }
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommonChunk {
|
||||
id: string
|
||||
object: "chat.completion.chunk"
|
||||
created: number
|
||||
model: string
|
||||
choices: Array<{
|
||||
index: number
|
||||
delta: {
|
||||
role?: "assistant"
|
||||
content?: string
|
||||
tool_calls?: Array<{
|
||||
index: number
|
||||
id?: string
|
||||
type?: "function"
|
||||
function?: {
|
||||
name?: string
|
||||
arguments?: string
|
||||
}
|
||||
}>
|
||||
}
|
||||
finish_reason: "stop" | "tool_calls" | "length" | "content_filter" | null
|
||||
}>
|
||||
usage?: {
|
||||
prompt_tokens?: number
|
||||
completion_tokens?: number
|
||||
total_tokens?: number
|
||||
prompt_tokens_details?: { cached_tokens?: number }
|
||||
}
|
||||
}
|
||||
|
||||
export function createBodyConverter(from: Format, to: Format) {
|
||||
return (body: any): any => {
|
||||
if (from === to) return body
|
||||
|
||||
let raw: CommonRequest
|
||||
if (from === "anthropic") raw = fromAnthropicRequest(body)
|
||||
else if (from === "openai") raw = fromOpenaiRequest(body)
|
||||
else raw = fromOaCompatibleRequest(body)
|
||||
|
||||
if (to === "anthropic") return toAnthropicRequest(raw)
|
||||
if (to === "openai") return toOpenaiRequest(raw)
|
||||
if (to === "oa-compat") return toOaCompatibleRequest(raw)
|
||||
}
|
||||
}
|
||||
|
||||
export function createStreamPartConverter(from: Format, to: Format) {
|
||||
return (part: any): any => {
|
||||
if (from === to) return part
|
||||
|
||||
let raw: CommonChunk | string
|
||||
if (from === "anthropic") raw = fromAnthropicChunk(part)
|
||||
else if (from === "openai") raw = fromOpenaiChunk(part)
|
||||
else raw = fromOaCompatibleChunk(part)
|
||||
|
||||
// If result is a string (error case), pass it through
|
||||
if (typeof raw === "string") return raw
|
||||
|
||||
if (to === "anthropic") return toAnthropicChunk(raw)
|
||||
if (to === "openai") return toOpenaiChunk(raw)
|
||||
if (to === "oa-compat") return toOaCompatibleChunk(raw)
|
||||
}
|
||||
}
|
||||
|
||||
export function createResponseConverter(from: Format, to: Format) {
|
||||
return (response: any): any => {
|
||||
if (from === to) return response
|
||||
|
||||
let raw: CommonResponse
|
||||
if (from === "anthropic") raw = fromAnthropicResponse(response)
|
||||
else if (from === "openai") raw = fromOpenaiResponse(response)
|
||||
else raw = fromOaCompatibleResponse(response)
|
||||
|
||||
if (to === "anthropic") return toAnthropicResponse(raw)
|
||||
if (to === "openai") return toOpenaiResponse(raw)
|
||||
if (to === "oa-compat") return toOaCompatibleResponse(raw)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,9 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { handler } from "~/routes/zen/handler"
|
||||
|
||||
type Usage = {
|
||||
prompt_tokens?: number
|
||||
completion_tokens?: number
|
||||
total_tokens?: number
|
||||
// used by moonshot
|
||||
cached_tokens?: number
|
||||
// used by xai
|
||||
prompt_tokens_details?: {
|
||||
text_tokens?: number
|
||||
audio_tokens?: number
|
||||
image_tokens?: number
|
||||
cached_tokens?: number
|
||||
}
|
||||
completion_tokens_details?: {
|
||||
reasoning_tokens?: number
|
||||
audio_tokens?: number
|
||||
accepted_prediction_tokens?: number
|
||||
rejected_prediction_tokens?: number
|
||||
}
|
||||
}
|
||||
import { handler } from "~/routes/zen/util/handler"
|
||||
|
||||
export function POST(input: APIEvent) {
|
||||
let usage: Usage
|
||||
return handler(input, {
|
||||
modifyBody: (body: any) => ({
|
||||
...body,
|
||||
...(body.stream ? { stream_options: { include_usage: true } } : {}),
|
||||
}),
|
||||
setAuthHeader: (headers: Headers, apiKey: string) => {
|
||||
headers.set("authorization", `Bearer ${apiKey}`)
|
||||
},
|
||||
format: "oa-compat",
|
||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||
onStreamPart: (chunk: string) => {
|
||||
if (!chunk.startsWith("data: ")) return
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(chunk.slice(6)) as { usage?: Usage }
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!json.usage) return
|
||||
usage = json.usage
|
||||
},
|
||||
getStreamUsage: () => usage,
|
||||
normalizeUsage: (usage: Usage) => {
|
||||
const inputTokens = usage.prompt_tokens ?? 0
|
||||
const outputTokens = usage.completion_tokens ?? 0
|
||||
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? undefined
|
||||
const cacheReadTokens = usage.cached_tokens ?? usage.prompt_tokens_details?.cached_tokens ?? undefined
|
||||
return {
|
||||
inputTokens: inputTokens - (cacheReadTokens ?? 0),
|
||||
outputTokens: outputTokens - (reasoningTokens ?? 0),
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,64 +1,9 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { handler } from "~/routes/zen/handler"
|
||||
|
||||
type Usage = {
|
||||
cache_creation?: {
|
||||
ephemeral_5m_input_tokens?: number
|
||||
ephemeral_1h_input_tokens?: number
|
||||
}
|
||||
cache_creation_input_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
input_tokens?: number
|
||||
output_tokens?: number
|
||||
server_tool_use?: {
|
||||
web_search_requests?: number
|
||||
}
|
||||
}
|
||||
import { handler } from "~/routes/zen/util/handler"
|
||||
|
||||
export function POST(input: APIEvent) {
|
||||
let usage: Usage
|
||||
return handler(input, {
|
||||
modifyBody: (body: any) => ({
|
||||
...body,
|
||||
service_tier: "standard_only",
|
||||
}),
|
||||
setAuthHeader: (headers: Headers, apiKey: string) => headers.set("x-api-key", apiKey),
|
||||
format: "anthropic",
|
||||
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
|
||||
onStreamPart: (chunk: string) => {
|
||||
const data = chunk.split("\n")[1]
|
||||
if (!data.startsWith("data: ")) return
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(data.slice(6))
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
// ie. { type: "message_start"; message: { usage: Usage } }
|
||||
// ie. { type: "message_delta"; usage: Usage }
|
||||
const usageUpdate = json.usage ?? json.message?.usage
|
||||
if (!usageUpdate) return
|
||||
usage = {
|
||||
...usage,
|
||||
...usageUpdate,
|
||||
cache_creation: {
|
||||
...usage?.cache_creation,
|
||||
...usageUpdate.cache_creation,
|
||||
},
|
||||
server_tool_use: {
|
||||
...usage?.server_tool_use,
|
||||
...usageUpdate.server_tool_use,
|
||||
},
|
||||
}
|
||||
},
|
||||
getStreamUsage: () => usage,
|
||||
normalizeUsage: (usage: Usage) => ({
|
||||
inputTokens: usage.input_tokens ?? 0,
|
||||
outputTokens: usage.output_tokens ?? 0,
|
||||
cacheReadTokens: usage.cache_read_input_tokens ?? undefined,
|
||||
cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens ?? undefined,
|
||||
cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens ?? undefined,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
||||
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
|
||||
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
||||
import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
|
||||
import { ZenData } from "@opencode-ai/console-core/model.js"
|
||||
|
||||
export async function OPTIONS(input: APIEvent) {
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function GET(input: APIEvent) {
|
||||
const zenData = ZenData.list()
|
||||
const disabledModels = await authenticate()
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
object: "list",
|
||||
data: Object.entries(zenData.models)
|
||||
.filter(([id]) => !disabledModels.includes(id))
|
||||
.map(([id, _model]) => ({
|
||||
id,
|
||||
object: "model",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
owned_by: "opencode",
|
||||
})),
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
async function authenticate() {
|
||||
const apiKey = input.request.headers.get("authorization")?.split(" ")[1]
|
||||
if (!apiKey) return []
|
||||
|
||||
const disabledModels = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
model: ModelTable.model,
|
||||
})
|
||||
.from(KeyTable)
|
||||
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID))
|
||||
.leftJoin(
|
||||
ModelTable,
|
||||
and(eq(ModelTable.workspaceID, KeyTable.workspaceID), isNull(ModelTable.timeDeleted)),
|
||||
)
|
||||
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
|
||||
.then((rows) => rows.map((row) => row.model)),
|
||||
)
|
||||
|
||||
return disabledModels
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,9 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { handler } from "~/routes/zen/handler"
|
||||
|
||||
type Usage = {
|
||||
input_tokens?: number
|
||||
input_tokens_details?: {
|
||||
cached_tokens?: number
|
||||
}
|
||||
output_tokens?: number
|
||||
output_tokens_details?: {
|
||||
reasoning_tokens?: number
|
||||
}
|
||||
total_tokens?: number
|
||||
}
|
||||
import { handler } from "~/routes/zen/util/handler"
|
||||
|
||||
export function POST(input: APIEvent) {
|
||||
let usage: Usage
|
||||
return handler(input, {
|
||||
setAuthHeader: (headers: Headers, apiKey: string) => {
|
||||
headers.set("authorization", `Bearer ${apiKey}`)
|
||||
},
|
||||
format: "openai",
|
||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||
onStreamPart: (chunk: string) => {
|
||||
const [event, data] = chunk.split("\n")
|
||||
if (event !== "event: response.completed") return
|
||||
if (!data.startsWith("data: ")) return
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(data.slice(6)) as { response?: { usage?: Usage } }
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!json.response?.usage) return
|
||||
usage = json.response.usage
|
||||
},
|
||||
getStreamUsage: () => usage,
|
||||
normalizeUsage: (usage: Usage) => {
|
||||
const inputTokens = usage.input_tokens ?? 0
|
||||
const outputTokens = usage.output_tokens ?? 0
|
||||
const reasoningTokens = usage.output_tokens_details?.reasoning_tokens ?? undefined
|
||||
const cacheReadTokens = usage.input_tokens_details?.cached_tokens ?? undefined
|
||||
return {
|
||||
inputTokens: inputTokens - (cacheReadTokens ?? 0),
|
||||
outputTokens: outputTokens - (reasoningTokens ?? 0),
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.svg" />
|
||||
<title>OpenCode</title>
|
||||
</head>
|
||||
<body class="overscroll-none select-none text-12-regular">
|
||||
<body class="antialiased overscroll-none select-none text-12-regular">
|
||||
<!-- <script> -->
|
||||
<!-- ;(function () { -->
|
||||
<!-- const savedTheme = localStorage.getItem("theme") || "opencode" -->
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"typecheck": "tsgo --noEmit"
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"opencode": "workspace:*",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-icons-spritesheet": "3.0.1",
|
||||
"vite-plugin-solid": "catalog:"
|
||||
@@ -25,7 +25,10 @@
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/active-element": "2.1.3",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
@@ -33,14 +36,13 @@
|
||||
"@solidjs/router": "0.15.3",
|
||||
"@thisbeyond/solid-dnd": "0.7.5",
|
||||
"diff": "catalog:",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"fuzzysort": "catalog:",
|
||||
"luxon": "catalog:",
|
||||
"marked": "16.2.0",
|
||||
"marked-shiki": "1.2.1",
|
||||
"remeda": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
"shiki": "3.9.2",
|
||||
"solid-js": "catalog:",
|
||||
"solid-list": "catalog:",
|
||||
"tailwindcss": "catalog:",
|
||||
"virtua": "catalog:"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bundledLanguages, type BundledLanguage, type ShikiTransformer } from "shiki"
|
||||
import { splitProps, type ComponentProps, createEffect, onMount, onCleanup, createMemo, createResource } from "solid-js"
|
||||
import { useLocal, useShiki } from "@/context"
|
||||
import type { TextSelection } from "@/context/local"
|
||||
import { useLocal, type TextSelection } from "@/context/local"
|
||||
import { getFileExtension, getNodeOffsetInLine, getSelectionInContainer } from "@/utils"
|
||||
import { useShiki } from "@/context/shiki"
|
||||
|
||||
type DefinedSelection = Exclude<TextSelection, undefined>
|
||||
|
||||
@@ -394,7 +394,7 @@ export function Code(props: Props) {
|
||||
[&_.diff-blank_.diff-oldln]:bg-background-element
|
||||
[&_.diff-blank_.diff-newln]:bg-background-element
|
||||
[&_.diff-collapsed]:block! [&_.diff-collapsed]:w-full [&_.diff-collapsed]:relative
|
||||
[&_.diff-collapsed]:cursor-pointer [&_.diff-collapsed]:select-none
|
||||
[&_.diff-collapsed]:select-none
|
||||
[&_.diff-collapsed]:bg-info/20 [&_.diff-collapsed]:hover:bg-info/40!
|
||||
[&_.diff-collapsed]:text-info/80 [&_.diff-collapsed]:hover:text-info
|
||||
[&_.diff-collapsed]:text-xs
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { FileDiff } from "@opencode-ai/sdk"
|
||||
import { createMemo, Show } from "solid-js"
|
||||
|
||||
export function DiffChanges(props: { diff: FileDiff | FileDiff[] }) {
|
||||
const additions = createMemo(() =>
|
||||
Array.isArray(props.diff) ? props.diff.reduce((acc, diff) => acc + (diff.additions ?? 0), 0) : props.diff.additions,
|
||||
)
|
||||
const deletions = createMemo(() =>
|
||||
Array.isArray(props.diff) ? props.diff.reduce((acc, diff) => acc + (diff.deletions ?? 0), 0) : props.diff.deletions,
|
||||
)
|
||||
const total = createMemo(() => additions() + deletions())
|
||||
return (
|
||||
<Show when={total() > 0}>
|
||||
<div class="flex gap-2 justify-end items-center">
|
||||
<span class="text-12-mono text-right text-text-diff-add-base">{`+${additions()}`}</span>
|
||||
<span class="text-12-mono text-right text-text-diff-delete-base">{`-${deletions()}`}</span>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
import { For, Match, Show, Switch, createSignal, splitProps } from "solid-js"
|
||||
import { Tabs, Tooltip } from "@opencode-ai/ui"
|
||||
import { Icon } from "@opencode-ai/ui"
|
||||
import { FileIcon, IconButton } from "@/ui"
|
||||
import {
|
||||
DragDropProvider,
|
||||
DragDropSensors,
|
||||
DragOverlay,
|
||||
SortableProvider,
|
||||
closestCenter,
|
||||
createSortable,
|
||||
useDragDropContext,
|
||||
} from "@thisbeyond/solid-dnd"
|
||||
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
|
||||
import type { LocalFile } from "@/context/local"
|
||||
import { Code } from "@/components/code"
|
||||
import { useLocal } from "@/context"
|
||||
import type { JSX } from "solid-js"
|
||||
|
||||
interface EditorPaneProps {
|
||||
onFileClick: (file: LocalFile) => void
|
||||
}
|
||||
|
||||
export default function EditorPane(props: EditorPaneProps): JSX.Element {
|
||||
const [localProps] = splitProps(props, ["onFileClick"])
|
||||
const local = useLocal()
|
||||
const [activeItem, setActiveItem] = createSignal<string | undefined>(undefined)
|
||||
|
||||
const navigateChange = (dir: 1 | -1) => {
|
||||
const active = local.file.active()
|
||||
if (!active) return
|
||||
const current = local.file.changeIndex(active.path)
|
||||
const next = current === undefined ? (dir === 1 ? 0 : -1) : current + dir
|
||||
local.file.setChangeIndex(active.path, next)
|
||||
}
|
||||
|
||||
const handleTabChange = (path: string) => {
|
||||
local.file.open(path)
|
||||
}
|
||||
|
||||
const handleTabClose = (file: LocalFile) => {
|
||||
local.file.close(file.path)
|
||||
}
|
||||
|
||||
const handleDragStart = (event: unknown) => {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
setActiveItem(id)
|
||||
}
|
||||
|
||||
const handleDragOver = (event: DragEvent) => {
|
||||
const { draggable, droppable } = event
|
||||
if (draggable && droppable) {
|
||||
const currentFiles = local.file.opened().map((file) => file.path)
|
||||
const fromIndex = currentFiles.indexOf(draggable.id.toString())
|
||||
const toIndex = currentFiles.indexOf(droppable.id.toString())
|
||||
if (fromIndex !== toIndex) {
|
||||
local.file.move(draggable.id.toString(), toIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragEnd = () => {
|
||||
setActiveItem(undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<DragDropProvider
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
collisionDetector={closestCenter}
|
||||
>
|
||||
<DragDropSensors />
|
||||
<ConstrainDragYAxis />
|
||||
<Tabs value={local.file.active()?.path} onChange={handleTabChange}>
|
||||
<div class="sticky top-0 shrink-0 flex">
|
||||
<Tabs.List>
|
||||
<SortableProvider ids={local.file.opened().map((file) => file.path)}>
|
||||
<For each={local.file.opened()}>
|
||||
{(file) => <SortableTab file={file} onTabClick={localProps.onFileClick} onTabClose={handleTabClose} />}
|
||||
</For>
|
||||
</SortableProvider>
|
||||
</Tabs.List>
|
||||
<div class="hidden shrink-0 h-full _flex items-center gap-1 px-2 border-b border-border-subtle/40">
|
||||
<Show when={local.file.active() && local.file.active()!.content?.diff}>
|
||||
{(() => {
|
||||
const activeFile = local.file.active()!
|
||||
const view = local.file.view(activeFile.path)
|
||||
return (
|
||||
<div class="flex items-center gap-1">
|
||||
<Show when={view !== "raw"}>
|
||||
<div class="mr-1 flex items-center gap-1">
|
||||
<Tooltip value="Previous change" placement="bottom">
|
||||
<IconButton size="xs" variant="ghost" onClick={() => navigateChange(-1)}>
|
||||
<Icon name="arrow-up" size={14} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip value="Next change" placement="bottom">
|
||||
<IconButton size="xs" variant="ghost" onClick={() => navigateChange(1)}>
|
||||
<Icon name="arrow-down" size={14} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Show>
|
||||
<Tooltip value="Raw" placement="bottom">
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
classList={{
|
||||
"text-text": view === "raw",
|
||||
"text-text-muted/70": view !== "raw",
|
||||
"bg-background-element": view === "raw",
|
||||
}}
|
||||
onClick={() => local.file.setView(activeFile.path, "raw")}
|
||||
>
|
||||
<Icon name="file-text" size={14} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip value="Unified diff" placement="bottom">
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
classList={{
|
||||
"text-text": view === "diff-unified",
|
||||
"text-text-muted/70": view !== "diff-unified",
|
||||
"bg-background-element": view === "diff-unified",
|
||||
}}
|
||||
onClick={() => local.file.setView(activeFile.path, "diff-unified")}
|
||||
>
|
||||
<Icon name="checklist" size={14} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip value="Split diff" placement="bottom">
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
classList={{
|
||||
"text-text": view === "diff-split",
|
||||
"text-text-muted/70": view !== "diff-split",
|
||||
"bg-background-element": view === "diff-split",
|
||||
}}
|
||||
onClick={() => local.file.setView(activeFile.path, "diff-split")}
|
||||
>
|
||||
<Icon name="columns" size={14} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<For each={local.file.opened()}>
|
||||
{(file) => (
|
||||
<Tabs.Content value={file.path} class="select-text">
|
||||
{(() => {
|
||||
const view = local.file.view(file.path)
|
||||
const showRaw = view === "raw" || !file.content?.diff
|
||||
const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "")
|
||||
return <Code path={file.path} code={code} class="[&_code]:pb-60" />
|
||||
})()}
|
||||
</Tabs.Content>
|
||||
)}
|
||||
</For>
|
||||
</Tabs>
|
||||
<DragOverlay>
|
||||
{(() => {
|
||||
const id = activeItem()
|
||||
if (!id) return null
|
||||
const draggedFile = local.file.node(id)
|
||||
if (!draggedFile) return null
|
||||
return (
|
||||
<div class="relative px-3 h-8 flex items-center text-sm font-medium text-text whitespace-nowrap shrink-0 bg-background-panel border-x border-border-subtle/40 border-b border-b-transparent">
|
||||
<TabVisual file={draggedFile} />
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</DragOverlay>
|
||||
</DragDropProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function TabVisual(props: { file: LocalFile }): JSX.Element {
|
||||
return (
|
||||
<div class="flex items-center gap-x-1.5">
|
||||
<FileIcon node={props.file} class="" />
|
||||
<span classList={{ "text-xs": true, "text-primary": !!props.file.status?.status, italic: !props.file.pinned }}>
|
||||
{props.file.name}
|
||||
</span>
|
||||
<span class="text-xs opacity-70">
|
||||
<Switch>
|
||||
<Match when={props.file.status?.status === "modified"}>
|
||||
<span class="text-primary">M</span>
|
||||
</Match>
|
||||
<Match when={props.file.status?.status === "added"}>
|
||||
<span class="text-success">A</span>
|
||||
</Match>
|
||||
<Match when={props.file.status?.status === "deleted"}>
|
||||
<span class="text-error">D</span>
|
||||
</Match>
|
||||
</Switch>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SortableTab(props: {
|
||||
file: LocalFile
|
||||
onTabClick: (file: LocalFile) => void
|
||||
onTabClose: (file: LocalFile) => void
|
||||
}): JSX.Element {
|
||||
const sortable = createSortable(props.file.path)
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<div use:sortable classList={{ "opacity-0": sortable.isActiveDraggable }}>
|
||||
<Tooltip value={props.file.path} placement="bottom">
|
||||
<div class="relative">
|
||||
<Tabs.Trigger value={props.file.path} class="peer/tab pr-7" onClick={() => props.onTabClick(props.file)}>
|
||||
<TabVisual file={props.file} />
|
||||
</Tabs.Trigger>
|
||||
<IconButton
|
||||
class="absolute right-1 top-1.5 opacity-0 text-text-muted/60 peer-data-[selected]/tab:opacity-100 peer-data-[selected]/tab:text-text peer-data-[selected]/tab:hover:bg-border-subtle hover:opacity-100 peer-hover/tab:opacity-100"
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
onClick={() => props.onTabClose(props.file)}
|
||||
>
|
||||
<Icon name="close" size={16} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ConstrainDragYAxis(): JSX.Element {
|
||||
const context = useDragDropContext()
|
||||
if (!context) return <></>
|
||||
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
|
||||
const transformer: Transformer = {
|
||||
id: "constrain-y-axis",
|
||||
order: 100,
|
||||
callback: (transform) => ({ ...transform, y: 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 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
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useLocal } from "@/context"
|
||||
import type { LocalFile } from "@/context/local"
|
||||
import { useLocal, type LocalFile } from "@/context/local"
|
||||
import { Tooltip } from "@opencode-ai/ui"
|
||||
import { Collapsible, FileIcon } from "@/ui"
|
||||
import { For, Match, Switch, Show, type ComponentProps, type ParentProps } from "solid-js"
|
||||
@@ -19,7 +18,7 @@ export default function FileTree(props: {
|
||||
<Dynamic
|
||||
component={p.as ?? "div"}
|
||||
classList={{
|
||||
"p-0.5 w-full flex items-center gap-x-2 hover:bg-background-element cursor-pointer": true,
|
||||
"p-0.5 w-full flex items-center gap-x-2 hover:bg-background-element": true,
|
||||
"bg-background-element": local.file.active()?.path === p.node.path,
|
||||
[props.nodeClass ?? ""]: !!props.nodeClass,
|
||||
}}
|
||||
@@ -83,7 +82,7 @@ export default function FileTree(props: {
|
||||
>
|
||||
<Collapsible.Trigger>
|
||||
<Node node={node}>
|
||||
<Collapsible.Arrow size={16} class="text-text-muted/60 ml-1" />
|
||||
<Collapsible.Arrow class="text-text-muted/60 ml-1" />
|
||||
<FileIcon
|
||||
node={node}
|
||||
expanded={local.file.node(node.path).expanded}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMarked } from "@/context"
|
||||
import { useMarked } from "@/context/marked"
|
||||
import { createResource } from "solid-js"
|
||||
|
||||
function strip(text: string): string {
|
||||
|
||||
@@ -0,0 +1,459 @@
|
||||
import type { Part, ReasoningPart, TextPart, ToolPart, Message, AssistantMessage, UserMessage } from "@opencode-ai/sdk"
|
||||
import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import { Markdown } from "./markdown"
|
||||
import { Checkbox, Collapsible, Diff, Icon, IconProps } from "@opencode-ai/ui"
|
||||
import { getDirectory, getFilename } from "@/utils"
|
||||
import type { Tool } from "opencode/tool/tool"
|
||||
import type { ReadTool } from "opencode/tool/read"
|
||||
import type { ListTool } from "opencode/tool/ls"
|
||||
import type { GlobTool } from "opencode/tool/glob"
|
||||
import type { GrepTool } from "opencode/tool/grep"
|
||||
import type { WebFetchTool } from "opencode/tool/webfetch"
|
||||
import type { TaskTool } from "opencode/tool/task"
|
||||
import type { BashTool } from "opencode/tool/bash"
|
||||
import type { EditTool } from "opencode/tool/edit"
|
||||
import type { WriteTool } from "opencode/tool/write"
|
||||
import type { TodoWriteTool } from "opencode/tool/todo"
|
||||
import { DiffChanges } from "./diff-changes"
|
||||
|
||||
export function Message(props: { message: Message; parts: Part[] }) {
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.message.role === "user" && props.message}>
|
||||
{(userMessage) => <UserMessage message={userMessage()} parts={props.parts} />}
|
||||
</Match>
|
||||
<Match when={props.message.role === "assistant" && props.message}>
|
||||
{(assistantMessage) => <AssistantMessage message={assistantMessage()} parts={props.parts} />}
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
||||
function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
|
||||
const filteredParts = createMemo(() => {
|
||||
return props.parts?.filter((x) => {
|
||||
if (x.type === "reasoning") return false
|
||||
return x.type !== "tool" || x.tool !== "todoread"
|
||||
})
|
||||
})
|
||||
return (
|
||||
<div class="w-full flex flex-col items-start gap-4">
|
||||
<For each={filteredParts()}>{(part) => <Part part={part} message={props.message} />}</For>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function UserMessage(props: { message: UserMessage; parts: Part[] }) {
|
||||
const text = createMemo(() =>
|
||||
props.parts
|
||||
?.filter((p) => p.type === "text" && !p.synthetic)
|
||||
?.map((p) => (p as TextPart).text)
|
||||
?.join(""),
|
||||
)
|
||||
return <div class="text-12-regular text-text-base line-clamp-3">{text()}</div>
|
||||
}
|
||||
|
||||
export function Part(props: { part: Part; message: Message; hideDetails?: boolean }) {
|
||||
const component = createMemo(() => PART_MAPPING[props.part.type as keyof typeof PART_MAPPING])
|
||||
return (
|
||||
<Show when={component()}>
|
||||
<Dynamic
|
||||
component={component()}
|
||||
part={props.part as any}
|
||||
message={props.message}
|
||||
hideDetails={props.hideDetails}
|
||||
/>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
const PART_MAPPING = {
|
||||
text: TextPart,
|
||||
tool: ToolPart,
|
||||
reasoning: ReasoningPart,
|
||||
}
|
||||
|
||||
function ReasoningPart(props: { part: ReasoningPart; message: Message }) {
|
||||
return (
|
||||
<Show when={props.part.text.trim()}>
|
||||
<Markdown text={props.part.text.trim()} />
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function TextPart(props: { part: TextPart; message: Message }) {
|
||||
return (
|
||||
<Show when={props.part.text.trim()}>
|
||||
<Markdown text={props.part.text.trim()} />
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function ToolPart(props: { part: ToolPart; message: Message; hideDetails?: boolean }) {
|
||||
const component = createMemo(() => {
|
||||
const render = ToolRegistry.render(props.part.tool) ?? GenericTool
|
||||
const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
|
||||
const input = props.part.state.status === "completed" ? props.part.state.input : {}
|
||||
|
||||
return (
|
||||
<Dynamic
|
||||
component={render}
|
||||
input={input}
|
||||
tool={props.part.tool}
|
||||
metadata={metadata}
|
||||
output={props.part.state.status === "completed" ? props.part.state.output : undefined}
|
||||
hideDetails={props.hideDetails}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
return <Show when={component()}>{component()}</Show>
|
||||
}
|
||||
|
||||
type TriggerTitle = {
|
||||
title: string
|
||||
titleClass?: string
|
||||
subtitle?: string
|
||||
subtitleClass?: string
|
||||
args?: string[]
|
||||
argsClass?: string
|
||||
action?: JSX.Element
|
||||
}
|
||||
|
||||
const isTriggerTitle = (val: any): val is TriggerTitle => {
|
||||
return typeof val === "object" && val !== null && "title" in val && !(val instanceof Node)
|
||||
}
|
||||
|
||||
function BasicTool(props: {
|
||||
icon: IconProps["name"]
|
||||
trigger: TriggerTitle | JSX.Element
|
||||
children?: JSX.Element
|
||||
hideDetails?: boolean
|
||||
}) {
|
||||
const resolved = children(() => props.children)
|
||||
return (
|
||||
<Collapsible>
|
||||
<Collapsible.Trigger>
|
||||
<div class="w-full flex items-center self-stretch gap-5 justify-between">
|
||||
<div class="w-full flex items-center self-stretch gap-5">
|
||||
<Icon name={props.icon} size="small" class="shrink-0" />
|
||||
<div class="grow min-w-0">
|
||||
<Switch>
|
||||
<Match when={isTriggerTitle(props.trigger) && props.trigger}>
|
||||
{(trigger) => (
|
||||
<div class="w-full flex items-center gap-2 justify-between">
|
||||
<div class="flex items-center gap-2 whitespace-nowrap truncate">
|
||||
<span
|
||||
classList={{
|
||||
"text-12-medium text-text-base": true,
|
||||
[trigger().titleClass ?? ""]: !!trigger().titleClass,
|
||||
}}
|
||||
>
|
||||
{trigger().title}
|
||||
</span>
|
||||
<Show when={trigger().subtitle}>
|
||||
<span
|
||||
classList={{
|
||||
"text-12-medium text-text-weak": true,
|
||||
[trigger().subtitleClass ?? ""]: !!trigger().subtitleClass,
|
||||
}}
|
||||
>
|
||||
{trigger().subtitle}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={trigger().args?.length}>
|
||||
<For each={trigger().args}>
|
||||
{(arg) => (
|
||||
<span
|
||||
classList={{
|
||||
"text-12-regular text-text-weak": true,
|
||||
[trigger().argsClass ?? ""]: !!trigger().argsClass,
|
||||
}}
|
||||
>
|
||||
{arg}
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={trigger().action}>{trigger().action}</Show>
|
||||
</div>
|
||||
)}
|
||||
</Match>
|
||||
<Match when={true}>{props.trigger as JSX.Element}</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={resolved() && !props.hideDetails}>
|
||||
<Collapsible.Arrow />
|
||||
</Show>
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Show when={resolved() && !props.hideDetails}>
|
||||
<Collapsible.Content>{resolved()}</Collapsible.Content>
|
||||
</Show>
|
||||
</Collapsible>
|
||||
// <>
|
||||
// <Show when={props.part.state.status === "error"}>{props.part.state.error.replace("Error: ", "")}</Show>
|
||||
// </>
|
||||
)
|
||||
}
|
||||
|
||||
function GenericTool(props: ToolProps<any>) {
|
||||
return <BasicTool icon="mcp" trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
|
||||
}
|
||||
|
||||
type ToolProps<T extends Tool.Info> = {
|
||||
input: Partial<Tool.InferParameters<T>>
|
||||
metadata: Partial<Tool.InferMetadata<T>>
|
||||
tool: string
|
||||
output?: string
|
||||
hideDetails?: boolean
|
||||
}
|
||||
|
||||
const ToolRegistry = (() => {
|
||||
const state: Record<
|
||||
string,
|
||||
{
|
||||
name: string
|
||||
render?: Component<ToolProps<any>>
|
||||
}
|
||||
> = {}
|
||||
function register<T extends Tool.Info>(input: { name: string; render?: Component<ToolProps<T>> }) {
|
||||
state[input.name] = input
|
||||
return input
|
||||
}
|
||||
return {
|
||||
register,
|
||||
render(name: string) {
|
||||
return state[name]?.render
|
||||
},
|
||||
}
|
||||
})()
|
||||
|
||||
ToolRegistry.register<typeof ReadTool>({
|
||||
name: "read",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool
|
||||
icon="glasses"
|
||||
trigger={{ title: "Read", subtitle: props.input.filePath ? getFilename(props.input.filePath) : "" }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof ListTool>({
|
||||
name: "list",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool icon="bullet-list" trigger={{ title: "List", subtitle: getDirectory(props.input.path || "/") }}>
|
||||
<Show when={false && props.output}>
|
||||
<div class="whitespace-pre">{props.output}</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof GlobTool>({
|
||||
name: "glob",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool
|
||||
icon="magnifying-glass-menu"
|
||||
trigger={{
|
||||
title: "Glob",
|
||||
subtitle: getDirectory(props.input.path || "/"),
|
||||
args: props.input.pattern ? ["pattern=" + props.input.pattern] : [],
|
||||
}}
|
||||
>
|
||||
<Show when={false && props.output}>
|
||||
<div class="whitespace-pre">{props.output}</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof GrepTool>({
|
||||
name: "grep",
|
||||
render(props) {
|
||||
const args = []
|
||||
if (props.input.pattern) args.push("pattern=" + props.input.pattern)
|
||||
if (props.input.include) args.push("include=" + props.input.include)
|
||||
return (
|
||||
<BasicTool
|
||||
icon="magnifying-glass-menu"
|
||||
trigger={{
|
||||
title: "Grep",
|
||||
subtitle: getDirectory(props.input.path || "/"),
|
||||
args,
|
||||
}}
|
||||
>
|
||||
<Show when={false && props.output}>
|
||||
<div class="whitespace-pre">{props.output}</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof WebFetchTool>({
|
||||
name: "webfetch",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool
|
||||
icon="window-cursor"
|
||||
trigger={{
|
||||
title: "Webfetch",
|
||||
subtitle: props.input.url || "",
|
||||
args: props.input.format ? ["format=" + props.input.format] : [],
|
||||
action: (
|
||||
<div class="size-6 flex items-center justify-center">
|
||||
<Icon name="square-arrow-top-right" size="small" />
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Show when={false && props.output}>
|
||||
<div class="whitespace-pre">{props.output}</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof TaskTool>({
|
||||
name: "task",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool
|
||||
icon="task"
|
||||
trigger={{
|
||||
title: `${props.input.subagent_type || props.tool} Agent`,
|
||||
titleClass: "capitalize",
|
||||
subtitle: props.input.description,
|
||||
}}
|
||||
>
|
||||
<Show when={false && props.output}>
|
||||
<div class="whitespace-pre">{props.output}</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof BashTool>({
|
||||
name: "bash",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool
|
||||
icon="console"
|
||||
trigger={{
|
||||
title: "Shell",
|
||||
subtitle: "Ran " + props.input.command,
|
||||
}}
|
||||
>
|
||||
<Show when={false && props.output}>
|
||||
<div class="whitespace-pre">{props.output}</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof EditTool>({
|
||||
name: "edit",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool
|
||||
icon="code-lines"
|
||||
trigger={
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-12-medium text-text-base capitalize">Edit</div>
|
||||
<div class="flex">
|
||||
<Show when={props.input.filePath?.includes("/")}>
|
||||
<span class="text-text-weak">{getDirectory(props.input.filePath!)}</span>
|
||||
</Show>
|
||||
<span class="text-text-strong">{getFilename(props.input.filePath ?? "")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4 items-center justify-end">
|
||||
<Show when={props.metadata.filediff}>
|
||||
<DiffChanges diff={props.metadata.filediff} />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Show when={props.metadata.filediff}>
|
||||
<div class="border-t border-border-weaker-base">
|
||||
<Diff
|
||||
before={{ name: getFilename(props.metadata.filediff.path), contents: props.metadata.filediff.before }}
|
||||
after={{ name: getFilename(props.metadata.filediff.path), contents: props.metadata.filediff.after }}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof WriteTool>({
|
||||
name: "write",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool
|
||||
icon="code-lines"
|
||||
trigger={
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-12-medium text-text-base capitalize">Write</div>
|
||||
<div class="flex">
|
||||
<Show when={props.input.filePath?.includes("/")}>
|
||||
<span class="text-text-weak">{getDirectory(props.input.filePath!)}</span>
|
||||
</Show>
|
||||
<span class="text-text-strong">{getFilename(props.input.filePath ?? "")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4 items-center justify-end">{/* <DiffChanges diff={diff} /> */}</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Show when={false && props.output}>
|
||||
<div class="whitespace-pre">{props.output}</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
ToolRegistry.register<typeof TodoWriteTool>({
|
||||
name: "todowrite",
|
||||
render(props) {
|
||||
return (
|
||||
<BasicTool
|
||||
icon="checklist"
|
||||
trigger={{
|
||||
title: "To-dos",
|
||||
subtitle: `${props.input.todos?.filter((t) => t.status === "completed").length}/${props.input.todos?.length}`,
|
||||
}}
|
||||
>
|
||||
<Show when={props.input.todos?.length}>
|
||||
<div class="px-12 pt-2.5 pb-6 flex flex-col gap-2">
|
||||
<For each={props.input.todos}>
|
||||
{(todo) => (
|
||||
<Checkbox readOnly checked={todo.status === "completed"}>
|
||||
<div classList={{ "line-through text-text-weaker": todo.status === "completed" }}>{todo.content}</div>
|
||||
</Checkbox>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</BasicTool>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -1,164 +0,0 @@
|
||||
import type { TextSelection } from "@/context/local"
|
||||
import { getFilename } from "@/utils"
|
||||
|
||||
export interface PromptTextPart {
|
||||
kind: "text"
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface PromptAttachmentPart {
|
||||
kind: "attachment"
|
||||
token: string
|
||||
display: string
|
||||
path: string
|
||||
selection?: TextSelection
|
||||
origin: "context" | "active"
|
||||
}
|
||||
|
||||
export interface PromptInterimPart {
|
||||
kind: "interim"
|
||||
value: string
|
||||
leadingSpace: boolean
|
||||
}
|
||||
|
||||
export type PromptContentPart = PromptTextPart | PromptAttachmentPart
|
||||
|
||||
export type PromptDisplaySegment =
|
||||
| { kind: "text"; value: string }
|
||||
| { kind: "attachment"; part: PromptAttachmentPart; source: string }
|
||||
| PromptInterimPart
|
||||
|
||||
export interface AttachmentCandidate {
|
||||
origin: "context" | "active"
|
||||
path: string
|
||||
selection?: TextSelection
|
||||
display: string
|
||||
}
|
||||
|
||||
export interface PromptSubmitValue {
|
||||
text: string
|
||||
parts: PromptContentPart[]
|
||||
}
|
||||
|
||||
export const mentionPattern = /@([A-Za-z0-9_\-./]+)/g
|
||||
export const mentionTriggerPattern = /(^|\s)@([A-Za-z0-9_\-./]*)$/
|
||||
|
||||
export type PromptSegment = (PromptTextPart | PromptAttachmentPart) & {
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
export type PromptAttachmentSegment = PromptAttachmentPart & {
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
function pushTextPart(parts: PromptContentPart[], value: string) {
|
||||
if (!value) return
|
||||
const last = parts[parts.length - 1]
|
||||
if (last && last.kind === "text") {
|
||||
last.value += value
|
||||
return
|
||||
}
|
||||
parts.push({ kind: "text", value })
|
||||
}
|
||||
|
||||
function addTextSegment(segments: PromptSegment[], start: number, value: string) {
|
||||
if (!value) return
|
||||
segments.push({ kind: "text", value, start, end: start + value.length })
|
||||
}
|
||||
|
||||
export function createAttachmentDisplay(path: string, selection?: TextSelection) {
|
||||
const base = getFilename(path)
|
||||
if (!selection) return base
|
||||
return `${base} (${selection.startLine}-${selection.endLine})`
|
||||
}
|
||||
|
||||
export function registerCandidate(
|
||||
map: Map<string, AttachmentCandidate>,
|
||||
candidate: AttachmentCandidate,
|
||||
tokens: (string | undefined)[],
|
||||
) {
|
||||
for (const token of tokens) {
|
||||
if (!token) continue
|
||||
const normalized = token.toLowerCase()
|
||||
if (map.has(normalized)) continue
|
||||
map.set(normalized, candidate)
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePrompt(value: string, lookup: Map<string, AttachmentCandidate>) {
|
||||
const segments: PromptSegment[] = []
|
||||
if (!value) return { parts: [] as PromptContentPart[], segments }
|
||||
|
||||
const pushTextRange = (rangeStart: number, rangeEnd: number) => {
|
||||
if (rangeEnd <= rangeStart) return
|
||||
const text = value.slice(rangeStart, rangeEnd)
|
||||
let cursor = 0
|
||||
for (const match of text.matchAll(mentionPattern)) {
|
||||
const localIndex = match.index ?? 0
|
||||
if (localIndex > cursor) {
|
||||
addTextSegment(segments, rangeStart + cursor, text.slice(cursor, localIndex))
|
||||
}
|
||||
const token = match[1]
|
||||
const candidate = lookup.get(token.toLowerCase())
|
||||
if (candidate) {
|
||||
const start = rangeStart + localIndex
|
||||
const end = start + match[0].length
|
||||
segments.push({
|
||||
kind: "attachment",
|
||||
token,
|
||||
display: candidate.display,
|
||||
path: candidate.path,
|
||||
selection: candidate.selection,
|
||||
origin: candidate.origin,
|
||||
start,
|
||||
end,
|
||||
})
|
||||
} else {
|
||||
addTextSegment(segments, rangeStart + localIndex, match[0])
|
||||
}
|
||||
cursor = localIndex + match[0].length
|
||||
}
|
||||
if (cursor < text.length) {
|
||||
addTextSegment(segments, rangeStart + cursor, text.slice(cursor))
|
||||
}
|
||||
}
|
||||
|
||||
pushTextRange(0, value.length)
|
||||
|
||||
const parts: PromptContentPart[] = []
|
||||
for (const segment of segments) {
|
||||
if (segment.kind === "text") {
|
||||
pushTextPart(parts, segment.value)
|
||||
} else {
|
||||
const { start, end, ...attachment } = segment
|
||||
parts.push(attachment as PromptAttachmentPart)
|
||||
}
|
||||
}
|
||||
return { parts, segments }
|
||||
}
|
||||
|
||||
export function composeDisplaySegments(
|
||||
segments: PromptSegment[],
|
||||
inputValue: string,
|
||||
interim: string,
|
||||
): PromptDisplaySegment[] {
|
||||
if (segments.length === 0 && !interim) return []
|
||||
|
||||
const display: PromptDisplaySegment[] = segments.map((segment) => {
|
||||
if (segment.kind === "text") {
|
||||
return { kind: "text", value: segment.value }
|
||||
}
|
||||
const { start, end, ...part } = segment
|
||||
const placeholder = inputValue.slice(start, end)
|
||||
return { kind: "attachment", part: part as PromptAttachmentPart, source: placeholder }
|
||||
})
|
||||
|
||||
if (interim) {
|
||||
const leadingSpace = !!(inputValue && !inputValue.endsWith(" ") && !interim.startsWith(" "))
|
||||
display.push({ kind: "interim", value: interim, leadingSpace })
|
||||
}
|
||||
|
||||
return display
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
import { createEffect, createMemo, createResource, type Accessor } from "solid-js"
|
||||
import type { SetStoreFunction } from "solid-js/store"
|
||||
import { getDirectory, getFilename } from "@/utils"
|
||||
import { createSpeechRecognition } from "@/utils/speech"
|
||||
import {
|
||||
createAttachmentDisplay,
|
||||
mentionPattern,
|
||||
mentionTriggerPattern,
|
||||
type PromptAttachmentPart,
|
||||
type PromptAttachmentSegment,
|
||||
} from "./prompt-form-helpers"
|
||||
import type { LocalFile, TextSelection } from "@/context/local"
|
||||
|
||||
export type MentionRange = {
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
export interface PromptFormState {
|
||||
promptInput: string
|
||||
isDragOver: boolean
|
||||
mentionOpen: boolean
|
||||
mentionQuery: string
|
||||
mentionRange: MentionRange | undefined
|
||||
mentionIndex: number
|
||||
mentionAnchorOffset: { x: number; y: number }
|
||||
inlineAliases: Map<string, PromptAttachmentPart>
|
||||
}
|
||||
|
||||
interface MentionControllerOptions {
|
||||
state: PromptFormState
|
||||
setState: SetStoreFunction<PromptFormState>
|
||||
attachmentSegments: Accessor<PromptAttachmentSegment[]>
|
||||
getInputRef: () => HTMLTextAreaElement | undefined
|
||||
getOverlayRef: () => HTMLDivElement | undefined
|
||||
getMeasureRef: () => HTMLDivElement | undefined
|
||||
searchFiles: (query: string) => Promise<string[]>
|
||||
resolveFile: (path: string) => LocalFile | undefined
|
||||
addContextFile: (path: string, selection?: TextSelection) => void
|
||||
getActiveContext: () => { path: string; selection?: TextSelection } | undefined
|
||||
}
|
||||
|
||||
interface MentionKeyDownOptions {
|
||||
event: KeyboardEvent & { currentTarget: HTMLTextAreaElement }
|
||||
mentionItems: () => string[]
|
||||
insertMention: (path: string) => void
|
||||
}
|
||||
|
||||
interface ScrollSyncOptions {
|
||||
state: PromptFormState
|
||||
getInputRef: () => HTMLTextAreaElement | undefined
|
||||
getOverlayRef: () => HTMLDivElement | undefined
|
||||
interim: Accessor<string>
|
||||
updateMentionPosition: (element: HTMLTextAreaElement, range?: MentionRange) => void
|
||||
}
|
||||
|
||||
export function usePromptSpeech(updatePromptInput: (updater: (prev: string) => string) => void) {
|
||||
return createSpeechRecognition({
|
||||
onFinal: (text) => updatePromptInput((prev) => (prev && !prev.endsWith(" ") ? `${prev} ` : prev) + text),
|
||||
})
|
||||
}
|
||||
|
||||
export function useMentionController(options: MentionControllerOptions) {
|
||||
const mentionSource = createMemo(() => (options.state.mentionOpen ? options.state.mentionQuery : undefined))
|
||||
const [mentionResults, { mutate: mutateMentionResults }] = createResource(mentionSource, (query) => {
|
||||
if (!options.state.mentionOpen) return []
|
||||
return options.searchFiles(query ?? "")
|
||||
})
|
||||
const mentionItems = createMemo(() => mentionResults() ?? [])
|
||||
|
||||
createEffect(() => {
|
||||
if (!options.state.mentionOpen) return
|
||||
options.state.mentionQuery
|
||||
options.setState("mentionIndex", 0)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!options.state.mentionOpen) return
|
||||
queueMicrotask(() => {
|
||||
const input = options.getInputRef()
|
||||
if (!input) return
|
||||
if (document.activeElement === input) return
|
||||
input.focus()
|
||||
})
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const used = new Set<string>()
|
||||
for (const match of options.state.promptInput.matchAll(mentionPattern)) {
|
||||
const token = match[1]
|
||||
if (token) used.add(token.toLowerCase())
|
||||
}
|
||||
options.setState("inlineAliases", (prev) => {
|
||||
if (prev.size === 0) return prev
|
||||
const next = new Map(prev)
|
||||
let changed = false
|
||||
for (const key of prev.keys()) {
|
||||
if (!used.has(key.toLowerCase())) {
|
||||
next.delete(key)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return changed ? next : prev
|
||||
})
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!options.state.mentionOpen) return
|
||||
const items = mentionItems()
|
||||
if (items.length === 0) {
|
||||
options.setState("mentionIndex", 0)
|
||||
return
|
||||
}
|
||||
if (options.state.mentionIndex < items.length) return
|
||||
options.setState("mentionIndex", items.length - 1)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!options.state.mentionOpen) return
|
||||
const rangeValue = options.state.mentionRange
|
||||
if (!rangeValue) return
|
||||
options.state.promptInput
|
||||
queueMicrotask(() => {
|
||||
const input = options.getInputRef()
|
||||
if (!input) return
|
||||
updateMentionPosition(input, rangeValue)
|
||||
})
|
||||
})
|
||||
|
||||
function closeMention() {
|
||||
if (options.state.mentionOpen) options.setState("mentionOpen", false)
|
||||
options.setState("mentionQuery", "")
|
||||
options.setState("mentionRange", undefined)
|
||||
options.setState("mentionIndex", 0)
|
||||
mutateMentionResults(() => undefined)
|
||||
options.setState("mentionAnchorOffset", { x: 0, y: 0 })
|
||||
}
|
||||
|
||||
function updateMentionPosition(element: HTMLTextAreaElement, rangeValue = options.state.mentionRange) {
|
||||
const measure = options.getMeasureRef()
|
||||
if (!measure) return
|
||||
if (!rangeValue) return
|
||||
measure.style.width = `${element.clientWidth}px`
|
||||
const measurement = element.value.slice(0, rangeValue.end)
|
||||
measure.textContent = measurement
|
||||
const caretSpan = document.createElement("span")
|
||||
caretSpan.textContent = "\u200b"
|
||||
measure.append(caretSpan)
|
||||
const caretRect = caretSpan.getBoundingClientRect()
|
||||
const containerRect = measure.getBoundingClientRect()
|
||||
measure.removeChild(caretSpan)
|
||||
const left = caretRect.left - containerRect.left
|
||||
const top = caretRect.top - containerRect.top - element.scrollTop
|
||||
options.setState("mentionAnchorOffset", { x: left, y: top < 0 ? 0 : top })
|
||||
}
|
||||
|
||||
function isValidMentionQuery(value: string) {
|
||||
return /^[A-Za-z0-9_\-./]*$/.test(value)
|
||||
}
|
||||
|
||||
function syncMentionFromCaret(element: HTMLTextAreaElement) {
|
||||
if (!options.state.mentionOpen) return
|
||||
const rangeValue = options.state.mentionRange
|
||||
if (!rangeValue) {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
const caret = element.selectionEnd ?? element.selectionStart ?? element.value.length
|
||||
if (rangeValue.start < 0 || rangeValue.start >= element.value.length) {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
if (element.value[rangeValue.start] !== "@") {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
if (caret <= rangeValue.start) {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
const mentionValue = element.value.slice(rangeValue.start + 1, caret)
|
||||
if (!isValidMentionQuery(mentionValue)) {
|
||||
closeMention()
|
||||
return
|
||||
}
|
||||
options.setState("mentionRange", { start: rangeValue.start, end: caret })
|
||||
options.setState("mentionQuery", mentionValue)
|
||||
updateMentionPosition(element, { start: rangeValue.start, end: caret })
|
||||
}
|
||||
|
||||
function tryOpenMentionFromCaret(element: HTMLTextAreaElement) {
|
||||
const selectionStart = element.selectionStart ?? element.value.length
|
||||
const selectionEnd = element.selectionEnd ?? selectionStart
|
||||
if (selectionStart !== selectionEnd) return false
|
||||
const caret = selectionEnd
|
||||
if (options.attachmentSegments().some((segment) => caret >= segment.start && caret <= segment.end)) {
|
||||
return false
|
||||
}
|
||||
const before = element.value.slice(0, caret)
|
||||
const match = before.match(mentionTriggerPattern)
|
||||
if (!match) return false
|
||||
const token = match[2] ?? ""
|
||||
const start = caret - token.length - 1
|
||||
if (start < 0) return false
|
||||
options.setState("mentionOpen", true)
|
||||
options.setState("mentionRange", { start, end: caret })
|
||||
options.setState("mentionQuery", token)
|
||||
options.setState("mentionIndex", 0)
|
||||
queueMicrotask(() => {
|
||||
updateMentionPosition(element, { start, end: caret })
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
function handlePromptInput(event: InputEvent & { currentTarget: HTMLTextAreaElement }) {
|
||||
const element = event.currentTarget
|
||||
options.setState("promptInput", element.value)
|
||||
if (options.state.mentionOpen) {
|
||||
syncMentionFromCaret(element)
|
||||
if (options.state.mentionOpen) return
|
||||
}
|
||||
const isDeletion = event.inputType ? event.inputType.startsWith("delete") : false
|
||||
if (!isDeletion && tryOpenMentionFromCaret(element)) return
|
||||
closeMention()
|
||||
}
|
||||
|
||||
function handleMentionKeyDown({ event, mentionItems: items, insertMention }: MentionKeyDownOptions) {
|
||||
if (!options.state.mentionOpen) return false
|
||||
const list = items()
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault()
|
||||
if (list.length === 0) return true
|
||||
const next = options.state.mentionIndex + 1 >= list.length ? 0 : options.state.mentionIndex + 1
|
||||
options.setState("mentionIndex", next)
|
||||
return true
|
||||
}
|
||||
if (event.key === "ArrowUp") {
|
||||
event.preventDefault()
|
||||
if (list.length === 0) return true
|
||||
const previous = options.state.mentionIndex - 1 < 0 ? list.length - 1 : options.state.mentionIndex - 1
|
||||
options.setState("mentionIndex", previous)
|
||||
return true
|
||||
}
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
const targetItem = list[options.state.mentionIndex] ?? list[0]
|
||||
if (targetItem) insertMention(targetItem)
|
||||
return true
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault()
|
||||
closeMention()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function generateMentionAlias(path: string) {
|
||||
const existing = new Set<string>()
|
||||
for (const key of options.state.inlineAliases.keys()) {
|
||||
existing.add(key.toLowerCase())
|
||||
}
|
||||
for (const match of options.state.promptInput.matchAll(mentionPattern)) {
|
||||
const token = match[1]
|
||||
if (token) existing.add(token.toLowerCase())
|
||||
}
|
||||
|
||||
const base = getFilename(path)
|
||||
if (base) {
|
||||
if (!existing.has(base.toLowerCase())) return base
|
||||
}
|
||||
|
||||
const directory = getDirectory(path)
|
||||
if (base && directory) {
|
||||
const segments = directory.split("/").filter(Boolean)
|
||||
for (let i = segments.length - 1; i >= 0; i -= 1) {
|
||||
const candidate = `${segments.slice(i).join("/")}/${base}`
|
||||
if (!existing.has(candidate.toLowerCase())) return candidate
|
||||
}
|
||||
}
|
||||
|
||||
if (!existing.has(path.toLowerCase())) return path
|
||||
|
||||
const fallback = base || path || "file"
|
||||
let index = 2
|
||||
let candidate = `${fallback}-${index}`
|
||||
while (existing.has(candidate.toLowerCase())) {
|
||||
index += 1
|
||||
candidate = `${fallback}-${index}`
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
|
||||
function insertMention(path: string) {
|
||||
const input = options.getInputRef()
|
||||
if (!input) return
|
||||
const rangeValue = options.state.mentionRange
|
||||
if (!rangeValue) return
|
||||
const node = options.resolveFile(path)
|
||||
const alias = generateMentionAlias(path)
|
||||
const mentionText = `@${alias}`
|
||||
const value = options.state.promptInput
|
||||
const before = value.slice(0, rangeValue.start)
|
||||
const after = value.slice(rangeValue.end)
|
||||
const needsLeadingSpace = before.length > 0 && !/\s$/.test(before)
|
||||
const needsTrailingSpace = after.length > 0 && !/^\s/.test(after)
|
||||
const leading = needsLeadingSpace ? `${before} ` : before
|
||||
const trailingSpacer = needsTrailingSpace ? " " : ""
|
||||
const nextValue = `${leading}${mentionText}${trailingSpacer}${after}`
|
||||
const origin = options.getActiveContext()?.path === path ? "active" : "context"
|
||||
const part: PromptAttachmentPart = {
|
||||
kind: "attachment",
|
||||
token: alias,
|
||||
display: createAttachmentDisplay(path, node?.selection),
|
||||
path,
|
||||
selection: node?.selection,
|
||||
origin,
|
||||
}
|
||||
options.setState("promptInput", nextValue)
|
||||
if (input.value !== nextValue) {
|
||||
input.value = nextValue
|
||||
}
|
||||
options.setState("inlineAliases", (prev) => {
|
||||
const next = new Map(prev)
|
||||
next.set(alias, part)
|
||||
return next
|
||||
})
|
||||
options.addContextFile(path, node?.selection)
|
||||
closeMention()
|
||||
queueMicrotask(() => {
|
||||
const caret = leading.length + mentionText.length + trailingSpacer.length
|
||||
input.setSelectionRange(caret, caret)
|
||||
syncMentionFromCaret(input)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
mentionResults,
|
||||
mentionItems,
|
||||
closeMention,
|
||||
syncMentionFromCaret,
|
||||
tryOpenMentionFromCaret,
|
||||
updateMentionPosition,
|
||||
handlePromptInput,
|
||||
handleMentionKeyDown,
|
||||
insertMention,
|
||||
}
|
||||
}
|
||||
|
||||
export function usePromptScrollSync(options: ScrollSyncOptions) {
|
||||
let shouldAutoScroll = true
|
||||
|
||||
createEffect(() => {
|
||||
options.state.promptInput
|
||||
options.interim()
|
||||
queueMicrotask(() => {
|
||||
const input = options.getInputRef()
|
||||
const overlay = options.getOverlayRef()
|
||||
if (!input || !overlay) return
|
||||
if (!shouldAutoScroll) {
|
||||
overlay.scrollTop = input.scrollTop
|
||||
if (options.state.mentionOpen) options.updateMentionPosition(input)
|
||||
return
|
||||
}
|
||||
const maxInputScroll = input.scrollHeight - input.clientHeight
|
||||
const next = maxInputScroll > 0 ? maxInputScroll : 0
|
||||
input.scrollTop = next
|
||||
overlay.scrollTop = next
|
||||
if (options.state.mentionOpen) options.updateMentionPosition(input)
|
||||
})
|
||||
})
|
||||
|
||||
function handlePromptScroll(event: Event & { currentTarget: HTMLTextAreaElement }) {
|
||||
const target = event.currentTarget
|
||||
shouldAutoScroll = target.scrollTop + target.clientHeight >= target.scrollHeight - 4
|
||||
const overlay = options.getOverlayRef()
|
||||
if (overlay) overlay.scrollTop = target.scrollTop
|
||||
if (options.state.mentionOpen) options.updateMentionPosition(target)
|
||||
}
|
||||
|
||||
function resetScrollPosition() {
|
||||
shouldAutoScroll = true
|
||||
const input = options.getInputRef()
|
||||
const overlay = options.getOverlayRef()
|
||||
if (input) input.scrollTop = 0
|
||||
if (overlay) overlay.scrollTop = 0
|
||||
}
|
||||
|
||||
return {
|
||||
handlePromptScroll,
|
||||
resetScrollPosition,
|
||||
setAutoScroll: (value: boolean) => {
|
||||
shouldAutoScroll = value
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,581 +0,0 @@
|
||||
import { For, Show, createMemo, onCleanup, type JSX } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Popover } from "@kobalte/core/popover"
|
||||
import { Tooltip, Button, Icon, Select } from "@opencode-ai/ui"
|
||||
import { FileIcon, IconButton } from "@/ui"
|
||||
import { useLocal } from "@/context"
|
||||
import type { FileContext, LocalFile } from "@/context/local"
|
||||
import { getDirectory, getFilename } from "@/utils"
|
||||
import { composeDisplaySegments, createAttachmentDisplay, parsePrompt, registerCandidate } from "./prompt-form-helpers"
|
||||
import type {
|
||||
AttachmentCandidate,
|
||||
PromptAttachmentPart,
|
||||
PromptAttachmentSegment,
|
||||
PromptDisplaySegment,
|
||||
PromptSubmitValue,
|
||||
} from "./prompt-form-helpers"
|
||||
import { useMentionController, usePromptScrollSync, usePromptSpeech, type PromptFormState } from "./prompt-form-hooks"
|
||||
|
||||
interface PromptFormProps {
|
||||
class?: string
|
||||
classList?: Record<string, boolean>
|
||||
onSubmit: (prompt: PromptSubmitValue) => Promise<void> | void
|
||||
onOpenModelSelect: () => void
|
||||
onInputRefChange?: (element: HTMLTextAreaElement | undefined) => void
|
||||
}
|
||||
|
||||
export default function PromptForm(props: PromptFormProps) {
|
||||
const local = useLocal()
|
||||
|
||||
const [state, setState] = createStore<PromptFormState>({
|
||||
promptInput: "",
|
||||
isDragOver: false,
|
||||
mentionOpen: false,
|
||||
mentionQuery: "",
|
||||
mentionRange: undefined,
|
||||
mentionIndex: 0,
|
||||
mentionAnchorOffset: { x: 0, y: 0 },
|
||||
inlineAliases: new Map<string, PromptAttachmentPart>(),
|
||||
})
|
||||
|
||||
const placeholderText = "Start typing or speaking..."
|
||||
|
||||
const {
|
||||
isSupported,
|
||||
isRecording,
|
||||
interim: interimTranscript,
|
||||
start: startSpeech,
|
||||
stop: stopSpeech,
|
||||
} = usePromptSpeech((updater) => setState("promptInput", updater))
|
||||
|
||||
let inputRef: HTMLTextAreaElement | undefined = undefined
|
||||
let overlayContainerRef: HTMLDivElement | undefined = undefined
|
||||
let mentionMeasureRef: HTMLDivElement | undefined = undefined
|
||||
|
||||
const attachmentLookup = createMemo(() => {
|
||||
const map = new Map<string, AttachmentCandidate>()
|
||||
const activeFile = local.context.active()
|
||||
if (activeFile) {
|
||||
registerCandidate(
|
||||
map,
|
||||
{
|
||||
origin: "active",
|
||||
path: activeFile.path,
|
||||
selection: activeFile.selection,
|
||||
display: createAttachmentDisplay(activeFile.path, activeFile.selection),
|
||||
},
|
||||
[activeFile.path, getFilename(activeFile.path)],
|
||||
)
|
||||
}
|
||||
for (const item of local.context.all()) {
|
||||
registerCandidate(
|
||||
map,
|
||||
{
|
||||
origin: "context",
|
||||
path: item.path,
|
||||
selection: item.selection,
|
||||
display: createAttachmentDisplay(item.path, item.selection),
|
||||
},
|
||||
[item.path, getFilename(item.path)],
|
||||
)
|
||||
}
|
||||
for (const [alias, part] of state.inlineAliases) {
|
||||
registerCandidate(
|
||||
map,
|
||||
{
|
||||
origin: part.origin,
|
||||
path: part.path,
|
||||
selection: part.selection,
|
||||
display: part.display ?? createAttachmentDisplay(part.path, part.selection),
|
||||
},
|
||||
[alias],
|
||||
)
|
||||
}
|
||||
return map
|
||||
})
|
||||
|
||||
const parsedPrompt = createMemo(() => parsePrompt(state.promptInput, attachmentLookup()))
|
||||
const baseParts = createMemo(() => parsedPrompt().parts)
|
||||
const attachmentSegments = createMemo<PromptAttachmentSegment[]>(() =>
|
||||
parsedPrompt().segments.filter((segment): segment is PromptAttachmentSegment => segment.kind === "attachment"),
|
||||
)
|
||||
|
||||
const {
|
||||
mentionResults,
|
||||
mentionItems,
|
||||
closeMention,
|
||||
syncMentionFromCaret,
|
||||
updateMentionPosition,
|
||||
handlePromptInput,
|
||||
handleMentionKeyDown,
|
||||
insertMention,
|
||||
} = useMentionController({
|
||||
state,
|
||||
setState,
|
||||
attachmentSegments,
|
||||
getInputRef: () => inputRef,
|
||||
getOverlayRef: () => overlayContainerRef,
|
||||
getMeasureRef: () => mentionMeasureRef,
|
||||
searchFiles: (query) => local.file.search(query),
|
||||
resolveFile: (path) => local.file.node(path) ?? undefined,
|
||||
addContextFile: (path, selection) =>
|
||||
local.context.add({
|
||||
type: "file",
|
||||
path,
|
||||
selection,
|
||||
}),
|
||||
getActiveContext: () => local.context.active() ?? undefined,
|
||||
})
|
||||
|
||||
const { handlePromptScroll, resetScrollPosition } = usePromptScrollSync({
|
||||
state,
|
||||
getInputRef: () => inputRef,
|
||||
getOverlayRef: () => overlayContainerRef,
|
||||
interim: () => (isRecording() ? interimTranscript() : ""),
|
||||
updateMentionPosition,
|
||||
})
|
||||
|
||||
const displaySegments = createMemo<PromptDisplaySegment[]>(() => {
|
||||
const value = state.promptInput
|
||||
const segments = parsedPrompt().segments
|
||||
const interim = isRecording() ? interimTranscript() : ""
|
||||
return composeDisplaySegments(segments, value, interim)
|
||||
})
|
||||
|
||||
const hasDisplaySegments = createMemo(() => displaySegments().length > 0)
|
||||
|
||||
function handleAttachmentNavigation(
|
||||
event: KeyboardEvent & { currentTarget: HTMLTextAreaElement },
|
||||
direction: "left" | "right",
|
||||
) {
|
||||
const element = event.currentTarget
|
||||
const caret = element.selectionStart ?? 0
|
||||
const segments = attachmentSegments()
|
||||
if (direction === "left") {
|
||||
let match = segments.find((segment) => caret > segment.start && caret <= segment.end)
|
||||
if (!match && element.selectionStart !== element.selectionEnd) {
|
||||
match = segments.find(
|
||||
(segment) => element.selectionStart === segment.start && element.selectionEnd === segment.end,
|
||||
)
|
||||
}
|
||||
if (!match) return false
|
||||
event.preventDefault()
|
||||
if (element.selectionStart === match.start && element.selectionEnd === match.end) {
|
||||
const next = Math.max(0, match.start)
|
||||
element.setSelectionRange(next, next)
|
||||
syncMentionFromCaret(element)
|
||||
return true
|
||||
}
|
||||
element.setSelectionRange(match.start, match.end)
|
||||
syncMentionFromCaret(element)
|
||||
return true
|
||||
}
|
||||
if (direction === "right") {
|
||||
let match = segments.find((segment) => caret >= segment.start && caret < segment.end)
|
||||
if (!match && element.selectionStart !== element.selectionEnd) {
|
||||
match = segments.find(
|
||||
(segment) => element.selectionStart === segment.start && element.selectionEnd === segment.end,
|
||||
)
|
||||
}
|
||||
if (!match) return false
|
||||
event.preventDefault()
|
||||
if (element.selectionStart === match.start && element.selectionEnd === match.end) {
|
||||
const next = match.end
|
||||
element.setSelectionRange(next, next)
|
||||
syncMentionFromCaret(element)
|
||||
return true
|
||||
}
|
||||
element.setSelectionRange(match.start, match.end)
|
||||
syncMentionFromCaret(element)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function renderAttachmentChip(part: PromptAttachmentPart, _placeholder: string) {
|
||||
const display = part.display ?? createAttachmentDisplay(part.path, part.selection)
|
||||
return <span class="truncate max-w-[16ch] text-primary">@{display}</span>
|
||||
}
|
||||
|
||||
function renderTextSegment(value: string) {
|
||||
if (!value) return undefined
|
||||
return <span class="text-text">{value}</span>
|
||||
}
|
||||
|
||||
function handlePromptKeyDown(event: KeyboardEvent & { currentTarget: HTMLTextAreaElement }) {
|
||||
if (event.isComposing) return
|
||||
const target = event.currentTarget
|
||||
const key = event.key
|
||||
|
||||
const handled = handleMentionKeyDown({
|
||||
event,
|
||||
mentionItems,
|
||||
insertMention,
|
||||
})
|
||||
if (handled) return
|
||||
|
||||
if (!state.mentionOpen) {
|
||||
if (key === "ArrowLeft") {
|
||||
if (handleAttachmentNavigation(event, "left")) return
|
||||
}
|
||||
if (key === "ArrowRight") {
|
||||
if (handleAttachmentNavigation(event, "right")) return
|
||||
}
|
||||
}
|
||||
|
||||
if (key === "ArrowLeft" || key === "ArrowRight" || key === "Home" || key === "End") {
|
||||
queueMicrotask(() => {
|
||||
syncMentionFromCaret(target)
|
||||
})
|
||||
}
|
||||
|
||||
if (key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
target.form?.requestSubmit()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (event: SubmitEvent) => {
|
||||
event.preventDefault()
|
||||
const parts = baseParts()
|
||||
const text = parts
|
||||
.map((part) => {
|
||||
if (part.kind === "text") return part.value
|
||||
return `@${part.path}`
|
||||
})
|
||||
.join("")
|
||||
|
||||
const currentPrompt: PromptSubmitValue = {
|
||||
text,
|
||||
parts,
|
||||
}
|
||||
setState("promptInput", "")
|
||||
resetScrollPosition()
|
||||
if (inputRef) {
|
||||
inputRef.blur()
|
||||
}
|
||||
|
||||
await props.onSubmit(currentPrompt)
|
||||
}
|
||||
|
||||
onCleanup(() => {
|
||||
props.onInputRefChange?.(undefined)
|
||||
})
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} class={props.class} classList={props.classList}>
|
||||
<div
|
||||
class="w-full min-w-0 p-2 mx-auto rounded-lg isolate backdrop-blur-xs
|
||||
flex flex-col gap-1 bg-gradient-to-b from-background-panel/90 to-background/90
|
||||
ring-1 ring-border-active/50 border border-transparent
|
||||
focus-within:ring-2 focus-within:ring-primary/40 focus-within:border-primary
|
||||
transition-all duration-200"
|
||||
classList={{
|
||||
"shadow-[0_0_33px_rgba(0,0,0,0.8)]": !!local.file.active(),
|
||||
"ring-2 ring-primary/60 bg-primary/5": state.isDragOver,
|
||||
}}
|
||||
onDragEnter={(event) => {
|
||||
const evt = event as unknown as globalThis.DragEvent
|
||||
if (evt.dataTransfer?.types.includes("text/plain")) {
|
||||
evt.preventDefault()
|
||||
setState("isDragOver", true)
|
||||
}
|
||||
}}
|
||||
onDragLeave={(event) => {
|
||||
if (event.currentTarget === event.target) {
|
||||
setState("isDragOver", false)
|
||||
}
|
||||
}}
|
||||
onDragOver={(event) => {
|
||||
const evt = event as unknown as globalThis.DragEvent
|
||||
if (evt.dataTransfer?.types.includes("text/plain")) {
|
||||
evt.preventDefault()
|
||||
evt.dataTransfer.dropEffect = "copy"
|
||||
}
|
||||
}}
|
||||
onDrop={(event) => {
|
||||
const evt = event as unknown as globalThis.DragEvent
|
||||
evt.preventDefault()
|
||||
setState("isDragOver", false)
|
||||
|
||||
const data = evt.dataTransfer?.getData("text/plain")
|
||||
if (data && data.startsWith("file:")) {
|
||||
const filePath = data.slice(5)
|
||||
const fileNode = local.file.node(filePath)
|
||||
if (fileNode) {
|
||||
local.context.add({
|
||||
type: "file",
|
||||
path: filePath,
|
||||
})
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Show when={local.context.all().length > 0 || local.context.active()}>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<Show when={local.context.active()}>
|
||||
<ActiveTabContextTag file={local.context.active()!} onClose={() => local.context.removeActive()} />
|
||||
</Show>
|
||||
<For each={local.context.all()}>
|
||||
{(file) => <FileTag file={file} onClose={() => local.context.remove(file.key)} />}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="relative">
|
||||
<textarea
|
||||
ref={(element) => {
|
||||
inputRef = element ?? undefined
|
||||
props.onInputRefChange?.(inputRef)
|
||||
}}
|
||||
value={state.promptInput}
|
||||
onInput={handlePromptInput}
|
||||
onKeyDown={handlePromptKeyDown}
|
||||
onClick={(event) =>
|
||||
queueMicrotask(() => {
|
||||
syncMentionFromCaret(event.currentTarget)
|
||||
})
|
||||
}
|
||||
onSelect={(event) =>
|
||||
queueMicrotask(() => {
|
||||
syncMentionFromCaret(event.currentTarget)
|
||||
})
|
||||
}
|
||||
onBlur={(event) => {
|
||||
const next = event.relatedTarget as HTMLElement | null
|
||||
if (next && next.closest('[data-mention-popover="true"]')) return
|
||||
closeMention()
|
||||
}}
|
||||
onScroll={handlePromptScroll}
|
||||
placeholder={placeholderText}
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck={false}
|
||||
class="relative w-full h-20 rounded-md px-0.5 resize-none overflow-y-auto
|
||||
bg-transparent text-transparent caret-text font-light text-base
|
||||
leading-relaxed focus:outline-none selection:bg-primary/20"
|
||||
></textarea>
|
||||
<div
|
||||
ref={(element) => {
|
||||
overlayContainerRef = element ?? undefined
|
||||
}}
|
||||
class="pointer-events-none absolute inset-0 overflow-hidden"
|
||||
>
|
||||
<PromptDisplayOverlay
|
||||
hasDisplaySegments={hasDisplaySegments()}
|
||||
displaySegments={displaySegments()}
|
||||
placeholder={placeholderText}
|
||||
renderAttachmentChip={renderAttachmentChip}
|
||||
renderTextSegment={renderTextSegment}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref={(element) => {
|
||||
mentionMeasureRef = element ?? undefined
|
||||
}}
|
||||
class="pointer-events-none invisible absolute inset-0 whitespace-pre-wrap text-base font-light leading-relaxed px-0.5"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<MentionSuggestions
|
||||
open={state.mentionOpen}
|
||||
anchor={state.mentionAnchorOffset}
|
||||
loading={mentionResults.loading}
|
||||
items={mentionItems()}
|
||||
activeIndex={state.mentionIndex}
|
||||
onHover={(index) => setState("mentionIndex", index)}
|
||||
onSelect={insertMention}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-xs text-text-muted">
|
||||
<div class="flex gap-2 items-center">
|
||||
<Select
|
||||
options={local.agent.list().map((agent) => agent.name)}
|
||||
current={local.agent.current().name}
|
||||
onSelect={local.agent.set}
|
||||
class="uppercase"
|
||||
/>
|
||||
<Button onClick={() => props.onOpenModelSelect()}>
|
||||
{local.model.current()?.name ?? "Select model"}
|
||||
<Icon name="chevron-down" size={24} class="text-text-muted" />
|
||||
</Button>
|
||||
<span class="text-text-muted/70 whitespace-nowrap">{local.model.current()?.provider.name}</span>
|
||||
</div>
|
||||
<div class="flex gap-1 items-center">
|
||||
<Show when={isSupported()}>
|
||||
<Tooltip value={isRecording() ? "Stop voice input" : "Start voice input"} placement="top">
|
||||
<IconButton
|
||||
onClick={async (event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
if (isRecording()) {
|
||||
stopSpeech()
|
||||
} else {
|
||||
startSpeech()
|
||||
}
|
||||
inputRef?.focus()
|
||||
}}
|
||||
classList={{
|
||||
"text-text-muted": !isRecording(),
|
||||
"text-error! animate-pulse": isRecording(),
|
||||
}}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
>
|
||||
<Icon name="mic" size={16} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<IconButton class="text-text-muted" size="xs" variant="ghost">
|
||||
<Icon name="photo" size={16} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
class="text-background-panel! bg-primary rounded-full! hover:bg-primary/90 ml-0.5"
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
type="submit"
|
||||
>
|
||||
<Icon name="arrow-up" size={14} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
const ActiveTabContextTag = (props: { file: LocalFile; onClose: () => void }) => (
|
||||
<div
|
||||
class="flex items-center bg-background group/tag
|
||||
border border-border-subtle/60 border-dashed
|
||||
rounded-md text-xs text-text-muted"
|
||||
>
|
||||
<IconButton class="text-text-muted" size="xs" variant="ghost" onClick={props.onClose}>
|
||||
<Icon name="file" class="group-hover/tag:hidden" size={12} />
|
||||
<Icon name="close" class="hidden group-hover/tag:block" size={12} />
|
||||
</IconButton>
|
||||
<div class="pr-1 flex gap-1 items-center">
|
||||
<span>{getFilename(props.file.path)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const FileTag = (props: { file: FileContext; onClose: () => void }) => (
|
||||
<div
|
||||
class="flex items-center bg-background group/tag
|
||||
border border-border-subtle/60
|
||||
rounded-md text-xs text-text-muted"
|
||||
>
|
||||
<IconButton class="text-text-muted" size="xs" variant="ghost" onClick={props.onClose}>
|
||||
<FileIcon node={props.file} class="group-hover/tag:hidden size-3!" />
|
||||
<Icon name="close" class="hidden group-hover/tag:block" size={12} />
|
||||
</IconButton>
|
||||
<div class="pr-1 flex gap-1 items-center">
|
||||
<span>{getFilename(props.file.path)}</span>
|
||||
<Show when={props.file.selection}>
|
||||
<span>
|
||||
({props.file.selection!.startLine}-{props.file.selection!.endLine})
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
function PromptDisplayOverlay(props: {
|
||||
hasDisplaySegments: boolean
|
||||
displaySegments: PromptDisplaySegment[]
|
||||
placeholder: string
|
||||
renderAttachmentChip: (part: PromptAttachmentPart, placeholder: string) => JSX.Element
|
||||
renderTextSegment: (value: string) => JSX.Element | undefined
|
||||
}) {
|
||||
return (
|
||||
<div class="px-0.5 text-base font-light leading-relaxed whitespace-pre-wrap text-left">
|
||||
<Show when={props.hasDisplaySegments} fallback={<span class="text-text-muted/70">{props.placeholder}</span>}>
|
||||
<For each={props.displaySegments}>
|
||||
{(segment) => {
|
||||
if (segment.kind === "text") {
|
||||
return props.renderTextSegment(segment.value)
|
||||
}
|
||||
if (segment.kind === "attachment") {
|
||||
return props.renderAttachmentChip(segment.part, segment.source)
|
||||
}
|
||||
return (
|
||||
<span class="text-text-muted/60 italic">
|
||||
{segment.leadingSpace ? ` ${segment.value}` : segment.value}
|
||||
</span>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MentionSuggestions(props: {
|
||||
open: boolean
|
||||
anchor: { x: number; y: number }
|
||||
loading: boolean
|
||||
items: string[]
|
||||
activeIndex: number
|
||||
onHover: (index: number) => void
|
||||
onSelect: (path: string) => void
|
||||
}) {
|
||||
return (
|
||||
<Popover open={props.open} modal={false} gutter={8} placement="bottom-start">
|
||||
<Popover.Trigger class="hidden" />
|
||||
<Popover.Anchor
|
||||
class="pointer-events-none absolute top-0 left-0 w-0 h-0"
|
||||
style={{ transform: `translate(${props.anchor.x}px, ${props.anchor.y}px)` }}
|
||||
/>
|
||||
<Popover.Portal>
|
||||
<Popover.Content
|
||||
data-mention-popover="true"
|
||||
class="z-50 w-72 max-h-60 overflow-y-auto rounded-md border border-border-subtle/40 bg-background-panel shadow-[0_10px_30px_rgba(0,0,0,0.35)] focus:outline-none"
|
||||
>
|
||||
<div class="py-1">
|
||||
<Show when={props.loading}>
|
||||
<div class="flex items-center gap-2 px-3 py-2 text-xs text-text-muted">
|
||||
<Icon name="refresh" size={12} class="animate-spin" />
|
||||
<span>Searching…</span>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={!props.loading && props.items.length === 0}>
|
||||
<div class="px-3 py-2 text-xs text-text-muted/80">No matching files</div>
|
||||
</Show>
|
||||
<For each={props.items}>
|
||||
{(path, indexAccessor) => {
|
||||
const index = indexAccessor()
|
||||
const dir = getDirectory(path)
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onMouseEnter={() => props.onHover(index)}
|
||||
onClick={() => props.onSelect(path)}
|
||||
class="w-full px-3 py-2 flex items-center gap-2 rounded-md text-left text-xs transition-colors"
|
||||
classList={{
|
||||
"bg-background-element text-text": index === props.activeIndex,
|
||||
"text-text-muted": index !== props.activeIndex,
|
||||
}}
|
||||
>
|
||||
<FileIcon node={{ path, type: "file" }} class="size-3 shrink-0" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<span class="truncate">{getFilename(path)}</span>
|
||||
{dir && <span class="truncate text-text-muted/70">{dir}</span>}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export type {
|
||||
PromptAttachmentPart,
|
||||
PromptAttachmentSegment,
|
||||
PromptContentPart,
|
||||
PromptDisplaySegment,
|
||||
PromptSubmitValue,
|
||||
} from "./prompt-form-helpers"
|
||||
@@ -1,63 +1,90 @@
|
||||
import { createEffect, on, Component, createMemo, Show } from "solid-js"
|
||||
import { Button, Icon, IconButton, Select, SelectDialog } from "@opencode-ai/ui"
|
||||
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
||||
import { createEffect, on, Component, createMemo, Show, For, onMount, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { FileIcon } from "@/ui"
|
||||
import { getDirectory, getFilename } from "@/utils"
|
||||
import { createFocusSignal } from "@solid-primitives/active-element"
|
||||
import { TextSelection, useLocal } from "@/context/local"
|
||||
import { DateTime } from "luxon"
|
||||
|
||||
interface TextPart {
|
||||
type: "text"
|
||||
interface PartBase {
|
||||
content: string
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
interface AttachmentPart {
|
||||
type: "attachment"
|
||||
fileId: string
|
||||
name: string
|
||||
export interface TextPart extends PartBase {
|
||||
type: "text"
|
||||
}
|
||||
|
||||
export type ContentPart = TextPart | AttachmentPart
|
||||
|
||||
export interface AttachmentToAdd {
|
||||
id: string
|
||||
name: string
|
||||
export interface FileAttachmentPart extends PartBase {
|
||||
type: "file"
|
||||
path: string
|
||||
selection?: TextSelection
|
||||
}
|
||||
|
||||
type AddAttachmentCallback = (attachment: AttachmentToAdd) => void
|
||||
|
||||
export interface PopoverState {
|
||||
isOpen: boolean
|
||||
searchQuery: string
|
||||
addAttachment: AddAttachmentCallback
|
||||
}
|
||||
export type ContentPart = TextPart | FileAttachmentPart
|
||||
|
||||
interface PromptInputProps {
|
||||
onSubmit: (parts: ContentPart[]) => void
|
||||
onShowAttachments?: (state: PopoverState | null) => void
|
||||
class?: string
|
||||
ref?: (el: HTMLDivElement) => void
|
||||
}
|
||||
|
||||
export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
let editorRef: HTMLDivElement | undefined
|
||||
const local = useLocal()
|
||||
let editorRef!: HTMLDivElement
|
||||
|
||||
const defaultParts = [{ type: "text", content: "" } as const]
|
||||
const defaultParts = [{ type: "text", content: "", start: 0, end: 0 } as const]
|
||||
const [store, setStore] = createStore<{
|
||||
contentParts: ContentPart[]
|
||||
popover: {
|
||||
isOpen: boolean
|
||||
searchQuery: string
|
||||
}
|
||||
popoverIsOpen: boolean
|
||||
}>({
|
||||
contentParts: defaultParts,
|
||||
popover: {
|
||||
isOpen: false,
|
||||
searchQuery: "",
|
||||
},
|
||||
popoverIsOpen: false,
|
||||
})
|
||||
|
||||
const isEmpty = createMemo(() => isEqual(store.contentParts, defaultParts))
|
||||
const isFocused = createFocusSignal(() => editorRef)
|
||||
|
||||
const handlePaste = (event: ClipboardEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
// @ts-expect-error
|
||||
const plainText = (event.clipboardData || window.clipboardData)?.getData("text/plain") ?? ""
|
||||
addPart({ type: "text", content: plainText, start: 0, end: 0 })
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
editorRef.addEventListener("paste", handlePaste)
|
||||
})
|
||||
onCleanup(() => {
|
||||
editorRef.removeEventListener("paste", handlePaste)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (isFocused()) {
|
||||
handleInput()
|
||||
} else {
|
||||
setStore("popoverIsOpen", false)
|
||||
}
|
||||
})
|
||||
|
||||
const { flat, active, onInput, onKeyDown } = useFilteredList<string>({
|
||||
items: local.file.search,
|
||||
key: (x) => x,
|
||||
onSelect: (path) => {
|
||||
if (!path) return
|
||||
addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
|
||||
setStore("popoverIsOpen", false)
|
||||
},
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => store.contentParts,
|
||||
(currentParts) => {
|
||||
if (!editorRef) return
|
||||
const domParts = parseFromDOM()
|
||||
if (isEqual(currentParts, domParts)) return
|
||||
|
||||
@@ -70,14 +97,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
editorRef.innerHTML = ""
|
||||
currentParts.forEach((part) => {
|
||||
if (part.type === "text") {
|
||||
editorRef!.appendChild(document.createTextNode(part.content))
|
||||
} else if (part.type === "attachment") {
|
||||
editorRef.appendChild(document.createTextNode(part.content))
|
||||
} else if (part.type === "file") {
|
||||
const pill = document.createElement("span")
|
||||
pill.textContent = `@${part.name}`
|
||||
pill.className = "attachment-pill"
|
||||
pill.setAttribute("data-file-id", part.fileId)
|
||||
pill.textContent = part.content
|
||||
pill.setAttribute("data-type", "file")
|
||||
pill.setAttribute("data-path", part.path)
|
||||
pill.setAttribute("contenteditable", "false")
|
||||
editorRef!.appendChild(pill)
|
||||
pill.style.userSelect = "text"
|
||||
pill.style.cursor = "default"
|
||||
editorRef.appendChild(pill)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -88,30 +117,32 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
),
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
if (store.popover.isOpen) {
|
||||
props.onShowAttachments?.({
|
||||
isOpen: true,
|
||||
searchQuery: store.popover.searchQuery,
|
||||
addAttachment: addAttachment,
|
||||
})
|
||||
} else {
|
||||
props.onShowAttachments?.(null)
|
||||
}
|
||||
})
|
||||
|
||||
const parseFromDOM = (): ContentPart[] => {
|
||||
if (!editorRef) return []
|
||||
const newParts: ContentPart[] = []
|
||||
let position = 0
|
||||
editorRef.childNodes.forEach((node) => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
if (node.textContent) newParts.push({ type: "text", content: node.textContent })
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.fileId) {
|
||||
newParts.push({
|
||||
type: "attachment",
|
||||
fileId: (node as HTMLElement).dataset.fileId!,
|
||||
name: node.textContent!.substring(1),
|
||||
})
|
||||
if (node.textContent) {
|
||||
const content = node.textContent
|
||||
newParts.push({ type: "text", content, start: position, end: position + content.length })
|
||||
position += content.length
|
||||
}
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type) {
|
||||
switch ((node as HTMLElement).dataset.type) {
|
||||
case "file":
|
||||
const content = node.textContent!
|
||||
newParts.push({
|
||||
type: "file",
|
||||
path: (node as HTMLElement).dataset.path!,
|
||||
content,
|
||||
start: position,
|
||||
end: position + content.length,
|
||||
})
|
||||
position += content.length
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
if (newParts.length === 0) newParts.push(...defaultParts)
|
||||
@@ -120,96 +151,265 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const handleInput = () => {
|
||||
const rawParts = parseFromDOM()
|
||||
const cursorPosition = getCursorPosition(editorRef!)
|
||||
const rawText = rawParts.map((p) => (p.type === "text" ? p.content : `@${p.name}`)).join("")
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const rawText = rawParts.map((p) => p.content).join("")
|
||||
|
||||
const atMatch = rawText.substring(0, cursorPosition).match(/@(\S*)$/)
|
||||
if (atMatch) {
|
||||
setStore("popover", { isOpen: true, searchQuery: atMatch[1] })
|
||||
} else if (store.popover.isOpen) {
|
||||
setStore("popover", "isOpen", false)
|
||||
onInput(atMatch[1])
|
||||
setStore("popoverIsOpen", true)
|
||||
} else if (store.popoverIsOpen) {
|
||||
setStore("popoverIsOpen", false)
|
||||
}
|
||||
|
||||
setStore("contentParts", rawParts)
|
||||
}
|
||||
|
||||
const addAttachment: AddAttachmentCallback = (attachment) => {
|
||||
const rawText = store.contentParts.map((p) => (p.type === "text" ? p.content : `@${p.name}`)).join("")
|
||||
const cursorPosition = getCursorPosition(editorRef!)
|
||||
|
||||
const addPart = (part: ContentPart) => {
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const rawText = store.contentParts.map((p) => p.content).join("")
|
||||
const textBeforeCursor = rawText.substring(0, cursorPosition)
|
||||
const atMatch = textBeforeCursor.match(/@(\S*)$/)
|
||||
|
||||
if (!atMatch) return
|
||||
const startIndex = atMatch ? atMatch.index! : cursorPosition
|
||||
const endIndex = atMatch ? cursorPosition : cursorPosition
|
||||
|
||||
const startIndex = atMatch.index!
|
||||
const pushText = (acc: { parts: ContentPart[]; runningIndex: number }, value: string) => {
|
||||
if (!value) return
|
||||
const last = acc.parts[acc.parts.length - 1]
|
||||
if (last && last.type === "text") {
|
||||
acc.parts[acc.parts.length - 1] = {
|
||||
type: "text",
|
||||
content: last.content + value,
|
||||
start: last.start,
|
||||
end: last.end + value.length,
|
||||
}
|
||||
return
|
||||
}
|
||||
acc.parts.push({ type: "text", content: value, start: acc.runningIndex, end: acc.runningIndex + value.length })
|
||||
}
|
||||
|
||||
// Create new structured content
|
||||
const newParts: ContentPart[] = []
|
||||
const textBeforeTrigger = rawText.substring(0, startIndex)
|
||||
if (textBeforeTrigger) newParts.push({ type: "text", content: textBeforeTrigger })
|
||||
const {
|
||||
parts: nextParts,
|
||||
inserted,
|
||||
cursorPositionAfter,
|
||||
} = store.contentParts.reduce(
|
||||
(acc, item) => {
|
||||
if (acc.inserted) {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex += item.content.length
|
||||
return acc
|
||||
}
|
||||
|
||||
newParts.push({ type: "attachment", fileId: attachment.id, name: attachment.name })
|
||||
const nextIndex = acc.runningIndex + item.content.length
|
||||
if (nextIndex <= startIndex) {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex = nextIndex
|
||||
return acc
|
||||
}
|
||||
|
||||
// Add a space after the pill for better UX
|
||||
newParts.push({ type: "text", content: " " })
|
||||
if (item.type !== "text") {
|
||||
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
|
||||
acc.runningIndex = nextIndex
|
||||
return acc
|
||||
}
|
||||
|
||||
const textAfterCursor = rawText.substring(cursorPosition)
|
||||
if (textAfterCursor) newParts.push({ type: "text", content: textAfterCursor })
|
||||
const headLength = Math.max(0, startIndex - acc.runningIndex)
|
||||
const tailLength = Math.max(0, endIndex - acc.runningIndex)
|
||||
const head = item.content.slice(0, headLength)
|
||||
const tail = item.content.slice(tailLength)
|
||||
|
||||
setStore("contentParts", newParts)
|
||||
setStore("popover", "isOpen", false)
|
||||
pushText(acc, head)
|
||||
acc.runningIndex += head.length
|
||||
|
||||
// Set cursor position after the newly added pill + space
|
||||
// We need to wait for the DOM to update
|
||||
queueMicrotask(() => {
|
||||
setCursorPosition(editorRef!, textBeforeTrigger.length + 1 + attachment.name.length + 1)
|
||||
})
|
||||
}
|
||||
if (part.type === "text") {
|
||||
pushText(acc, part.content)
|
||||
acc.runningIndex += part.content.length
|
||||
}
|
||||
if (part.type !== "text") {
|
||||
acc.parts.push({ ...part, start: acc.runningIndex, end: acc.runningIndex + part.content.length })
|
||||
acc.runningIndex += part.content.length
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (store.popover.isOpen && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter")) {
|
||||
// In a real implementation, you'd prevent default and delegate this to the popover
|
||||
console.log("Key press delegated to popover:", event.key)
|
||||
event.preventDefault()
|
||||
const needsGap = Boolean(atMatch)
|
||||
const rest = needsGap ? (tail ? (/^\s/.test(tail) ? tail : ` ${tail}`) : " ") : tail
|
||||
pushText(acc, rest)
|
||||
acc.runningIndex += rest.length
|
||||
|
||||
const baseCursor = startIndex + part.content.length
|
||||
const cursorAddition = needsGap && rest.length > 0 ? 1 : 0
|
||||
acc.cursorPositionAfter = baseCursor + cursorAddition
|
||||
|
||||
acc.inserted = true
|
||||
return acc
|
||||
},
|
||||
{
|
||||
parts: [] as ContentPart[],
|
||||
runningIndex: 0,
|
||||
inserted: false,
|
||||
cursorPositionAfter: cursorPosition + part.content.length,
|
||||
},
|
||||
)
|
||||
|
||||
if (!inserted) {
|
||||
const baseParts = store.contentParts.filter((item) => !(item.type === "text" && item.content === ""))
|
||||
const runningIndex = baseParts.reduce((sum, p) => sum + p.content.length, 0)
|
||||
const appendedAcc = { parts: [...baseParts] as ContentPart[], runningIndex }
|
||||
if (part.type === "text") {
|
||||
pushText(appendedAcc, part.content)
|
||||
}
|
||||
if (part.type !== "text") {
|
||||
appendedAcc.parts.push({
|
||||
...part,
|
||||
start: appendedAcc.runningIndex,
|
||||
end: appendedAcc.runningIndex + part.content.length,
|
||||
})
|
||||
}
|
||||
const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : defaultParts
|
||||
setStore("contentParts", next)
|
||||
setStore("popoverIsOpen", false)
|
||||
const nextCursor = rawText.length + part.content.length
|
||||
queueMicrotask(() => setCursorPosition(editorRef, nextCursor))
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
setStore("contentParts", nextParts)
|
||||
setStore("popoverIsOpen", false)
|
||||
|
||||
queueMicrotask(() => setCursorPosition(editorRef, cursorPositionAfter))
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (store.popoverIsOpen && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter")) {
|
||||
onKeyDown(event)
|
||||
event.preventDefault()
|
||||
if (store.contentParts.length > 0) {
|
||||
props.onSubmit([...store.contentParts])
|
||||
setStore("contentParts", defaultParts)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
handleSubmit(event)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = (event: Event) => {
|
||||
event.preventDefault()
|
||||
if (store.contentParts.length > 0) {
|
||||
props.onSubmit([...store.contentParts])
|
||||
setStore("contentParts", defaultParts)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
classList={{
|
||||
"size-full max-w-xl bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
|
||||
"rounded-2xl overflow-clip focus-within:shadow-xs-border-selected": true,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
<div class="p-3" />
|
||||
<div class="relative">
|
||||
<div
|
||||
ref={editorRef}
|
||||
contenteditable="true"
|
||||
onInput={handleInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
classList={{
|
||||
"w-full p-3 text-sm focus:outline-none": true,
|
||||
}}
|
||||
/>
|
||||
<Show when={isEmpty()}>
|
||||
<div class="absolute bottom-0 left-0 p-3 text-sm text-text-weak pointer-events-none">
|
||||
Plan and build anything
|
||||
<div class="relative size-full _max-h-[320px] flex flex-col gap-3">
|
||||
<Show when={store.popoverIsOpen}>
|
||||
<div class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-[252px] min-h-10 overflow-y-auto flex flex-col p-2 pb-0 rounded-2xl border border-border-base bg-surface-raised-stronger-non-alpha shadow-md">
|
||||
<For each={flat()}>
|
||||
{(i) => (
|
||||
<div
|
||||
classList={{
|
||||
"w-full flex items-center justify-between rounded-md": true,
|
||||
"bg-surface-raised-base-hover": active() === i,
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center gap-x-2 grow min-w-0">
|
||||
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
|
||||
<div class="flex items-center text-14-regular">
|
||||
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
||||
{getDirectory(i)}
|
||||
</span>
|
||||
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
classList={{
|
||||
"bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
|
||||
"rounded-2xl overflow-clip focus-within:shadow-xs-border-selected": true,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
<div class="relative max-h-[240px] overflow-y-auto">
|
||||
<div
|
||||
ref={(el) => {
|
||||
editorRef = el
|
||||
props.ref?.(el)
|
||||
}}
|
||||
contenteditable="true"
|
||||
onInput={handleInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
classList={{
|
||||
"w-full p-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
||||
"[&>[data-type=file]]:text-icon-info-active": true,
|
||||
}}
|
||||
/>
|
||||
<Show when={isEmpty()}>
|
||||
<div class="absolute top-0 left-0 p-3 text-14-regular text-text-weak pointer-events-none">
|
||||
Plan and build anything
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="p-3 flex items-center justify-between">
|
||||
<div class="flex items-center justify-start gap-1">
|
||||
<Select
|
||||
options={local.agent.list().map((agent) => agent.name)}
|
||||
current={local.agent.current().name}
|
||||
onSelect={local.agent.set}
|
||||
class="capitalize"
|
||||
/>
|
||||
<SelectDialog
|
||||
title="Select model"
|
||||
placeholder="Search models"
|
||||
emptyMessage="No model results"
|
||||
key={(x) => `${x.provider.id}:${x.id}`}
|
||||
items={local.model.list()}
|
||||
current={local.model.current()}
|
||||
filterKeys={["provider.name", "name", "id"]}
|
||||
groupBy={(x) => x.provider.name}
|
||||
sortGroupsBy={(a, b) => {
|
||||
const order = ["opencode", "anthropic", "github-copilot", "openai", "google", "openrouter", "vercel"]
|
||||
const aProvider = a.items[0].provider.id
|
||||
const bProvider = b.items[0].provider.id
|
||||
if (order.includes(aProvider) && !order.includes(bProvider)) return -1
|
||||
if (!order.includes(aProvider) && order.includes(bProvider)) return 1
|
||||
return order.indexOf(aProvider) - order.indexOf(bProvider)
|
||||
}}
|
||||
onSelect={(x) => local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined)}
|
||||
trigger={
|
||||
<Button as="div" variant="ghost">
|
||||
{local.model.current()?.name ?? "Select model"}
|
||||
<span class="ml-0.5 text-text-weak text-12-regular">{local.model.current()?.provider.name}</span>
|
||||
<Icon name="chevron-down" size="small" />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{(i) => (
|
||||
<div class="w-full flex items-center justify-between gap-x-3">
|
||||
<div class="flex items-center gap-x-2.5 text-text-muted grow min-w-0">
|
||||
<img src={`https://models.dev/logos/${i.provider.id}.svg`} class="size-6 p-0.5 shrink-0 " />
|
||||
<div class="flex gap-x-3 items-baseline flex-[1_0_0]">
|
||||
<span class="text-14-medium text-text-strong overflow-hidden text-ellipsis">{i.name}</span>
|
||||
<Show when={i.release_date}>
|
||||
<span class="text-12-medium text-text-weak overflow-hidden text-ellipsis truncate min-w-0">
|
||||
{DateTime.fromFormat(i.release_date, "yyyy-MM-dd").toFormat("LLL yyyy")}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={!i.cost || i.cost?.input === 0}>
|
||||
<div class="overflow-hidden text-12-medium text-text-strong">Free</div>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</SelectDialog>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="p-3" />
|
||||
<IconButton type="submit" disabled={isEmpty()} icon="arrow-up" variant="primary" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -223,7 +423,7 @@ function isEqual(arrA: ContentPart[], arrB: ContentPart[]): boolean {
|
||||
if (partA.type === "text" && partA.content !== (partB as TextPart).content) {
|
||||
return false
|
||||
}
|
||||
if (partA.type === "attachment" && partA.fileId !== (partB as AttachmentPart).fileId) {
|
||||
if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -241,24 +441,48 @@ function getCursorPosition(parent: HTMLElement): number {
|
||||
}
|
||||
|
||||
function setCursorPosition(parent: HTMLElement, position: number) {
|
||||
let child = parent.firstChild
|
||||
let offset = position
|
||||
while (child) {
|
||||
if (offset > child.textContent!.length) {
|
||||
offset -= child.textContent!.length
|
||||
child = child.nextSibling
|
||||
} else {
|
||||
try {
|
||||
const range = document.createRange()
|
||||
const sel = window.getSelection()
|
||||
range.setStart(child, offset)
|
||||
range.collapse(true)
|
||||
sel?.removeAllRanges()
|
||||
sel?.addRange(range)
|
||||
} catch (e) {
|
||||
console.error("Failed to set cursor position.", e)
|
||||
}
|
||||
let remaining = position
|
||||
let node = parent.firstChild
|
||||
while (node) {
|
||||
const length = node.textContent ? node.textContent.length : 0
|
||||
const isText = node.nodeType === Node.TEXT_NODE
|
||||
const isFile = node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type === "file"
|
||||
|
||||
if (isText && remaining <= length) {
|
||||
const range = document.createRange()
|
||||
const selection = window.getSelection()
|
||||
range.setStart(node, remaining)
|
||||
range.collapse(true)
|
||||
selection?.removeAllRanges()
|
||||
selection?.addRange(range)
|
||||
return
|
||||
}
|
||||
|
||||
if (isFile && remaining <= length) {
|
||||
const range = document.createRange()
|
||||
const selection = window.getSelection()
|
||||
range.setStartAfter(node)
|
||||
range.collapse(true)
|
||||
selection?.removeAllRanges()
|
||||
selection?.addRange(range)
|
||||
return
|
||||
}
|
||||
|
||||
remaining -= length
|
||||
node = node.nextSibling
|
||||
}
|
||||
|
||||
const fallbackRange = document.createRange()
|
||||
const fallbackSelection = window.getSelection()
|
||||
const last = parent.lastChild
|
||||
if (last && last.nodeType === Node.TEXT_NODE) {
|
||||
const len = last.textContent ? last.textContent.length : 0
|
||||
fallbackRange.setStart(last, len)
|
||||
}
|
||||
if (!last || last.nodeType !== Node.TEXT_NODE) {
|
||||
fallbackRange.selectNodeContents(parent)
|
||||
}
|
||||
fallbackRange.collapse(false)
|
||||
fallbackSelection?.removeAllRanges()
|
||||
fallbackSelection?.addRange(fallbackRange)
|
||||
}
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
import { createEffect, Show, For, createMemo, type JSX, createResource } from "solid-js"
|
||||
import { Dialog } from "@kobalte/core/dialog"
|
||||
import { Icon } from "@opencode-ai/ui"
|
||||
import { IconButton } from "@/ui"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { entries, flatMap, groupBy, map, pipe } from "remeda"
|
||||
import { createList } from "solid-list"
|
||||
import fuzzysort from "fuzzysort"
|
||||
|
||||
interface SelectDialogProps<T> {
|
||||
items: T[] | ((filter: string) => Promise<T[]>)
|
||||
key: (item: T) => string
|
||||
render: (item: T) => JSX.Element
|
||||
filter?: string[]
|
||||
current?: T
|
||||
placeholder?: string
|
||||
groupBy?: (x: T) => string
|
||||
onSelect?: (value: T | undefined) => void
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
export function SelectDialog<T>(props: SelectDialogProps<T>) {
|
||||
let scrollRef: HTMLDivElement | undefined
|
||||
const [store, setStore] = createStore({
|
||||
filter: "",
|
||||
mouseActive: false,
|
||||
})
|
||||
|
||||
const [grouped] = createResource(
|
||||
() => store.filter,
|
||||
async (filter) => {
|
||||
const needle = filter.toLowerCase()
|
||||
const all = (typeof props.items === "function" ? await props.items(needle) : props.items) || []
|
||||
const result = pipe(
|
||||
all,
|
||||
(x) => {
|
||||
if (!needle) return x
|
||||
if (!props.filter && Array.isArray(x) && x.every((e) => typeof e === "string")) {
|
||||
return fuzzysort.go(needle, x).map((x) => x.target) as T[]
|
||||
}
|
||||
return fuzzysort.go(needle, x, { keys: props.filter! }).map((x) => x.obj)
|
||||
},
|
||||
groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
|
||||
// mapValues((x) => x.sort((a, b) => props.key(a).localeCompare(props.key(b)))),
|
||||
entries(),
|
||||
map(([k, v]) => ({ category: k, items: v })),
|
||||
)
|
||||
return result
|
||||
},
|
||||
)
|
||||
const flat = createMemo(() => {
|
||||
return pipe(
|
||||
grouped() || [],
|
||||
flatMap((x) => x.items),
|
||||
)
|
||||
})
|
||||
const list = createList({
|
||||
items: () => flat().map(props.key),
|
||||
initialActive: props.current ? props.key(props.current) : undefined,
|
||||
loop: true,
|
||||
})
|
||||
const resetSelection = () => {
|
||||
const all = flat()
|
||||
if (all.length === 0) return
|
||||
list.setActive(props.key(all[0]))
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
store.filter
|
||||
scrollRef?.scrollTo(0, 0)
|
||||
resetSelection()
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const all = flat()
|
||||
if (store.mouseActive || all.length === 0) return
|
||||
if (list.active() === props.key(all[0])) {
|
||||
scrollRef?.scrollTo(0, 0)
|
||||
return
|
||||
}
|
||||
const element = scrollRef?.querySelector(`[data-key="${list.active()}"]`)
|
||||
element?.scrollIntoView({ block: "nearest", behavior: "smooth" })
|
||||
})
|
||||
|
||||
const handleInput = (value: string) => {
|
||||
setStore("filter", value)
|
||||
resetSelection()
|
||||
}
|
||||
|
||||
const handleSelect = (item: T) => {
|
||||
props.onSelect?.(item)
|
||||
props.onClose?.()
|
||||
}
|
||||
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
setStore("mouseActive", false)
|
||||
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
const selected = flat().find((x) => props.key(x) === list.active())
|
||||
if (selected) handleSelect(selected)
|
||||
} else if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
props.onClose?.()
|
||||
} else {
|
||||
list.onKeyDown(e)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog defaultOpen modal onOpenChange={(open) => open || props.onClose?.()}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay class="fixed inset-0 bg-black/50 backdrop-blur-sm z-[100]" />
|
||||
<Dialog.Content
|
||||
class="fixed top-[20%] left-1/2 -translate-x-1/2 w-[90vw] max-w-2xl
|
||||
shadow-[0_0_33px_rgba(0,0,0,0.8)]
|
||||
bg-background border border-border-subtle/30 rounded-lg z-[101]
|
||||
max-h-[60vh] flex flex-col"
|
||||
>
|
||||
<div class="border-b border-border-subtle/30">
|
||||
<div class="relative">
|
||||
<Icon name="command" size={16} class="absolute left-3 top-1/2 -translate-y-1/2 text-text-muted/80" />
|
||||
<input
|
||||
type="text"
|
||||
value={store.filter}
|
||||
onInput={(e) => handleInput(e.currentTarget.value)}
|
||||
onKeyDown={handleKey}
|
||||
placeholder={props.placeholder}
|
||||
class="w-full pl-10 pr-4 py-2 rounded-t-md
|
||||
text-sm text-text placeholder-text-muted/70
|
||||
focus:outline-none"
|
||||
autofocus
|
||||
spellcheck={false}
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
/>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2">
|
||||
{/* <Show when={fileResults.loading && mode() === "files"}>
|
||||
<div class="text-text-muted">
|
||||
<Icon name="refresh" size={14} class="animate-spin" />
|
||||
</div>
|
||||
</Show> */}
|
||||
<Show when={store.filter}>
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
class="text-text-muted hover:text-text"
|
||||
onClick={() => {
|
||||
setStore("filter", "")
|
||||
resetSelection()
|
||||
}}
|
||||
>
|
||||
<Icon name="close" size={14} />
|
||||
</IconButton>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={(el) => (scrollRef = el)} class="relative flex-1 overflow-y-auto">
|
||||
<Show
|
||||
when={flat().length > 0}
|
||||
fallback={<div class="text-center py-8 text-text-muted text-sm">No results</div>}
|
||||
>
|
||||
<For each={grouped()}>
|
||||
{(group) => (
|
||||
<>
|
||||
<Show when={group.category}>
|
||||
<div class="top-0 sticky z-10 bg-background-panel p-2 text-xs text-text-muted/60 tracking-wider uppercase">
|
||||
{group.category}
|
||||
</div>
|
||||
</Show>
|
||||
<div class="p-2">
|
||||
<For each={group.items}>
|
||||
{(item) => (
|
||||
<button
|
||||
data-key={props.key(item)}
|
||||
onClick={() => handleSelect(item)}
|
||||
onMouseMove={() => {
|
||||
setStore("mouseActive", true)
|
||||
list.setActive(props.key(item))
|
||||
}}
|
||||
classList={{
|
||||
"w-full px-3 py-2 flex items-center gap-3": true,
|
||||
"rounded-md text-left transition-colors group": true,
|
||||
"bg-background-element": props.key(item) === list.active(),
|
||||
}}
|
||||
>
|
||||
{props.render(item)}
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="p-3 border-t border-border-subtle/30 flex items-center justify-between text-xs text-text-muted">
|
||||
<div class="flex items-center gap-5">
|
||||
<span class="flex items-center gap-1.5">
|
||||
<kbd class="px-1.5 py-0.5 bg-background-element border border-border-subtle/30 rounded text-[10px]">
|
||||
↑↓
|
||||
</kbd>
|
||||
Navigate
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<kbd class="px-1.5 py-0.5 bg-background-element border border-border-subtle/30 rounded text-[10px]">
|
||||
↵
|
||||
</kbd>
|
||||
Select
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<kbd class="px-1.5 py-0.5 bg-background-element border border-border-subtle/30 rounded text-[10px]">
|
||||
ESC
|
||||
</kbd>
|
||||
Close
|
||||
</span>
|
||||
</div>
|
||||
<span>{`${flat().length} results`}</span>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useLocal, useSync } from "@/context"
|
||||
import { Icon, Tooltip } from "@opencode-ai/ui"
|
||||
import { Collapsible } from "@/ui"
|
||||
import type { AssistantMessage, Part, ToolPart } from "@opencode-ai/sdk"
|
||||
import type { AssistantMessage, Message, Part, ToolPart } from "@opencode-ai/sdk"
|
||||
import { DateTime } from "luxon"
|
||||
import {
|
||||
createSignal,
|
||||
onMount,
|
||||
For,
|
||||
Match,
|
||||
splitProps,
|
||||
@@ -23,6 +21,8 @@ import { createElementSize } from "@solid-primitives/resize-observer"
|
||||
import { createScrollPosition } from "@solid-primitives/scroll"
|
||||
import { ProgressCircle } from "./progress-circle"
|
||||
import { pipe, sumBy } from "remeda"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useLocal } from "@/context/local"
|
||||
|
||||
function Part(props: ParentProps & ComponentProps<"div">) {
|
||||
const [local, others] = splitProps(props, ["class", "classList", "children"])
|
||||
@@ -67,7 +67,7 @@ function ReadToolPart(props: { part: ToolPart }) {
|
||||
{(state) => {
|
||||
const path = state().input["filePath"] as string
|
||||
return (
|
||||
<Part class="cursor-pointer" onClick={() => local.file.open(path)}>
|
||||
<Part onClick={() => local.file.open(path)}>
|
||||
<span class="">Read</span> {getFilename(path)}
|
||||
</Part>
|
||||
)
|
||||
@@ -253,7 +253,7 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
case "patch":
|
||||
return false
|
||||
case "text":
|
||||
return !part.synthetic
|
||||
return !part.synthetic && part.text.trim()
|
||||
case "reasoning":
|
||||
return part.text.trim()
|
||||
case "tool":
|
||||
@@ -270,8 +270,17 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
}
|
||||
}
|
||||
|
||||
const hasValidParts = (message: Message) => {
|
||||
return sync.data.part[message.id]?.filter(valid).length > 0
|
||||
}
|
||||
|
||||
const hasTextPart = (message: Message) => {
|
||||
return !!sync.data.part[message.id]?.filter(valid).find((p) => p.type === "text")
|
||||
}
|
||||
|
||||
const session = createMemo(() => sync.session.get(props.session))
|
||||
const messages = createMemo(() => sync.data.message[props.session] ?? [])
|
||||
const messagesWithValidParts = createMemo(() => sync.data.message[props.session]?.filter(hasValidParts) ?? [])
|
||||
const working = createMemo(() => {
|
||||
const last = messages()[messages().length - 1]
|
||||
if (!last) return false
|
||||
@@ -378,6 +387,11 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
}
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
console.log("WHAT")
|
||||
console.log(JSON.stringify(messagesWithValidParts()))
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setRoot}
|
||||
@@ -386,7 +400,7 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
<div class="py-1.5 px-10 flex justify-end items-center self-stretch">
|
||||
<div class="flex justify-end items-center self-stretch">
|
||||
<div class="flex items-center gap-6">
|
||||
<Tooltip value={`${tokens()} Tokens`} class="flex items-center gap-1.5">
|
||||
<Show when={context()}>
|
||||
@@ -397,11 +411,16 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
<div class="text-14-regular text-text-strong text-right">{cost()}</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul role="list" class="flex flex-col gap-6 items-start self-stretch px-10 pt-2 pb-6">
|
||||
<For each={messages()}>
|
||||
<ul role="list" class="flex flex-col items-start self-stretch">
|
||||
<For each={messagesWithValidParts()}>
|
||||
{(message) => (
|
||||
<div class="flex flex-col gap-1 justify-center items-start self-stretch">
|
||||
<For each={sync.data.part[message.id]?.filter(valid)}>
|
||||
<div
|
||||
classList={{
|
||||
"flex flex-col gap-1 justify-center items-start self-stretch": true,
|
||||
"mt-6": hasTextPart(message),
|
||||
}}
|
||||
>
|
||||
<For each={sync.data.part[message.id]?.filter(valid) ?? []}>
|
||||
{(part) => (
|
||||
<li class="group/li">
|
||||
<Switch fallback={<div class="">{part.type}</div>}>
|
||||
@@ -449,9 +468,9 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
<Collapsible defaultOpen={false}>
|
||||
<Collapsible.Trigger>
|
||||
<div class="mt-12 ml-1 flex items-center gap-x-2 text-xs text-text-muted">
|
||||
<Icon name="file-code" size={16} />
|
||||
<Icon name="file-code" />
|
||||
<span>Raw Session Data</span>
|
||||
<Collapsible.Arrow size={18} class="text-text-muted" />
|
||||
<Collapsible.Arrow class="text-text-muted" />
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content class="mt-5">
|
||||
@@ -460,9 +479,9 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
<Collapsible>
|
||||
<Collapsible.Trigger>
|
||||
<div class="flex items-center gap-x-2 text-xs text-text-muted ml-1">
|
||||
<Icon name="file-code" size={16} />
|
||||
<Icon name="file-code" />
|
||||
<span>session</span>
|
||||
<Collapsible.Arrow size={18} class="text-text-muted" />
|
||||
<Collapsible.Arrow class="text-text-muted" />
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
@@ -477,9 +496,9 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
<Collapsible>
|
||||
<Collapsible.Trigger>
|
||||
<div class="flex items-center gap-x-2 text-xs text-text-muted ml-1">
|
||||
<Icon name="file-code" size={16} />
|
||||
<Icon name="file-code" />
|
||||
<span>{message.role === "user" ? "user" : "assistant"}</span>
|
||||
<Collapsible.Arrow size={18} class="text-text-muted" />
|
||||
<Collapsible.Arrow class="text-text-muted" />
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
@@ -493,9 +512,9 @@ export default function SessionTimeline(props: { session: string; class?: string
|
||||
<Collapsible>
|
||||
<Collapsible.Trigger>
|
||||
<div class="flex items-center gap-x-2 text-xs text-text-muted ml-1">
|
||||
<Icon name="file-code" size={16} />
|
||||
<Icon name="file-code" />
|
||||
<span>{part.type}</span>
|
||||
<Collapsible.Arrow size={18} class="text-text-muted" />
|
||||
<Collapsible.Arrow class="text-text-muted" />
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
{
|
||||
"colors": {
|
||||
"actionBar.toggledBackground": "var(--surface-raised-base)",
|
||||
"activityBarBadge.background": "var(--surface-brand-base)",
|
||||
"checkbox.border": "var(--border-base)",
|
||||
"editor.background": "transparent",
|
||||
"editor.foreground": "var(--text-base)",
|
||||
"editor.inactiveSelectionBackground": "var(--surface-raised-base)",
|
||||
"editor.selectionHighlightBackground": "var(--border-active)",
|
||||
"editorIndentGuide.activeBackground1": "var(--border-weak-base)",
|
||||
"editorIndentGuide.background1": "var(--border-weak-base)",
|
||||
"input.placeholderForeground": "var(--text-weak)",
|
||||
"list.activeSelectionIconForeground": "var(--text-base)",
|
||||
"list.dropBackground": "var(--surface-raised-base)",
|
||||
"menu.background": "var(--surface-base)",
|
||||
"menu.border": "var(--border-base)",
|
||||
"menu.foreground": "var(--text-base)",
|
||||
"menu.selectionBackground": "var(--surface-interactive-base)",
|
||||
"menu.separatorBackground": "var(--border-base)",
|
||||
"ports.iconRunningProcessForeground": "var(--icon-success-base)",
|
||||
"sideBarSectionHeader.background": "transparent",
|
||||
"sideBarSectionHeader.border": "var(--border-weak-base)",
|
||||
"sideBarTitle.foreground": "var(--text-weak)",
|
||||
"statusBarItem.remoteBackground": "var(--surface-success-base)",
|
||||
"statusBarItem.remoteForeground": "var(--text-base)",
|
||||
"tab.lastPinnedBorder": "var(--border-weak-base)",
|
||||
"tab.selectedBackground": "var(--surface-raised-base)",
|
||||
"tab.selectedForeground": "var(--text-weak)",
|
||||
"terminal.inactiveSelectionBackground": "var(--surface-raised-base)",
|
||||
"widget.border": "var(--border-base)"
|
||||
},
|
||||
"displayName": "opencode",
|
||||
"name": "opencode",
|
||||
"semanticHighlighting": true,
|
||||
"semanticTokenColors": {
|
||||
"customLiteral": "var(--syntax-function)",
|
||||
"newOperator": "var(--syntax-operator)",
|
||||
"numberLiteral": "var(--syntax-number)",
|
||||
"stringLiteral": "var(--syntax-string)"
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
"scope": [
|
||||
"meta.embedded",
|
||||
"source.groovy.embedded",
|
||||
"string meta.image.inline.markdown",
|
||||
"variable.legacy.builtin.python"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--text-base)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "emphasis",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "strong",
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "header",
|
||||
"settings": {
|
||||
"foreground": "var(--markdown-heading)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "comment",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-comment)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.language",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.numeric",
|
||||
"variable.other.enummember",
|
||||
"keyword.operator.plus.exponent",
|
||||
"keyword.operator.minus.exponent"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-number)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.regexp",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.tag",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["entity.name.tag.css", "entity.name.tag.less"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.other.attribute-name",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-variable)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.other.attribute-name.class.css",
|
||||
"source.css entity.other.attribute-name.class",
|
||||
"entity.other.attribute-name.id.css",
|
||||
"entity.other.attribute-name.parent-selector.css",
|
||||
"entity.other.attribute-name.parent.less",
|
||||
"source.css entity.other.attribute-name.pseudo-class",
|
||||
"entity.other.attribute-name.pseudo-element.css",
|
||||
"source.css.less entity.other.attribute-name.id",
|
||||
"entity.other.attribute-name.scss"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "invalid",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-critical)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.underline",
|
||||
"settings": {
|
||||
"fontStyle": "underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.bold",
|
||||
"settings": {
|
||||
"fontStyle": "bold",
|
||||
"foreground": "var(--markdown-strong)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.heading",
|
||||
"settings": {
|
||||
"fontStyle": "bold",
|
||||
"foreground": "var(--theme-markdown-heading)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.italic",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.strikethrough",
|
||||
"settings": {
|
||||
"fontStyle": "strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inserted",
|
||||
"settings": {
|
||||
"foreground": "var(--text-diff-add-base)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.deleted",
|
||||
"settings": {
|
||||
"foreground": "var(--text-diff-delete-base)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.changed",
|
||||
"settings": {
|
||||
"foreground": "var(--text-base)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "punctuation.definition.quote.begin.markdown",
|
||||
"settings": {
|
||||
"foreground": "var(--markdown-block-quote)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "punctuation.definition.list.begin.markdown",
|
||||
"settings": {
|
||||
"foreground": "var(--markdown-list-enumeration)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "markup.inline.raw",
|
||||
"settings": {
|
||||
"foreground": "var(--markdown-code)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "punctuation.definition.tag",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-punctuation)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["meta.preprocessor", "entity.name.function.preprocessor"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.string",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-string)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.preprocessor.numeric",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-number)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.structure.dictionary.key.python",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-variable)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.diff.header",
|
||||
"settings": {
|
||||
"foreground": "var(--text-weak)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "storage.type",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["storage.modifier", "keyword.operator.noexcept"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["string", "meta.embedded.assembly"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-string)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.tag",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-string)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.value",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-string)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "string.regexp",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.definition.template-expression.begin",
|
||||
"punctuation.definition.template-expression.end",
|
||||
"punctuation.section.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["meta.template.expression"],
|
||||
"settings": {
|
||||
"foreground": "var(--text-base)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.vendored.property-name",
|
||||
"support.type.property-name",
|
||||
"source.css variable",
|
||||
"source.coffee.embedded"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-variable)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.control",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.new",
|
||||
"keyword.operator.expression",
|
||||
"keyword.operator.cast",
|
||||
"keyword.operator.sizeof",
|
||||
"keyword.operator.alignof",
|
||||
"keyword.operator.typeid",
|
||||
"keyword.operator.alignas",
|
||||
"keyword.operator.instanceof",
|
||||
"keyword.operator.logical.python",
|
||||
"keyword.operator.wordlike"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.other.unit",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-number)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["punctuation.section.embedded.begin.php", "punctuation.section.embedded.end.php"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "support.function.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-variable)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.sha.git-rebase",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-number)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"],
|
||||
"settings": {
|
||||
"foreground": "var(--text-base)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "variable.language",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.function",
|
||||
"support.function",
|
||||
"support.constant.handlebars",
|
||||
"source.powershell variable.other.member",
|
||||
"entity.name.operator.custom-literal"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-function)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.class",
|
||||
"support.type",
|
||||
"entity.name.type",
|
||||
"entity.name.namespace",
|
||||
"entity.other.attribute",
|
||||
"entity.name.scope-resolution",
|
||||
"entity.name.class",
|
||||
"storage.type.numeric.go",
|
||||
"storage.type.byte.go",
|
||||
"storage.type.boolean.go",
|
||||
"storage.type.string.go",
|
||||
"storage.type.uintptr.go",
|
||||
"storage.type.error.go",
|
||||
"storage.type.rune.go",
|
||||
"storage.type.cs",
|
||||
"storage.type.generic.cs",
|
||||
"storage.type.modifier.cs",
|
||||
"storage.type.variable.cs",
|
||||
"storage.type.annotation.java",
|
||||
"storage.type.generic.java",
|
||||
"storage.type.java",
|
||||
"storage.type.object.array.java",
|
||||
"storage.type.primitive.array.java",
|
||||
"storage.type.primitive.java",
|
||||
"storage.type.token.java",
|
||||
"storage.type.groovy",
|
||||
"storage.type.annotation.groovy",
|
||||
"storage.type.parameters.groovy",
|
||||
"storage.type.generic.groovy",
|
||||
"storage.type.object.array.groovy",
|
||||
"storage.type.primitive.array.groovy",
|
||||
"storage.type.primitive.groovy"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-type)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"meta.type.cast.expr",
|
||||
"meta.type.new.expr",
|
||||
"support.constant.math",
|
||||
"support.constant.dom",
|
||||
"support.constant.json",
|
||||
"entity.other.inherited-class",
|
||||
"punctuation.separator.namespace.ruby"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-type)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"source.cpp keyword.operator.new",
|
||||
"keyword.operator.delete",
|
||||
"keyword.other.using",
|
||||
"keyword.other.directive.using",
|
||||
"keyword.other.operator",
|
||||
"entity.name.operator"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"variable",
|
||||
"meta.definition.variable.name",
|
||||
"support.variable",
|
||||
"entity.name.variable",
|
||||
"constant.other.placeholder"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-variable)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["variable.other.constant", "variable.other.enummember"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-variable)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["meta.object-literal.key"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-variable)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.constant.property-value",
|
||||
"support.constant.font-name",
|
||||
"support.constant.media-type",
|
||||
"support.constant.media",
|
||||
"constant.other.color.rgb-value",
|
||||
"constant.other.rgb-value",
|
||||
"support.constant.color"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-string)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.definition.group.regexp",
|
||||
"punctuation.definition.group.assertion.regexp",
|
||||
"punctuation.definition.character-class.regexp",
|
||||
"punctuation.character.set.begin.regexp",
|
||||
"punctuation.character.set.end.regexp",
|
||||
"keyword.operator.negation.regexp",
|
||||
"support.other.parenthesis.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-string)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.character.character-class.regexp",
|
||||
"constant.other.character-class.set.regexp",
|
||||
"constant.other.character-class.regexp",
|
||||
"constant.character.set.regexp"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["keyword.operator.or.regexp", "keyword.control.anchor.regexp"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "keyword.operator.quantifier.regexp",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["constant.character", "constant.other.option"],
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-keyword)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "constant.character.escape",
|
||||
"settings": {
|
||||
"foreground": "var(--syntax-operator)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "entity.name.label",
|
||||
"settings": {
|
||||
"foreground": "var(--text-weak)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "dark"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { createContext, useContext, type ParentProps } from "solid-js"
|
||||
import { createEventBus } from "@solid-primitives/event-bus"
|
||||
import type { Event as SDKEvent } from "@opencode-ai/sdk"
|
||||
import { useSDK } from "@/context"
|
||||
|
||||
export type Event = SDKEvent // can extend with custom events later
|
||||
|
||||
function init() {
|
||||
const sdk = useSDK()
|
||||
const bus = createEventBus<Event>()
|
||||
sdk.event.subscribe().then(async (events) => {
|
||||
for await (const event of events.stream) {
|
||||
bus.emit(event)
|
||||
}
|
||||
})
|
||||
return bus
|
||||
}
|
||||
|
||||
type EventContext = ReturnType<typeof init>
|
||||
|
||||
const ctx = createContext<EventContext>()
|
||||
|
||||
export function EventProvider(props: ParentProps) {
|
||||
const value = init()
|
||||
return <ctx.Provider value={value}>{props.children}</ctx.Provider>
|
||||
}
|
||||
|
||||
export function useEvent() {
|
||||
const value = useContext(ctx)
|
||||
if (!value) {
|
||||
throw new Error("useEvent must be used within a EventProvider")
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createContext, Show, useContext, type ParentProps } from "solid-js"
|
||||
|
||||
export function createSimpleContext<T, Props extends Record<string, any>>(input: {
|
||||
name: string
|
||||
init: ((input: Props) => T) | (() => T)
|
||||
}) {
|
||||
const ctx = createContext<T>()
|
||||
|
||||
return {
|
||||
provider: (props: ParentProps<Props>) => {
|
||||
const init = input.init(props)
|
||||
return (
|
||||
// @ts-expect-error
|
||||
<Show when={init.ready === undefined || init.ready === true}>
|
||||
<ctx.Provider value={init}>{props.children}</ctx.Provider>
|
||||
</Show>
|
||||
)
|
||||
},
|
||||
use() {
|
||||
const value = useContext(ctx)
|
||||
if (!value) throw new Error(`${input.name} context must be used within a context provider`)
|
||||
return value
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export { EventProvider, useEvent } from "./event"
|
||||
export { LocalProvider, useLocal } from "./local"
|
||||
export { MarkedProvider, useMarked } from "./marked"
|
||||
export { SDKProvider, useSDK } from "./sdk"
|
||||
export { ShikiProvider, useShiki } from "./shiki"
|
||||
export { SyncProvider, useSync } from "./sync"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,43 +1,30 @@
|
||||
import { createContext, useContext, type ParentProps } from "solid-js"
|
||||
import { useShiki } from "@/context"
|
||||
import { marked } from "marked"
|
||||
import markedShiki from "marked-shiki"
|
||||
import { bundledLanguages, type BundledLanguage } from "shiki"
|
||||
|
||||
function init(highlighter: ReturnType<typeof useShiki>) {
|
||||
return marked.use(
|
||||
markedShiki({
|
||||
async highlight(code, lang) {
|
||||
if (!(lang in bundledLanguages)) {
|
||||
lang = "text"
|
||||
}
|
||||
if (!highlighter.getLoadedLanguages().includes(lang)) {
|
||||
await highlighter.loadLanguage(lang as BundledLanguage)
|
||||
}
|
||||
return highlighter.codeToHtml(code, {
|
||||
lang: lang || "text",
|
||||
theme: "opencode",
|
||||
tabindex: false,
|
||||
})
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useShiki } from "./shiki"
|
||||
|
||||
type MarkedContext = ReturnType<typeof init>
|
||||
|
||||
const ctx = createContext<MarkedContext>()
|
||||
|
||||
export function MarkedProvider(props: ParentProps) {
|
||||
const highlighter = useShiki()
|
||||
const value = init(highlighter)
|
||||
return <ctx.Provider value={value}>{props.children}</ctx.Provider>
|
||||
}
|
||||
|
||||
export function useMarked() {
|
||||
const value = useContext(ctx)
|
||||
if (!value) {
|
||||
throw new Error("useMarked must be used within a MarkedProvider")
|
||||
}
|
||||
return value
|
||||
}
|
||||
export const { use: useMarked, provider: MarkedProvider } = createSimpleContext({
|
||||
name: "Marked",
|
||||
init: () => {
|
||||
const highlighter = useShiki()
|
||||
return marked.use(
|
||||
markedShiki({
|
||||
async highlight(code, lang) {
|
||||
if (!(lang in bundledLanguages)) {
|
||||
lang = "text"
|
||||
}
|
||||
if (!highlighter.getLoadedLanguages().includes(lang)) {
|
||||
await highlighter.loadLanguage(lang as BundledLanguage)
|
||||
}
|
||||
return highlighter.codeToHtml(code, {
|
||||
lang: lang || "text",
|
||||
theme: "opencode",
|
||||
tabindex: false,
|
||||
})
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
import { createContext, useContext, type ParentProps } from "solid-js"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk/client"
|
||||
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/client"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||
import { onCleanup } from "solid-js"
|
||||
|
||||
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
|
||||
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
|
||||
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||
name: "SDK",
|
||||
init: (props: { url: string }) => {
|
||||
const abort = new AbortController()
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: props.url,
|
||||
signal: abort.signal,
|
||||
fetch: (req) => {
|
||||
// @ts-ignore
|
||||
req.timeout = false
|
||||
return fetch(req)
|
||||
},
|
||||
})
|
||||
|
||||
function init() {
|
||||
const client = createOpencodeClient({
|
||||
baseUrl: `http://${host}:${port}`,
|
||||
})
|
||||
return client
|
||||
}
|
||||
const emitter = createGlobalEmitter<{
|
||||
[key in Event["type"]]: Extract<Event, { type: key }>
|
||||
}>()
|
||||
|
||||
type SDKContext = ReturnType<typeof init>
|
||||
sdk.event.subscribe().then(async (events) => {
|
||||
for await (const event of events.stream) {
|
||||
console.log("event", event.type)
|
||||
emitter.emit(event.type, event)
|
||||
}
|
||||
})
|
||||
|
||||
const ctx = createContext<SDKContext>()
|
||||
onCleanup(() => {
|
||||
abort.abort()
|
||||
})
|
||||
|
||||
export function SDKProvider(props: ParentProps) {
|
||||
const value = init()
|
||||
return <ctx.Provider value={value}>{props.children}</ctx.Provider>
|
||||
}
|
||||
|
||||
export function useSDK() {
|
||||
const value = useContext(ctx)
|
||||
if (!value) {
|
||||
throw new Error("useSDK must be used within a SDKProvider")
|
||||
}
|
||||
return value
|
||||
}
|
||||
return { client: sdk, event: emitter }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { createHighlighter, type ThemeInput } from "shiki"
|
||||
import { createContext, useContext, type ParentProps } from "solid-js"
|
||||
|
||||
const theme: ThemeInput = {
|
||||
colors: {
|
||||
"actionBar.toggledBackground": "var(--theme-background-element)",
|
||||
"activityBarBadge.background": "var(--theme-accent)",
|
||||
"checkbox.border": "var(--theme-border)",
|
||||
"actionBar.toggledBackground": "var(--surface-raised-base)",
|
||||
"activityBarBadge.background": "var(--surface-brand-base)",
|
||||
"checkbox.border": "var(--border-base)",
|
||||
"editor.background": "transparent",
|
||||
"editor.foreground": "var(--theme-text)",
|
||||
"editor.inactiveSelectionBackground": "var(--theme-background-element)",
|
||||
"editor.selectionHighlightBackground": "var(--theme-border-active)",
|
||||
"editorIndentGuide.activeBackground1": "var(--theme-border-subtle)",
|
||||
"editorIndentGuide.background1": "var(--theme-border-subtle)",
|
||||
"input.placeholderForeground": "var(--theme-text-muted)",
|
||||
"list.activeSelectionIconForeground": "var(--theme-text)",
|
||||
"list.dropBackground": "var(--theme-background-element)",
|
||||
"menu.background": "var(--theme-background-panel)",
|
||||
"menu.border": "var(--theme-border)",
|
||||
"menu.foreground": "var(--theme-text)",
|
||||
"menu.selectionBackground": "var(--theme-primary)",
|
||||
"menu.separatorBackground": "var(--theme-border)",
|
||||
"ports.iconRunningProcessForeground": "var(--theme-success)",
|
||||
"editor.foreground": "var(--text-base)",
|
||||
"editor.inactiveSelectionBackground": "var(--surface-raised-base)",
|
||||
"editor.selectionHighlightBackground": "var(--border-active)",
|
||||
"editorIndentGuide.activeBackground1": "var(--border-weak-base)",
|
||||
"editorIndentGuide.background1": "var(--border-weak-base)",
|
||||
"input.placeholderForeground": "var(--text-weak)",
|
||||
"list.activeSelectionIconForeground": "var(--text-base)",
|
||||
"list.dropBackground": "var(--surface-raised-base)",
|
||||
"menu.background": "var(--surface-base)",
|
||||
"menu.border": "var(--border-base)",
|
||||
"menu.foreground": "var(--text-base)",
|
||||
"menu.selectionBackground": "var(--surface-interactive-base)",
|
||||
"menu.separatorBackground": "var(--border-base)",
|
||||
"ports.iconRunningProcessForeground": "var(--icon-success-base)",
|
||||
"sideBarSectionHeader.background": "transparent",
|
||||
"sideBarSectionHeader.border": "var(--theme-border-subtle)",
|
||||
"sideBarTitle.foreground": "var(--theme-text-muted)",
|
||||
"statusBarItem.remoteBackground": "var(--theme-success)",
|
||||
"statusBarItem.remoteForeground": "var(--theme-text)",
|
||||
"tab.lastPinnedBorder": "var(--theme-border-subtle)",
|
||||
"tab.selectedBackground": "var(--theme-background-element)",
|
||||
"tab.selectedForeground": "var(--theme-text-muted)",
|
||||
"terminal.inactiveSelectionBackground": "var(--theme-background-element)",
|
||||
"widget.border": "var(--theme-border)",
|
||||
"sideBarSectionHeader.border": "var(--border-weak-base)",
|
||||
"sideBarTitle.foreground": "var(--text-weak)",
|
||||
"statusBarItem.remoteBackground": "var(--surface-success-base)",
|
||||
"statusBarItem.remoteForeground": "var(--text-base)",
|
||||
"tab.lastPinnedBorder": "var(--border-weak-base)",
|
||||
"tab.selectedBackground": "var(--surface-raised-base)",
|
||||
"tab.selectedForeground": "var(--text-weak)",
|
||||
"terminal.inactiveSelectionBackground": "var(--surface-raised-base)",
|
||||
"widget.border": "var(--border-base)",
|
||||
},
|
||||
displayName: "opencode",
|
||||
name: "opencode",
|
||||
semanticHighlighting: true,
|
||||
semanticTokenColors: {
|
||||
customLiteral: "var(--theme-syntax-function)",
|
||||
newOperator: "var(--theme-syntax-operator)",
|
||||
numberLiteral: "var(--theme-syntax-number)",
|
||||
stringLiteral: "var(--theme-syntax-string)",
|
||||
customLiteral: "var(--syntax-function)",
|
||||
newOperator: "var(--syntax-operator)",
|
||||
numberLiteral: "var(--syntax-number)",
|
||||
stringLiteral: "var(--syntax-string)",
|
||||
},
|
||||
tokenColors: [
|
||||
{
|
||||
@@ -50,7 +50,7 @@ const theme: ThemeInput = {
|
||||
"variable.legacy.builtin.python",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-text)",
|
||||
foreground: "var(--text-base)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -68,19 +68,19 @@ const theme: ThemeInput = {
|
||||
{
|
||||
scope: "header",
|
||||
settings: {
|
||||
foreground: "var(--theme-markdown-heading)",
|
||||
foreground: "var(--markdown-heading)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "comment",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-comment)",
|
||||
foreground: "var(--syntax-comment)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "constant.language",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -91,31 +91,31 @@ const theme: ThemeInput = {
|
||||
"keyword.operator.minus.exponent",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-number)",
|
||||
foreground: "var(--syntax-number)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "constant.regexp",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "entity.name.tag",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["entity.name.tag.css", "entity.name.tag.less"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "entity.other.attribute-name",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-variable)",
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -131,13 +131,13 @@ const theme: ThemeInput = {
|
||||
"entity.other.attribute-name.scss",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "invalid",
|
||||
settings: {
|
||||
foreground: "var(--theme-error)",
|
||||
foreground: "var(--syntax-critical)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -150,7 +150,7 @@ const theme: ThemeInput = {
|
||||
scope: "markup.bold",
|
||||
settings: {
|
||||
fontStyle: "bold",
|
||||
foreground: "var(--theme-markdown-strong)",
|
||||
foreground: "var(--markdown-strong)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -175,115 +175,115 @@ const theme: ThemeInput = {
|
||||
{
|
||||
scope: "markup.inserted",
|
||||
settings: {
|
||||
foreground: "var(--theme-diff-added)",
|
||||
foreground: "var(--text-diff-add-base)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.deleted",
|
||||
settings: {
|
||||
foreground: "var(--theme-diff-removed)",
|
||||
foreground: "var(--text-diff-delete-base)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.changed",
|
||||
settings: {
|
||||
foreground: "var(--theme-diff-context)",
|
||||
foreground: "var(--text-base)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "punctuation.definition.quote.begin.markdown",
|
||||
settings: {
|
||||
foreground: "var(--theme-markdown-block-quote)",
|
||||
foreground: "var(--markdown-block-quote)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "punctuation.definition.list.begin.markdown",
|
||||
settings: {
|
||||
foreground: "var(--theme-markdown-list-enumeration)",
|
||||
foreground: "var(--markdown-list-enumeration)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "markup.inline.raw",
|
||||
settings: {
|
||||
foreground: "var(--theme-markdown-code)",
|
||||
foreground: "var(--markdown-code)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "punctuation.definition.tag",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-punctuation)",
|
||||
foreground: "var(--syntax-punctuation)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["meta.preprocessor", "entity.name.function.preprocessor"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.preprocessor.string",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-string)",
|
||||
foreground: "var(--syntax-string)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.preprocessor.numeric",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-number)",
|
||||
foreground: "var(--syntax-number)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.structure.dictionary.key.python",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-variable)",
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "meta.diff.header",
|
||||
settings: {
|
||||
foreground: "var(--theme-diff-hunk-header)",
|
||||
foreground: "var(--text-weak)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "storage",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "storage.type",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["storage.modifier", "keyword.operator.noexcept"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["string", "meta.embedded.assembly"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-string)",
|
||||
foreground: "var(--syntax-string)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "string.tag",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-string)",
|
||||
foreground: "var(--syntax-string)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "string.value",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-string)",
|
||||
foreground: "var(--syntax-string)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "string.regexp",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -293,13 +293,13 @@ const theme: ThemeInput = {
|
||||
"punctuation.section.embedded",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["meta.template.expression"],
|
||||
settings: {
|
||||
foreground: "var(--theme-text)",
|
||||
foreground: "var(--text-base)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -310,25 +310,25 @@ const theme: ThemeInput = {
|
||||
"source.coffee.embedded",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-variable)",
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "keyword",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "keyword.control",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "keyword.operator",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -345,43 +345,43 @@ const theme: ThemeInput = {
|
||||
"keyword.operator.wordlike",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "keyword.other.unit",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-number)",
|
||||
foreground: "var(--syntax-number)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["punctuation.section.embedded.begin.php", "punctuation.section.embedded.end.php"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "support.function.git-rebase",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-variable)",
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "constant.sha.git-rebase",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-number)",
|
||||
foreground: "var(--syntax-number)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"],
|
||||
settings: {
|
||||
foreground: "var(--theme-text)",
|
||||
foreground: "var(--text-base)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "variable.language",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -393,7 +393,7 @@ const theme: ThemeInput = {
|
||||
"entity.name.operator.custom-literal",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-function)",
|
||||
foreground: "var(--syntax-function)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -432,7 +432,7 @@ const theme: ThemeInput = {
|
||||
"storage.type.primitive.groovy",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-type)",
|
||||
foreground: "var(--syntax-type)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -446,7 +446,7 @@ const theme: ThemeInput = {
|
||||
"punctuation.separator.namespace.ruby",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-type)",
|
||||
foreground: "var(--syntax-type)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -460,7 +460,7 @@ const theme: ThemeInput = {
|
||||
"entity.name.operator",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -472,19 +472,19 @@ const theme: ThemeInput = {
|
||||
"constant.other.placeholder",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-variable)",
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["variable.other.constant", "variable.other.enummember"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-variable)",
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["meta.object-literal.key"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-variable)",
|
||||
foreground: "var(--syntax-variable)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -498,7 +498,7 @@ const theme: ThemeInput = {
|
||||
"support.constant.color",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-string)",
|
||||
foreground: "var(--syntax-string)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -512,7 +512,7 @@ const theme: ThemeInput = {
|
||||
"support.other.parenthesis.regexp",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-string)",
|
||||
foreground: "var(--syntax-string)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -523,37 +523,37 @@ const theme: ThemeInput = {
|
||||
"constant.character.set.regexp",
|
||||
],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["keyword.operator.or.regexp", "keyword.control.anchor.regexp"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "keyword.operator.quantifier.regexp",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: ["constant.character", "constant.other.option"],
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-keyword)",
|
||||
foreground: "var(--syntax-keyword)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "constant.character.escape",
|
||||
settings: {
|
||||
foreground: "var(--theme-syntax-operator)",
|
||||
foreground: "var(--syntax-operator)",
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: "entity.name.label",
|
||||
settings: {
|
||||
foreground: "var(--theme-text-muted)",
|
||||
foreground: "var(--text-weak)",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -565,18 +565,9 @@ const highlighter = await createHighlighter({
|
||||
langs: [],
|
||||
})
|
||||
|
||||
type ShikiContext = typeof highlighter
|
||||
|
||||
const ctx = createContext<ShikiContext>()
|
||||
|
||||
export function ShikiProvider(props: ParentProps) {
|
||||
return <ctx.Provider value={highlighter}>{props.children}</ctx.Provider>
|
||||
}
|
||||
|
||||
export function useShiki() {
|
||||
const value = useContext(ctx)
|
||||
if (!value) {
|
||||
throw new Error("useShiki must be used within a ShikiProvider")
|
||||
}
|
||||
return value
|
||||
}
|
||||
export const { use: useShiki, provider: ShikiProvider } = createSimpleContext({
|
||||
name: "Shiki",
|
||||
init: () => {
|
||||
return highlighter
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,177 +1,169 @@
|
||||
import type { Message, Agent, Provider, Session, Part, Config, Path, File, FileNode, Project } from "@opencode-ai/sdk"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { createContext, createMemo, Show, useContext, type ParentProps } from "solid-js"
|
||||
import { useSDK, useEvent } from "@/context"
|
||||
import { createMemo } from "solid-js"
|
||||
import { Binary } from "@/utils/binary"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useSDK } from "./sdk"
|
||||
|
||||
function init() {
|
||||
const [store, setStore] = createStore<{
|
||||
ready: boolean
|
||||
provider: Provider[]
|
||||
agent: Agent[]
|
||||
project: Project
|
||||
config: Config
|
||||
path: Path
|
||||
session: Session[]
|
||||
message: {
|
||||
[sessionID: string]: Message[]
|
||||
}
|
||||
part: {
|
||||
[messageID: string]: Part[]
|
||||
}
|
||||
node: FileNode[]
|
||||
changes: File[]
|
||||
}>({
|
||||
project: { id: "", worktree: "", time: { created: 0, initialized: 0 } },
|
||||
config: {},
|
||||
path: { state: "", config: "", worktree: "", directory: "" },
|
||||
ready: false,
|
||||
agent: [],
|
||||
provider: [],
|
||||
session: [],
|
||||
message: {},
|
||||
part: {},
|
||||
node: [],
|
||||
changes: [],
|
||||
})
|
||||
|
||||
const bus = useEvent()
|
||||
bus.listen((event) => {
|
||||
switch (event.type) {
|
||||
case "session.updated": {
|
||||
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
||||
if (result.found) {
|
||||
setStore("session", result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
name: "Sync",
|
||||
init: () => {
|
||||
const [store, setStore] = createStore<{
|
||||
ready: boolean
|
||||
provider: Provider[]
|
||||
agent: Agent[]
|
||||
project: Project
|
||||
config: Config
|
||||
path: Path
|
||||
session: Session[]
|
||||
message: {
|
||||
[sessionID: string]: Message[]
|
||||
}
|
||||
case "message.updated": {
|
||||
const messages = store.message[event.properties.info.sessionID]
|
||||
if (!messages) {
|
||||
setStore("message", event.properties.info.sessionID, [event.properties.info])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
|
||||
if (result.found) {
|
||||
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"message",
|
||||
event.properties.info.sessionID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
part: {
|
||||
[messageID: string]: Part[]
|
||||
}
|
||||
case "message.part.updated": {
|
||||
const parts = store.part[event.properties.part.messageID]
|
||||
if (!parts) {
|
||||
setStore("part", event.properties.part.messageID, [event.properties.part])
|
||||
node: FileNode[]
|
||||
changes: File[]
|
||||
}>({
|
||||
project: { id: "", worktree: "", time: { created: 0, initialized: 0 } },
|
||||
config: {},
|
||||
path: { state: "", config: "", worktree: "", directory: "" },
|
||||
ready: false,
|
||||
agent: [],
|
||||
provider: [],
|
||||
session: [],
|
||||
message: {},
|
||||
part: {},
|
||||
node: [],
|
||||
changes: [],
|
||||
})
|
||||
|
||||
const sdk = useSDK()
|
||||
sdk.event.listen((e) => {
|
||||
const event = e.details
|
||||
switch (event.type) {
|
||||
case "session.updated": {
|
||||
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
||||
if (result.found) {
|
||||
setStore("session", result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
const result = Binary.search(parts, event.properties.part.id, (p) => p.id)
|
||||
if (result.found) {
|
||||
setStore("part", event.properties.part.messageID, result.index, reconcile(event.properties.part))
|
||||
case "message.updated": {
|
||||
const messages = store.message[event.properties.info.sessionID]
|
||||
if (!messages) {
|
||||
setStore("message", event.properties.info.sessionID, [event.properties.info])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
|
||||
if (result.found) {
|
||||
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"message",
|
||||
event.properties.info.sessionID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.info)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.part.updated": {
|
||||
const parts = store.part[event.properties.part.messageID]
|
||||
if (!parts) {
|
||||
setStore("part", event.properties.part.messageID, [event.properties.part])
|
||||
break
|
||||
}
|
||||
const result = Binary.search(parts, event.properties.part.id, (p) => p.id)
|
||||
if (result.found) {
|
||||
setStore("part", event.properties.part.messageID, result.index, reconcile(event.properties.part))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"part",
|
||||
event.properties.part.messageID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.part)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"part",
|
||||
event.properties.part.messageID,
|
||||
produce((draft) => {
|
||||
draft.splice(result.index, 0, event.properties.part)
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const sdk = useSDK()
|
||||
|
||||
const load = {
|
||||
project: () => sdk.project.current().then((x) => setStore("project", x.data!)),
|
||||
provider: () => sdk.config.providers().then((x) => setStore("provider", x.data!.providers)),
|
||||
path: () => sdk.path.get().then((x) => setStore("path", x.data!)),
|
||||
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||
session: () =>
|
||||
sdk.session.list().then((x) =>
|
||||
setStore(
|
||||
"session",
|
||||
(x.data ?? []).slice().sort((a, b) => a.id.localeCompare(b.id)),
|
||||
const load = {
|
||||
project: () => sdk.client.project.current().then((x) => setStore("project", x.data!)),
|
||||
provider: () => sdk.client.config.providers().then((x) => setStore("provider", x.data!.providers)),
|
||||
path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)),
|
||||
agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||
session: () =>
|
||||
sdk.client.session.list().then((x) =>
|
||||
setStore(
|
||||
"session",
|
||||
(x.data ?? []).slice().sort((a, b) => a.id.localeCompare(b.id)),
|
||||
),
|
||||
),
|
||||
),
|
||||
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
|
||||
changes: () => sdk.file.status().then((x) => setStore("changes", x.data!)),
|
||||
node: () => sdk.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)),
|
||||
}
|
||||
config: () => sdk.client.config.get().then((x) => setStore("config", x.data!)),
|
||||
changes: () => sdk.client.file.status().then((x) => setStore("changes", x.data!)),
|
||||
node: () => sdk.client.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)),
|
||||
}
|
||||
|
||||
Promise.all(Object.values(load).map((p) => p())).then(() => setStore("ready", true))
|
||||
Promise.all(Object.values(load).map((p) => p())).then(() => setStore("ready", true))
|
||||
|
||||
const sanitizer = createMemo(() => new RegExp(`${store.path.directory}/`, "g"))
|
||||
const sanitize = (text: string) => text.replace(sanitizer(), "")
|
||||
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
|
||||
const sanitizer = createMemo(() => new RegExp(`${store.path.directory}/`, "g"))
|
||||
const sanitize = (text: string) => text.replace(sanitizer(), "")
|
||||
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
|
||||
|
||||
return {
|
||||
data: store,
|
||||
set: setStore,
|
||||
session: {
|
||||
get(sessionID: string) {
|
||||
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
||||
if (match.found) return store.session[match.index]
|
||||
return undefined
|
||||
return {
|
||||
data: store,
|
||||
set: setStore,
|
||||
get ready() {
|
||||
return store.ready
|
||||
},
|
||||
async sync(sessionID: string) {
|
||||
const [session, messages] = await Promise.all([
|
||||
sdk.session.get({ path: { id: sessionID } }),
|
||||
sdk.session.messages({ path: { id: sessionID } }),
|
||||
])
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
const match = Binary.search(draft.session, sessionID, (s) => s.id)
|
||||
draft.session[match.index] = session.data!
|
||||
draft.message[sessionID] = messages
|
||||
.data!.map((x) => x.info)
|
||||
.slice()
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
for (const message of messages.data!) {
|
||||
draft.part[message.info.id] = message.parts.slice().sort((a, b) => a.id.localeCompare(b.id))
|
||||
}
|
||||
}),
|
||||
)
|
||||
session: {
|
||||
get(sessionID: string) {
|
||||
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
||||
if (match.found) return store.session[match.index]
|
||||
return undefined
|
||||
},
|
||||
async sync(sessionID: string, isRetry = false) {
|
||||
const [session, messages] = await Promise.all([
|
||||
sdk.client.session.get({ path: { id: sessionID } }),
|
||||
sdk.client.session.messages({ path: { id: sessionID } }),
|
||||
])
|
||||
|
||||
// If no messages and this might be a new session, retry after a delay
|
||||
if (!isRetry && messages.data!.length === 0) {
|
||||
setTimeout(() => this.sync(sessionID, true), 500)
|
||||
return
|
||||
}
|
||||
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
const match = Binary.search(draft.session, sessionID, (s) => s.id)
|
||||
draft.session[match.index] = session.data!
|
||||
draft.message[sessionID] = messages
|
||||
.data!.map((x) => x.info)
|
||||
.slice()
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
for (const message of messages.data!) {
|
||||
draft.part[message.info.id] = message.parts.slice().sort((a, b) => a.id.localeCompare(b.id))
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
},
|
||||
load,
|
||||
absolute,
|
||||
sanitize,
|
||||
}
|
||||
}
|
||||
|
||||
type SyncContext = ReturnType<typeof init>
|
||||
|
||||
const ctx = createContext<SyncContext>()
|
||||
|
||||
export function SyncProvider(props: ParentProps) {
|
||||
const value = init()
|
||||
return (
|
||||
<Show when={value.data.ready}>
|
||||
<ctx.Provider value={value}>{props.children}</ctx.Provider>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSync() {
|
||||
const value = useContext(ctx)
|
||||
if (!value) {
|
||||
throw new Error("useSync must be used within a SyncProvider")
|
||||
}
|
||||
return value
|
||||
}
|
||||
load,
|
||||
absolute,
|
||||
sanitize,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,10 +3,19 @@ import "@/index.css"
|
||||
import { render } from "solid-js/web"
|
||||
import { Router, Route } from "@solidjs/router"
|
||||
import { MetaProvider } from "@solidjs/meta"
|
||||
import { EventProvider, SDKProvider, SyncProvider, LocalProvider, ShikiProvider, MarkedProvider } from "@/context"
|
||||
import { Fonts } from "@opencode-ai/ui"
|
||||
import { ShikiProvider } from "./context/shiki"
|
||||
import { MarkedProvider } from "./context/marked"
|
||||
import { SDKProvider } from "./context/sdk"
|
||||
import { SyncProvider } from "./context/sync"
|
||||
import { LocalProvider } from "./context/local"
|
||||
import Home from "@/pages"
|
||||
|
||||
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
|
||||
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
|
||||
|
||||
const url = new URLSearchParams(document.location.search).get("url") || `http://${host}:${port}`
|
||||
|
||||
const root = document.getElementById("root")
|
||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
throw new Error(
|
||||
@@ -18,19 +27,17 @@ render(
|
||||
() => (
|
||||
<ShikiProvider>
|
||||
<MarkedProvider>
|
||||
<SDKProvider>
|
||||
<EventProvider>
|
||||
<SyncProvider>
|
||||
<LocalProvider>
|
||||
<MetaProvider>
|
||||
<Fonts />
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
</Router>
|
||||
</MetaProvider>
|
||||
</LocalProvider>
|
||||
</SyncProvider>
|
||||
</EventProvider>
|
||||
<SDKProvider url={url}>
|
||||
<SyncProvider>
|
||||
<LocalProvider>
|
||||
<MetaProvider>
|
||||
<Fonts />
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
</Router>
|
||||
</MetaProvider>
|
||||
</LocalProvider>
|
||||
</SyncProvider>
|
||||
</SDKProvider>
|
||||
</MarkedProvider>
|
||||
</ShikiProvider>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ function CollapsibleTrigger(props: CollapsibleTriggerProps) {
|
||||
return (
|
||||
<KobalteCollapsible.Trigger
|
||||
classList={{
|
||||
"w-full group/collapsible cursor-pointer": true,
|
||||
"w-full group/collapsible": true,
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
{...others}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Button as KobalteButton } from "@kobalte/core/button"
|
||||
import { splitProps } from "solid-js"
|
||||
import type { ComponentProps, JSX } from "solid-js"
|
||||
|
||||
export interface IconButtonProps extends ComponentProps<typeof KobalteButton> {
|
||||
variant?: "primary" | "secondary" | "outline" | "ghost"
|
||||
size?: "xs" | "sm" | "md" | "lg"
|
||||
children: JSX.Element
|
||||
}
|
||||
|
||||
export function IconButton(props: IconButtonProps) {
|
||||
const [local, others] = splitProps(props, ["variant", "size", "class", "classList"])
|
||||
return (
|
||||
<KobalteButton
|
||||
classList={{
|
||||
...(local.classList || {}),
|
||||
"inline-flex items-center justify-center rounded-md font-medium cursor-pointer": true,
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2": true,
|
||||
"disabled:pointer-events-none disabled:opacity-50": true,
|
||||
"bg-primary text-background hover:bg-secondary focus-visible:ring-primary data-[disabled]:opacity-50":
|
||||
(local.variant || "primary") === "primary",
|
||||
"bg-background-panel text-text hover:bg-background-element focus-visible:ring-secondary data-[disabled]:opacity-50":
|
||||
local.variant === "secondary",
|
||||
"border border-border bg-transparent text-text hover:bg-background-panel": local.variant === "outline",
|
||||
"focus-visible:ring-border-active data-[disabled]:border-border-subtle data-[disabled]:text-text-muted":
|
||||
local.variant === "outline",
|
||||
"text-text hover:bg-background-panel focus-visible:ring-border-active data-[disabled]:text-text-muted":
|
||||
local.variant === "ghost",
|
||||
"h-5 w-5 text-xs": local.size === "xs",
|
||||
"h-8 w-8 text-sm": local.size === "sm",
|
||||
"h-10 w-10 text-sm": (local.size || "md") === "md",
|
||||
"h-12 w-12 text-base": local.size === "lg",
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -5,4 +5,3 @@ export {
|
||||
type CollapsibleContentProps,
|
||||
} from "./collapsible"
|
||||
export { FileIcon, type FileIconProps } from "./file-icon"
|
||||
export { IconButton, type IconButtonProps } from "./icon-button"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useSync } from "@/context/sync"
|
||||
|
||||
export function getFilename(path: string) {
|
||||
if (!path) return ""
|
||||
const trimmed = path.replace(/[\/]+$/, "")
|
||||
@@ -6,8 +8,10 @@ export function getFilename(path: string) {
|
||||
}
|
||||
|
||||
export function getDirectory(path: string) {
|
||||
const sync = useSync()
|
||||
const parts = path.split("/")
|
||||
return parts.slice(0, parts.length - 1).join("/")
|
||||
const dir = parts.slice(0, parts.length - 1).join("/")
|
||||
return dir ? sync.sanitize(dir + "/") : ""
|
||||
}
|
||||
|
||||
export function getFileExtension(path: string) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.15.9",
|
||||
"version": "0.15.29",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -20,6 +20,10 @@
|
||||
"@ai-sdk/amazon-bedrock": "2.2.10",
|
||||
"@ai-sdk/google-vertex": "3.0.16",
|
||||
"@octokit/webhooks-types": "7.6.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@tsconfig/bun": "catalog:",
|
||||
@@ -29,12 +33,12 @@
|
||||
"typescript": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"vscode-languageserver-types": "3.17.5",
|
||||
"zod-to-json-schema": "3.24.5",
|
||||
"@opencode-ai/script": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@agentclientprotocol/sdk": "0.4.9",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@hono/standard-validator": "0.1.5",
|
||||
"@hono/zod-validator": "catalog:",
|
||||
@@ -46,6 +50,7 @@
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"ai": "catalog:",
|
||||
@@ -69,6 +74,7 @@
|
||||
"web-tree-sitter": "0.22.6",
|
||||
"xdg-basedir": "5.1.0",
|
||||
"yargs": "18.0.0",
|
||||
"zod": "catalog:"
|
||||
"zod": "catalog:",
|
||||
"zod-to-json-schema": "3.24.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env bun
|
||||
import path from "path"
|
||||
const dir = new URL("..", import.meta.url).pathname
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const dir = path.resolve(__dirname, "..")
|
||||
|
||||
process.chdir(dir)
|
||||
import { $ } from "bun"
|
||||
|
||||
@@ -40,6 +45,7 @@ for (const [os, arch] of targets) {
|
||||
await $`tar -xf ../../node_modules/${watcher.replace("@parcel/", "parcel-")}-*.tgz -C ../../node_modules/${watcher} --strip-components=1`
|
||||
|
||||
await Bun.build({
|
||||
sourcemap: "external",
|
||||
compile: {
|
||||
target: `bun-${os}-${arch}` as any,
|
||||
outfile: `dist/${name}/bin/opencode`,
|
||||
|
||||
@@ -40,16 +40,35 @@ for (const [name] of Object.entries(binaries)) {
|
||||
}
|
||||
await $`cd ./dist/${pkg.name} && bun publish --access public --tag ${Script.channel}`
|
||||
|
||||
if (!Script.preview) {
|
||||
const major = Script.version.split(".")[0]
|
||||
const majorTag = `latest-${major}`
|
||||
for (const [name] of Object.entries(binaries)) {
|
||||
await $`cd dist/${name} && npm dist-tag add ${name}@${Script.version} ${majorTag}`
|
||||
}
|
||||
await $`cd ./dist/${pkg.name} && npm dist-tag add ${pkg.name}-ai@${Script.version} ${majorTag}`
|
||||
}
|
||||
|
||||
if (!Script.preview) {
|
||||
for (const key of Object.keys(binaries)) {
|
||||
await $`cd dist/${key}/bin && zip -r ../../${key}.zip *`
|
||||
}
|
||||
|
||||
// Calculate SHA values
|
||||
const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.zip | cut -d' ' -f1`
|
||||
.text()
|
||||
.then((x) => x.trim())
|
||||
const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.zip | cut -d' ' -f1`
|
||||
.text()
|
||||
.then((x) => x.trim())
|
||||
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`
|
||||
.text()
|
||||
.then((x) => x.trim())
|
||||
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`
|
||||
.text()
|
||||
.then((x) => x.trim())
|
||||
|
||||
const [pkgver, _subver = ""] = Script.version.split(/(-.*)/, 2)
|
||||
|
||||
// arch
|
||||
const binaryPkgbuild = [
|
||||
@@ -57,7 +76,8 @@ if (!Script.preview) {
|
||||
"# Maintainer: adam",
|
||||
"",
|
||||
"pkgname='opencode-bin'",
|
||||
`pkgver=${Script.version.split("-")[0]}`,
|
||||
`pkgver=${pkgver}`,
|
||||
`_subver=${_subver}`,
|
||||
"options=('!debug' '!strip')",
|
||||
"pkgrel=1",
|
||||
"pkgdesc='The AI coding agent built for the terminal.'",
|
||||
@@ -68,10 +88,10 @@ if (!Script.preview) {
|
||||
"conflicts=('opencode')",
|
||||
"depends=('fzf' 'ripgrep')",
|
||||
"",
|
||||
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.zip::https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-linux-arm64.zip")`,
|
||||
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.zip::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.zip")`,
|
||||
`sha256sums_aarch64=('${arm64Sha}')`,
|
||||
"",
|
||||
`source_x86_64=("\${pkgname}_\${pkgver}_x86_64.zip::https://github.com/sst/opencode/releases/download/v${Script.version}/opencode-linux-x64.zip")`,
|
||||
`source_x86_64=("\${pkgname}_\${pkgver}_x86_64.zip::https://github.com/sst/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-x64.zip")`,
|
||||
`sha256sums_x86_64=('${x64Sha}')`,
|
||||
"",
|
||||
"package() {",
|
||||
@@ -86,7 +106,8 @@ if (!Script.preview) {
|
||||
"# Maintainer: adam",
|
||||
"",
|
||||
"pkgname='opencode'",
|
||||
`pkgver=${Script.version.split("-")[0]}`,
|
||||
`pkgver=${pkgver}`,
|
||||
`_subver=${_subver}`,
|
||||
"options=('!debug' '!strip')",
|
||||
"pkgrel=1",
|
||||
"pkgdesc='The AI coding agent built for the terminal.'",
|
||||
@@ -98,7 +119,7 @@ if (!Script.preview) {
|
||||
"depends=('fzf' 'ripgrep')",
|
||||
"makedepends=('git' 'bun-bin' 'go')",
|
||||
"",
|
||||
`source=("opencode-\${pkgver}.tar.gz::https://github.com/sst/opencode/archive/v${Script.version}.tar.gz")`,
|
||||
`source=("opencode-\${pkgver}.tar.gz::https://github.com/sst/opencode/archive/v\${pkgver}\${_subver}.tar.gz")`,
|
||||
`sha256sums=('SKIP')`,
|
||||
"",
|
||||
"build() {",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { z } from "zod/v4"
|
||||
import { z } from "zod"
|
||||
import { Config } from "../src/config/config"
|
||||
|
||||
const file = process.argv[2]
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
# ACP (Agent Client Protocol) Implementation
|
||||
|
||||
This directory contains a clean, protocol-compliant implementation of the [Agent Client Protocol](https://agentclientprotocol.com/) for opencode.
|
||||
|
||||
## Architecture
|
||||
|
||||
The implementation follows a clean separation of concerns:
|
||||
|
||||
### Core Components
|
||||
|
||||
- **`agent.ts`** - Implements the `Agent` interface from `@agentclientprotocol/sdk`
|
||||
- Handles initialization and capability negotiation
|
||||
- Manages session lifecycle (`session/new`, `session/load`)
|
||||
- Processes prompts and returns responses
|
||||
- Properly implements ACP protocol v1
|
||||
|
||||
- **`client.ts`** - Implements the `Client` interface for client-side capabilities
|
||||
- File operations (`readTextFile`, `writeTextFile`)
|
||||
- Permission requests (auto-approves for now)
|
||||
- Terminal support (stub implementation)
|
||||
|
||||
- **`session.ts`** - Session state management
|
||||
- Creates and tracks ACP sessions
|
||||
- Maps ACP sessions to internal opencode sessions
|
||||
- Maintains working directory context
|
||||
- Handles MCP server configurations
|
||||
|
||||
- **`server.ts`** - ACP server startup and lifecycle
|
||||
- Sets up JSON-RPC over stdio using the official library
|
||||
- Manages graceful shutdown on SIGTERM/SIGINT
|
||||
- Provides Instance context for the agent
|
||||
|
||||
- **`types.ts`** - Type definitions for internal use
|
||||
|
||||
## Usage
|
||||
|
||||
### Command Line
|
||||
|
||||
```bash
|
||||
# Start the ACP server in the current directory
|
||||
opencode acp
|
||||
|
||||
# Start in a specific directory
|
||||
opencode acp --cwd /path/to/project
|
||||
```
|
||||
|
||||
### Programmatic
|
||||
|
||||
```typescript
|
||||
import { ACPServer } from "./acp/server"
|
||||
|
||||
await ACPServer.start()
|
||||
```
|
||||
|
||||
### Integration with Zed
|
||||
|
||||
Add to your Zed configuration (`~/.config/zed/settings.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"OpenCode": {
|
||||
"command": "opencode",
|
||||
"args": ["acp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Protocol Compliance
|
||||
|
||||
This implementation follows the ACP specification v1:
|
||||
|
||||
✅ **Initialization**
|
||||
|
||||
- Proper `initialize` request/response with protocol version negotiation
|
||||
- Capability advertisement (`agentCapabilities`)
|
||||
- Authentication support (stub)
|
||||
|
||||
✅ **Session Management**
|
||||
|
||||
- `session/new` - Create new conversation sessions
|
||||
- `session/load` - Resume existing sessions (basic support)
|
||||
- Working directory context (`cwd`)
|
||||
- MCP server configuration support
|
||||
|
||||
✅ **Prompting**
|
||||
|
||||
- `session/prompt` - Process user messages
|
||||
- Content block handling (text, resources)
|
||||
- Response with stop reasons
|
||||
|
||||
✅ **Client Capabilities**
|
||||
|
||||
- File read/write operations
|
||||
- Permission requests
|
||||
- Terminal support (stub for future)
|
||||
|
||||
## Current Limitations
|
||||
|
||||
### Not Yet Implemented
|
||||
|
||||
1. **Streaming Responses** - Currently returns complete responses instead of streaming via `session/update` notifications
|
||||
2. **Tool Call Reporting** - Doesn't report tool execution progress
|
||||
3. **Session Modes** - No mode switching support yet
|
||||
4. **Authentication** - No actual auth implementation
|
||||
5. **Terminal Support** - Placeholder only
|
||||
6. **Session Persistence** - `session/load` doesn't restore actual conversation history
|
||||
|
||||
### Future Enhancements
|
||||
|
||||
- **Real-time Streaming**: Implement `session/update` notifications for progressive responses
|
||||
- **Tool Call Visibility**: Report tool executions as they happen
|
||||
- **Session Persistence**: Save and restore full conversation history
|
||||
- **Mode Support**: Implement different operational modes (ask, code, etc.)
|
||||
- **Enhanced Permissions**: More sophisticated permission handling
|
||||
- **Terminal Integration**: Full terminal support via opencode's bash tool
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run ACP tests
|
||||
bun test test/acp.test.ts
|
||||
|
||||
# Test manually with stdio
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1}}' | opencode acp
|
||||
```
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### Why the Official Library?
|
||||
|
||||
We use `@agentclientprotocol/sdk` instead of implementing JSON-RPC ourselves because:
|
||||
|
||||
- Ensures protocol compliance
|
||||
- Handles edge cases and future protocol versions
|
||||
- Reduces maintenance burden
|
||||
- Works with other ACP clients automatically
|
||||
|
||||
### Clean Architecture
|
||||
|
||||
Each component has a single responsibility:
|
||||
|
||||
- **Agent** = Protocol interface
|
||||
- **Client** = Client-side operations
|
||||
- **Session** = State management
|
||||
- **Server** = Lifecycle and I/O
|
||||
|
||||
This makes the codebase maintainable and testable.
|
||||
|
||||
### Mapping to OpenCode
|
||||
|
||||
ACP sessions map cleanly to opencode's internal session model:
|
||||
|
||||
- ACP `session/new` → creates internal Session
|
||||
- ACP `session/prompt` → uses SessionPrompt.prompt()
|
||||
- Working directory context preserved per-session
|
||||
- Tool execution uses existing ToolRegistry
|
||||
|
||||
## References
|
||||
|
||||
- [ACP Specification](https://agentclientprotocol.com/)
|
||||
- [TypeScript Library](https://github.com/agentclientprotocol/typescript-sdk)
|
||||
- [Protocol Examples](https://github.com/agentclientprotocol/typescript-sdk/tree/main/src/examples)
|
||||
@@ -0,0 +1,704 @@
|
||||
import {
|
||||
type Agent as ACPAgent,
|
||||
type AgentSideConnection,
|
||||
type AuthenticateRequest,
|
||||
type CancelNotification,
|
||||
type InitializeRequest,
|
||||
type LoadSessionRequest,
|
||||
type NewSessionRequest,
|
||||
type PermissionOption,
|
||||
type PlanEntry,
|
||||
type PromptRequest,
|
||||
type SetSessionModelRequest,
|
||||
type SetSessionModeRequest,
|
||||
type SetSessionModeResponse,
|
||||
type ToolCallContent,
|
||||
type ToolKind,
|
||||
} from "@agentclientprotocol/sdk"
|
||||
import { Log } from "../util/log"
|
||||
import { ACPSessionManager } from "./session"
|
||||
import type { ACPConfig } from "./types"
|
||||
import { Provider } from "../provider/provider"
|
||||
import { SessionPrompt } from "../session/prompt"
|
||||
import { Installation } from "@/installation"
|
||||
import { SessionLock } from "@/session/lock"
|
||||
import { Bus } from "@/bus"
|
||||
import { MessageV2 } from "@/session/message-v2"
|
||||
import { Storage } from "@/storage/storage"
|
||||
import { Command } from "@/command"
|
||||
import { Agent as Agents } from "@/agent/agent"
|
||||
import { Permission } from "@/permission"
|
||||
import { Session } from "@/session"
|
||||
import { Identifier } from "@/id/id"
|
||||
import { SessionCompaction } from "@/session/compaction"
|
||||
import type { Config } from "@/config/config"
|
||||
import { MCP } from "@/mcp"
|
||||
import { Todo } from "@/session/todo"
|
||||
import { z } from "zod"
|
||||
|
||||
export namespace ACP {
|
||||
const log = Log.create({ service: "acp-agent" })
|
||||
|
||||
export class Agent implements ACPAgent {
|
||||
private sessionManager = new ACPSessionManager()
|
||||
private connection: AgentSideConnection
|
||||
private config: ACPConfig
|
||||
|
||||
constructor(connection: AgentSideConnection, config: ACPConfig = {}) {
|
||||
this.connection = connection
|
||||
this.config = config
|
||||
this.setupEventSubscriptions()
|
||||
}
|
||||
|
||||
private setupEventSubscriptions() {
|
||||
const options: PermissionOption[] = [
|
||||
{ optionId: "once", kind: "allow_once", name: "Allow once" },
|
||||
{ optionId: "always", kind: "allow_always", name: "Always allow" },
|
||||
{ optionId: "reject", kind: "reject_once", name: "Reject" },
|
||||
]
|
||||
Bus.subscribe(Permission.Event.Updated, async (event) => {
|
||||
const acpSession = this.sessionManager.get(event.properties.sessionID)
|
||||
if (!acpSession) return
|
||||
try {
|
||||
const permission = event.properties
|
||||
const res = await this.connection
|
||||
.requestPermission({
|
||||
sessionId: acpSession.id,
|
||||
toolCall: {
|
||||
toolCallId: permission.callID ?? permission.id,
|
||||
status: "pending",
|
||||
title: permission.title,
|
||||
rawInput: permission.metadata,
|
||||
kind: toToolKind(permission.type),
|
||||
locations: toLocations(permission.type, permission.metadata),
|
||||
},
|
||||
options,
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error("failed to request permission from ACP", {
|
||||
error,
|
||||
permissionID: permission.id,
|
||||
sessionID: permission.sessionID,
|
||||
})
|
||||
Permission.respond({
|
||||
sessionID: permission.sessionID,
|
||||
permissionID: permission.id,
|
||||
response: "reject",
|
||||
})
|
||||
return
|
||||
})
|
||||
if (!res) return
|
||||
if (res.outcome.outcome !== "selected") {
|
||||
Permission.respond({ sessionID: permission.sessionID, permissionID: permission.id, response: "reject" })
|
||||
return
|
||||
}
|
||||
Permission.respond({
|
||||
sessionID: permission.sessionID,
|
||||
permissionID: permission.id,
|
||||
response: res.outcome.optionId as "once" | "always" | "reject",
|
||||
})
|
||||
} catch (err) {
|
||||
if (!(err instanceof Permission.RejectedError)) {
|
||||
log.error("unexpected error when handling permission", { error: err })
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Bus.subscribe(MessageV2.Event.PartUpdated, async (event) => {
|
||||
const props = event.properties
|
||||
const { part } = props
|
||||
const acpSession = this.sessionManager.get(part.sessionID)
|
||||
if (!acpSession) return
|
||||
|
||||
const message = await Storage.read<MessageV2.Info>(["message", part.sessionID, part.messageID]).catch(
|
||||
() => undefined,
|
||||
)
|
||||
if (!message || message.role !== "assistant") return
|
||||
|
||||
if (part.type === "tool") {
|
||||
switch (part.state.status) {
|
||||
case "pending":
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId: acpSession.id,
|
||||
update: {
|
||||
sessionUpdate: "tool_call",
|
||||
toolCallId: part.callID,
|
||||
title: part.tool,
|
||||
kind: toToolKind(part.tool),
|
||||
status: "pending",
|
||||
locations: [],
|
||||
rawInput: {},
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error("failed to send tool pending to ACP", { error: err })
|
||||
})
|
||||
break
|
||||
case "running":
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId: acpSession.id,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: part.callID,
|
||||
status: "in_progress",
|
||||
locations: toLocations(part.tool, part.state.input),
|
||||
rawInput: part.state.input,
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error("failed to send tool in_progress to ACP", { error: err })
|
||||
})
|
||||
break
|
||||
case "completed":
|
||||
const kind = toToolKind(part.tool)
|
||||
const content: ToolCallContent[] = [
|
||||
{
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: part.state.output,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if (kind === "edit") {
|
||||
const input = part.state.input
|
||||
const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
|
||||
const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
|
||||
const newText =
|
||||
typeof input["newString"] === "string"
|
||||
? input["newString"]
|
||||
: typeof input["content"] === "string"
|
||||
? input["content"]
|
||||
: ""
|
||||
content.push({
|
||||
type: "diff",
|
||||
path: filePath,
|
||||
oldText,
|
||||
newText,
|
||||
})
|
||||
}
|
||||
|
||||
if (part.tool === "todowrite") {
|
||||
const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
|
||||
if (parsedTodos.success) {
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId: acpSession.id,
|
||||
update: {
|
||||
sessionUpdate: "plan",
|
||||
entries: parsedTodos.data.map((todo) => {
|
||||
const status: PlanEntry["status"] =
|
||||
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
|
||||
return {
|
||||
priority: "medium",
|
||||
status,
|
||||
content: todo.content,
|
||||
}
|
||||
}),
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error("failed to send session update for todo", { error: err })
|
||||
})
|
||||
} else {
|
||||
log.error("failed to parse todo output", { error: parsedTodos.error })
|
||||
}
|
||||
}
|
||||
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId: acpSession.id,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: part.callID,
|
||||
status: "completed",
|
||||
kind,
|
||||
content,
|
||||
title: part.state.title,
|
||||
rawOutput: {
|
||||
output: part.state.output,
|
||||
metadata: part.state.metadata,
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error("failed to send tool completed to ACP", { error: err })
|
||||
})
|
||||
break
|
||||
case "error":
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId: acpSession.id,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: part.callID,
|
||||
status: "failed",
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: part.state.error,
|
||||
},
|
||||
},
|
||||
],
|
||||
rawOutput: {
|
||||
error: part.state.error,
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error("failed to send tool error to ACP", { error: err })
|
||||
})
|
||||
break
|
||||
}
|
||||
} else if (part.type === "text") {
|
||||
const delta = props.delta
|
||||
if (delta && part.synthetic !== true) {
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId: acpSession.id,
|
||||
update: {
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: {
|
||||
type: "text",
|
||||
text: delta,
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error("failed to send text to ACP", { error: err })
|
||||
})
|
||||
}
|
||||
} else if (part.type === "reasoning") {
|
||||
const delta = props.delta
|
||||
if (delta) {
|
||||
await this.connection
|
||||
.sessionUpdate({
|
||||
sessionId: acpSession.id,
|
||||
update: {
|
||||
sessionUpdate: "agent_thought_chunk",
|
||||
content: {
|
||||
type: "text",
|
||||
text: delta,
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error("failed to send reasoning to ACP", { error: err })
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async initialize(params: InitializeRequest) {
|
||||
log.info("initialize", { protocolVersion: params.protocolVersion })
|
||||
|
||||
return {
|
||||
protocolVersion: 1,
|
||||
agentCapabilities: {
|
||||
loadSession: true,
|
||||
mcpCapabilities: {
|
||||
http: true,
|
||||
sse: true,
|
||||
},
|
||||
promptCapabilities: {
|
||||
embeddedContext: true,
|
||||
image: true,
|
||||
},
|
||||
},
|
||||
authMethods: [
|
||||
{
|
||||
description: "Run `opencode auth login` in the terminal",
|
||||
name: "Login with opencode",
|
||||
id: "opencode-login",
|
||||
},
|
||||
],
|
||||
_meta: {
|
||||
opencode: {
|
||||
version: Installation.VERSION,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async authenticate(_params: AuthenticateRequest) {
|
||||
throw new Error("Authentication not implemented")
|
||||
}
|
||||
|
||||
async newSession(params: NewSessionRequest) {
|
||||
const model = await defaultModel(this.config)
|
||||
const session = await this.sessionManager.create(params.cwd, params.mcpServers, model)
|
||||
|
||||
log.info("creating_session", { mcpServers: params.mcpServers.length })
|
||||
const load = await this.loadSession({
|
||||
cwd: params.cwd,
|
||||
mcpServers: params.mcpServers,
|
||||
sessionId: session.id,
|
||||
})
|
||||
|
||||
return {
|
||||
sessionId: session.id,
|
||||
models: load.models,
|
||||
modes: load.modes,
|
||||
_meta: {},
|
||||
}
|
||||
}
|
||||
|
||||
async loadSession(params: LoadSessionRequest) {
|
||||
const model = await defaultModel(this.config)
|
||||
const sessionId = params.sessionId
|
||||
|
||||
const providers = await Provider.list()
|
||||
const entries = Object.entries(providers).sort((a, b) => {
|
||||
const nameA = a[1].info.name.toLowerCase()
|
||||
const nameB = b[1].info.name.toLowerCase()
|
||||
if (nameA < nameB) return -1
|
||||
if (nameA > nameB) return 1
|
||||
return 0
|
||||
})
|
||||
const availableModels = entries.flatMap(([providerID, provider]) => {
|
||||
const models = Provider.sort(Object.values(provider.info.models))
|
||||
return models.map((model) => ({
|
||||
modelId: `${providerID}/${model.id}`,
|
||||
name: `${provider.info.name}/${model.name}`,
|
||||
}))
|
||||
})
|
||||
|
||||
const availableCommands = (await Command.list()).map((command) => ({
|
||||
name: command.name,
|
||||
description: command.description ?? "",
|
||||
}))
|
||||
const names = new Set(availableCommands.map((c) => c.name))
|
||||
if (!names.has("init"))
|
||||
availableCommands.push({
|
||||
name: "init",
|
||||
description: "create/update a AGENTS.md",
|
||||
})
|
||||
if (!names.has("compact"))
|
||||
availableCommands.push({
|
||||
name: "compact",
|
||||
description: "compact the session",
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.connection.sessionUpdate({
|
||||
sessionId,
|
||||
update: {
|
||||
sessionUpdate: "available_commands_update",
|
||||
availableCommands,
|
||||
},
|
||||
})
|
||||
}, 0)
|
||||
|
||||
const availableModes = (await Agents.list())
|
||||
.filter((agent) => agent.mode !== "subagent")
|
||||
.map((agent) => ({
|
||||
id: agent.name,
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
}))
|
||||
|
||||
const currentModeId = availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id
|
||||
|
||||
const mcpServers: Record<string, Config.Mcp> = {}
|
||||
for (const server of params.mcpServers) {
|
||||
if ("type" in server) {
|
||||
mcpServers[server.name] = {
|
||||
url: server.url,
|
||||
headers: server.headers.reduce<Record<string, string>>((acc, { name, value }) => {
|
||||
acc[name] = value
|
||||
return acc
|
||||
}, {}),
|
||||
type: "remote",
|
||||
}
|
||||
} else {
|
||||
mcpServers[server.name] = {
|
||||
type: "local",
|
||||
command: [server.command, ...server.args],
|
||||
environment: server.env.reduce<Record<string, string>>((acc, { name, value }) => {
|
||||
acc[name] = value
|
||||
return acc
|
||||
}, {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(mcpServers).map(async ([key, mcp]) => {
|
||||
await MCP.add(key, mcp)
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
sessionId,
|
||||
models: {
|
||||
currentModelId: `${model.providerID}/${model.modelID}`,
|
||||
availableModels,
|
||||
},
|
||||
modes: {
|
||||
availableModes,
|
||||
currentModeId,
|
||||
},
|
||||
_meta: {},
|
||||
}
|
||||
}
|
||||
|
||||
async setSessionModel(params: SetSessionModelRequest) {
|
||||
const session = this.sessionManager.get(params.sessionId)
|
||||
if (!session) {
|
||||
throw new Error(`Session not found: ${params.sessionId}`)
|
||||
}
|
||||
|
||||
const parsed = Provider.parseModel(params.modelId)
|
||||
const model = await Provider.getModel(parsed.providerID, parsed.modelID)
|
||||
|
||||
this.sessionManager.setModel(session.id, {
|
||||
providerID: model.providerID,
|
||||
modelID: model.modelID,
|
||||
})
|
||||
|
||||
return {
|
||||
_meta: {},
|
||||
}
|
||||
}
|
||||
|
||||
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
|
||||
const session = this.sessionManager.get(params.sessionId)
|
||||
if (!session) {
|
||||
throw new Error(`Session not found: ${params.sessionId}`)
|
||||
}
|
||||
await Agents.get(params.modeId).then((agent) => {
|
||||
if (!agent) throw new Error(`Agent not found: ${params.modeId}`)
|
||||
})
|
||||
this.sessionManager.setMode(params.sessionId, params.modeId)
|
||||
}
|
||||
|
||||
async prompt(params: PromptRequest) {
|
||||
const sessionID = params.sessionId
|
||||
const acpSession = this.sessionManager.get(sessionID)
|
||||
if (!acpSession) {
|
||||
throw new Error(`Session not found: ${sessionID}`)
|
||||
}
|
||||
|
||||
const current = acpSession.model
|
||||
const model = current ?? (await defaultModel(this.config))
|
||||
if (!current) {
|
||||
this.sessionManager.setModel(acpSession.id, model)
|
||||
}
|
||||
const agent = acpSession.modeId ?? "build"
|
||||
|
||||
const parts: SessionPrompt.PromptInput["parts"] = []
|
||||
for (const part of params.prompt) {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
parts.push({
|
||||
type: "text" as const,
|
||||
text: part.text,
|
||||
})
|
||||
break
|
||||
case "image":
|
||||
if (part.data) {
|
||||
parts.push({
|
||||
type: "file",
|
||||
url: `data:${part.mimeType};base64,${part.data}`,
|
||||
mime: part.mimeType,
|
||||
})
|
||||
} else if (part.uri && part.uri.startsWith("http:")) {
|
||||
parts.push({
|
||||
type: "file",
|
||||
url: part.uri,
|
||||
mime: part.mimeType,
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case "resource_link":
|
||||
const parsed = parseUri(part.uri)
|
||||
parts.push(parsed)
|
||||
|
||||
break
|
||||
|
||||
case "resource":
|
||||
const resource = part.resource
|
||||
if ("text" in resource) {
|
||||
parts.push({
|
||||
type: "text",
|
||||
text: resource.text,
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.info("parts", { parts })
|
||||
|
||||
const cmd = (() => {
|
||||
const text = parts
|
||||
.filter((p) => p.type === "text")
|
||||
.map((p) => p.text)
|
||||
.join("")
|
||||
.trim()
|
||||
|
||||
if (!text.startsWith("/")) return
|
||||
|
||||
const [name, ...rest] = text.slice(1).split(/\s+/)
|
||||
return { name, args: rest.join(" ").trim() }
|
||||
})()
|
||||
|
||||
const done = {
|
||||
stopReason: "end_turn" as const,
|
||||
_meta: {},
|
||||
}
|
||||
|
||||
if (!cmd) {
|
||||
await SessionPrompt.prompt({
|
||||
sessionID,
|
||||
model: {
|
||||
providerID: model.providerID,
|
||||
modelID: model.modelID,
|
||||
},
|
||||
parts,
|
||||
agent,
|
||||
})
|
||||
return done
|
||||
}
|
||||
|
||||
const command = await Command.get(cmd.name)
|
||||
if (command) {
|
||||
await SessionPrompt.command({
|
||||
sessionID,
|
||||
command: command.name,
|
||||
arguments: cmd.args,
|
||||
model: model.providerID + "/" + model.modelID,
|
||||
agent,
|
||||
})
|
||||
return done
|
||||
}
|
||||
|
||||
switch (cmd.name) {
|
||||
case "init":
|
||||
await Session.initialize({
|
||||
sessionID,
|
||||
messageID: Identifier.ascending("message"),
|
||||
providerID: model.providerID,
|
||||
modelID: model.modelID,
|
||||
})
|
||||
break
|
||||
case "compact":
|
||||
await SessionCompaction.run({
|
||||
sessionID,
|
||||
providerID: model.providerID,
|
||||
modelID: model.modelID,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
return done
|
||||
}
|
||||
|
||||
async cancel(params: CancelNotification) {
|
||||
SessionLock.abort(params.sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
function toToolKind(toolName: string): ToolKind {
|
||||
const tool = toolName.toLocaleLowerCase()
|
||||
switch (tool) {
|
||||
case "bash":
|
||||
return "execute"
|
||||
case "webfetch":
|
||||
return "fetch"
|
||||
|
||||
case "edit":
|
||||
case "patch":
|
||||
case "write":
|
||||
return "edit"
|
||||
|
||||
case "grep":
|
||||
case "glob":
|
||||
case "context7_resolve_library_id":
|
||||
case "context7_get_library_docs":
|
||||
return "search"
|
||||
|
||||
case "list":
|
||||
case "read":
|
||||
return "read"
|
||||
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
||||
function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
|
||||
const tool = toolName.toLocaleLowerCase()
|
||||
switch (tool) {
|
||||
case "read":
|
||||
case "edit":
|
||||
case "write":
|
||||
return input["filePath"] ? [{ path: input["filePath"] }] : []
|
||||
case "glob":
|
||||
case "grep":
|
||||
return input["path"] ? [{ path: input["path"] }] : []
|
||||
case "bash":
|
||||
return []
|
||||
case "list":
|
||||
return input["path"] ? [{ path: input["path"] }] : []
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function defaultModel(config: ACPConfig) {
|
||||
const configured = config.defaultModel
|
||||
if (configured) return configured
|
||||
return Provider.defaultModel()
|
||||
}
|
||||
|
||||
function parseUri(
|
||||
uri: string,
|
||||
): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } {
|
||||
try {
|
||||
if (uri.startsWith("file://")) {
|
||||
const path = uri.slice(7)
|
||||
const name = path.split("/").pop() || path
|
||||
return {
|
||||
type: "file",
|
||||
url: uri,
|
||||
filename: name,
|
||||
mime: "text/plain",
|
||||
}
|
||||
}
|
||||
if (uri.startsWith("zed://")) {
|
||||
const url = new URL(uri)
|
||||
const path = url.searchParams.get("path")
|
||||
if (path) {
|
||||
const name = path.split("/").pop() || path
|
||||
return {
|
||||
type: "file",
|
||||
url: `file://${path}`,
|
||||
filename: name,
|
||||
mime: "text/plain",
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "text",
|
||||
text: uri,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
type: "text",
|
||||
text: uri,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { McpServer } from "@agentclientprotocol/sdk"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Session } from "../session"
|
||||
import { Provider } from "../provider/provider"
|
||||
import type { ACPSessionState } from "./types"
|
||||
|
||||
export class ACPSessionManager {
|
||||
private sessions = new Map<string, ACPSessionState>()
|
||||
|
||||
async create(cwd: string, mcpServers: McpServer[], model?: ACPSessionState["model"]): Promise<ACPSessionState> {
|
||||
const session = await Session.create({ title: `ACP Session ${crypto.randomUUID()}` })
|
||||
const sessionId = session.id
|
||||
const resolvedModel = model ?? (await Provider.defaultModel())
|
||||
|
||||
const state: ACPSessionState = {
|
||||
id: sessionId,
|
||||
cwd,
|
||||
mcpServers,
|
||||
createdAt: new Date(),
|
||||
model: resolvedModel,
|
||||
}
|
||||
|
||||
this.sessions.set(sessionId, state)
|
||||
return state
|
||||
}
|
||||
|
||||
get(sessionId: string) {
|
||||
return this.sessions.get(sessionId)
|
||||
}
|
||||
|
||||
async remove(sessionId: string) {
|
||||
const state = this.sessions.get(sessionId)
|
||||
if (!state) return
|
||||
|
||||
await Session.remove(sessionId).catch(() => {})
|
||||
this.sessions.delete(sessionId)
|
||||
}
|
||||
|
||||
has(sessionId: string) {
|
||||
return this.sessions.has(sessionId)
|
||||
}
|
||||
|
||||
getModel(sessionId: string) {
|
||||
const session = this.sessions.get(sessionId)
|
||||
if (!session) return
|
||||
return session.model
|
||||
}
|
||||
|
||||
setModel(sessionId: string, model: ACPSessionState["model"]) {
|
||||
const session = this.sessions.get(sessionId)
|
||||
if (!session) return
|
||||
session.model = model
|
||||
this.sessions.set(sessionId, session)
|
||||
return session
|
||||
}
|
||||
|
||||
setMode(sessionId: string, modeId: string) {
|
||||
const session = this.sessions.get(sessionId)
|
||||
if (!session) return
|
||||
session.modeId = modeId
|
||||
this.sessions.set(sessionId, session)
|
||||
return session
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { McpServer } from "@agentclientprotocol/sdk"
|
||||
|
||||
export interface ACPSessionState {
|
||||
id: string
|
||||
cwd: string
|
||||
mcpServers: McpServer[]
|
||||
createdAt: Date
|
||||
model: {
|
||||
providerID: string
|
||||
modelID: string
|
||||
}
|
||||
modeId?: string
|
||||
}
|
||||
|
||||
export interface ACPConfig {
|
||||
defaultModel?: {
|
||||
providerID: string
|
||||
modelID: string
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Config } from "../config/config"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Provider } from "../provider/provider"
|
||||
import { generateObject, type ModelMessage } from "ai"
|
||||
import PROMPT_GENERATE from "./generate.txt"
|
||||
@@ -51,7 +51,44 @@ export namespace Agent {
|
||||
const planPermission = mergeAgentPermissions(
|
||||
{
|
||||
edit: "deny",
|
||||
bash: "ask",
|
||||
bash: {
|
||||
"cut*": "allow",
|
||||
"diff*": "allow",
|
||||
"du*": "allow",
|
||||
"file *": "allow",
|
||||
"find * -delete*": "ask",
|
||||
"find * -exec*": "ask",
|
||||
"find * -fprint*": "ask",
|
||||
"find * -fls*": "ask",
|
||||
"find * -fprintf*": "ask",
|
||||
"find * -ok*": "ask",
|
||||
"find *": "allow",
|
||||
"git diff*": "allow",
|
||||
"git log*": "allow",
|
||||
"git show*": "allow",
|
||||
"git status*": "allow",
|
||||
"git branch": "allow",
|
||||
"git branch -v": "allow",
|
||||
"grep*": "allow",
|
||||
"head*": "allow",
|
||||
"less*": "allow",
|
||||
"ls*": "allow",
|
||||
"more*": "allow",
|
||||
"pwd*": "allow",
|
||||
"rg*": "allow",
|
||||
"sort --output=*": "ask",
|
||||
"sort -o *": "ask",
|
||||
"sort*": "allow",
|
||||
"stat*": "allow",
|
||||
"tail*": "allow",
|
||||
"tree -o *": "ask",
|
||||
"tree*": "allow",
|
||||
"uniq*": "allow",
|
||||
"wc*": "allow",
|
||||
"whereis*": "allow",
|
||||
"which*": "allow",
|
||||
"*": "ask",
|
||||
},
|
||||
webfetch: "allow",
|
||||
},
|
||||
cfg.permission ?? {},
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import z from "zod/v4"
|
||||
import { Auth } from "./index"
|
||||
import { NamedError } from "../util/error"
|
||||
|
||||
export namespace AuthGithubCopilot {
|
||||
const CLIENT_ID = "Iv1.b507a08c87ecfe98"
|
||||
const DEVICE_CODE_URL = "https://github.com/login/device/code"
|
||||
const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"
|
||||
const COPILOT_API_KEY_URL = "https://api.github.com/copilot_internal/v2/token"
|
||||
|
||||
interface DeviceCodeResponse {
|
||||
device_code: string
|
||||
user_code: string
|
||||
verification_uri: string
|
||||
expires_in: number
|
||||
interval: number
|
||||
}
|
||||
|
||||
interface AccessTokenResponse {
|
||||
access_token?: string
|
||||
error?: string
|
||||
error_description?: string
|
||||
}
|
||||
|
||||
interface CopilotTokenResponse {
|
||||
token: string
|
||||
expires_at: number
|
||||
refresh_in: number
|
||||
endpoints: {
|
||||
api: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function authorize() {
|
||||
const deviceResponse = await fetch(DEVICE_CODE_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "GitHubCopilotChat/0.26.7",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
client_id: CLIENT_ID,
|
||||
scope: "read:user",
|
||||
}),
|
||||
})
|
||||
const deviceData: DeviceCodeResponse = await deviceResponse.json()
|
||||
return {
|
||||
device: deviceData.device_code,
|
||||
user: deviceData.user_code,
|
||||
verification: deviceData.verification_uri,
|
||||
interval: deviceData.interval || 5,
|
||||
expiry: deviceData.expires_in,
|
||||
}
|
||||
}
|
||||
|
||||
export async function poll(device_code: string) {
|
||||
const response = await fetch(ACCESS_TOKEN_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "GitHubCopilotChat/0.26.7",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
client_id: CLIENT_ID,
|
||||
device_code,
|
||||
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) return "failed"
|
||||
|
||||
const data: AccessTokenResponse = await response.json()
|
||||
|
||||
if (data.access_token) {
|
||||
// Store the GitHub OAuth token
|
||||
await Auth.set("github-copilot", {
|
||||
type: "oauth",
|
||||
refresh: data.access_token,
|
||||
access: "",
|
||||
expires: 0,
|
||||
})
|
||||
return "complete"
|
||||
}
|
||||
|
||||
if (data.error === "authorization_pending") return "pending"
|
||||
|
||||
if (data.error) return "failed"
|
||||
|
||||
return "pending"
|
||||
}
|
||||
|
||||
export async function access() {
|
||||
const info = await Auth.get("github-copilot")
|
||||
if (!info || info.type !== "oauth") return
|
||||
if (info.access && info.expires > Date.now()) return info.access
|
||||
|
||||
// Get new Copilot API token
|
||||
const response = await fetch(COPILOT_API_KEY_URL, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${info.refresh}`,
|
||||
"User-Agent": "GitHubCopilotChat/0.26.7",
|
||||
"Editor-Version": "vscode/1.99.3",
|
||||
"Editor-Plugin-Version": "copilot-chat/0.26.7",
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) return
|
||||
|
||||
const tokenData: CopilotTokenResponse = await response.json()
|
||||
|
||||
// Store the Copilot API token
|
||||
await Auth.set("github-copilot", {
|
||||
type: "oauth",
|
||||
refresh: info.refresh,
|
||||
access: tokenData.token,
|
||||
expires: tokenData.expires_at * 1000,
|
||||
})
|
||||
|
||||
return tokenData.token
|
||||
}
|
||||
|
||||
export const DeviceCodeError = NamedError.create("DeviceCodeError", z.object({}))
|
||||
|
||||
export const TokenExchangeError = NamedError.create(
|
||||
"TokenExchangeError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
export const AuthenticationError = NamedError.create(
|
||||
"AuthenticationError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
export const CopilotTokenError = NamedError.create(
|
||||
"CopilotTokenError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "path"
|
||||
import { Global } from "../global"
|
||||
import fs from "fs/promises"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
|
||||
export namespace Auth {
|
||||
export const Oauth = z
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Global } from "../global"
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import z from "zod/v4"
|
||||
import type { ZodType } from "zod/v4"
|
||||
import z from "zod"
|
||||
import type { ZodType } from "zod"
|
||||
import { Log } from "../util/log"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Log } from "@/util/log"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { cmd } from "./cmd"
|
||||
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
||||
import { ACP } from "@/acp/agent"
|
||||
|
||||
const log = Log.create({ service: "acp-command" })
|
||||
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
log.error("Unhandled rejection", {
|
||||
promise,
|
||||
reason,
|
||||
})
|
||||
})
|
||||
|
||||
export const AcpCommand = cmd({
|
||||
command: "acp",
|
||||
describe: "Start ACP (Agent Client Protocol) server",
|
||||
builder: (yargs) => {
|
||||
return yargs.option("cwd", {
|
||||
describe: "working directory",
|
||||
type: "string",
|
||||
default: process.cwd(),
|
||||
})
|
||||
},
|
||||
handler: async (opts) => {
|
||||
if (opts.cwd) process.chdir(opts["cwd"])
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const input = new WritableStream<Uint8Array>({
|
||||
write(chunk) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
process.stdout.write(chunk, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
const output = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
process.stdin.on("data", (chunk: Buffer) => {
|
||||
controller.enqueue(new Uint8Array(chunk))
|
||||
})
|
||||
process.stdin.on("end", () => controller.close())
|
||||
process.stdin.on("error", (err) => controller.error(err))
|
||||
},
|
||||
})
|
||||
|
||||
const stream = ndJsonStream(input, output)
|
||||
|
||||
new AgentSideConnection((conn) => {
|
||||
return new ACP.Agent(conn)
|
||||
}, stream)
|
||||
|
||||
log.info("setup connection")
|
||||
})
|
||||
process.stdin.resume()
|
||||
},
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EOL } from "os"
|
||||
import { Config } from "../../../config/config"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
@@ -8,7 +9,7 @@ export const ConfigCommand = cmd({
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const config = await Config.get()
|
||||
console.log(JSON.stringify(config, null, 2))
|
||||
process.stdout.write(JSON.stringify(config, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EOL } from "os"
|
||||
import { File } from "../../../file"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
@@ -13,7 +14,7 @@ const FileSearchCommand = cmd({
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const results = await File.search({ query: args.query })
|
||||
console.log(results.join("\n"))
|
||||
process.stdout.write(results.join(EOL) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -29,7 +30,7 @@ const FileReadCommand = cmd({
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const content = await File.read(args.path)
|
||||
console.log(content)
|
||||
process.stdout.write(JSON.stringify(content, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -40,7 +41,7 @@ const FileStatusCommand = cmd({
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const status = await File.status()
|
||||
console.log(JSON.stringify(status, null, 2))
|
||||
process.stdout.write(JSON.stringify(status, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -56,7 +57,7 @@ const FileListCommand = cmd({
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const files = await File.list(args.path)
|
||||
console.log(JSON.stringify(files, null, 2))
|
||||
process.stdout.write(JSON.stringify(files, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import { LSP } from "../../../lsp"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
import { Log } from "../../../util/log"
|
||||
import { EOL } from "os"
|
||||
|
||||
export const LSPCommand = cmd({
|
||||
command: "lsp",
|
||||
@@ -16,7 +17,7 @@ const DiagnosticsCommand = cmd({
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
await LSP.touchFile(args.file, true)
|
||||
console.log(JSON.stringify(await LSP.diagnostics(), null, 2))
|
||||
process.stdout.write(JSON.stringify(await LSP.diagnostics(), null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -28,7 +29,7 @@ export const SymbolsCommand = cmd({
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
using _ = Log.Default.time("symbols")
|
||||
const results = await LSP.workspaceSymbol(args.query)
|
||||
console.log(JSON.stringify(results, null, 2))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -40,7 +41,7 @@ export const DocumentSymbolsCommand = cmd({
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
using _ = Log.Default.time("document-symbols")
|
||||
const results = await LSP.documentSymbol(args.uri)
|
||||
console.log(JSON.stringify(results, null, 2))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EOL } from "os"
|
||||
import { Ripgrep } from "../../../file/ripgrep"
|
||||
import { Instance } from "../../../project/instance"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
@@ -17,7 +18,7 @@ const TreeCommand = cmd({
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
console.log(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit }))
|
||||
process.stdout.write(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit }) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -48,7 +49,7 @@ const FilesCommand = cmd({
|
||||
files.push(file)
|
||||
if (args.limit && files.length >= args.limit) break
|
||||
}
|
||||
console.log(files.join("\n"))
|
||||
process.stdout.write(files.join(EOL) + EOL)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -77,6 +78,6 @@ const SearchCommand = cmd({
|
||||
glob: args.glob as string[] | undefined,
|
||||
limit: args.limit,
|
||||
})
|
||||
console.log(JSON.stringify(results, null, 2))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EOL } from "os"
|
||||
import { Project } from "../../../project/project"
|
||||
import { Log } from "../../../util/log"
|
||||
import { cmd } from "../cmd"
|
||||
@@ -8,7 +9,7 @@ export const ScrapCommand = cmd({
|
||||
async handler() {
|
||||
const timer = Log.Default.time("scrap")
|
||||
const list = await Project.list()
|
||||
console.log(list)
|
||||
process.stdout.write(JSON.stringify(list, null, 2) + EOL)
|
||||
timer.stop()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import { cmd } from "./cmd"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { UI } from "../ui"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { EOL } from "os"
|
||||
|
||||
export const ExportCommand = cmd({
|
||||
command: "export [sessionID]",
|
||||
@@ -67,7 +68,7 @@ export const ExportCommand = cmd({
|
||||
}
|
||||
|
||||
process.stdout.write(JSON.stringify(exportData, null, 2))
|
||||
process.stdout.write("\n")
|
||||
process.stdout.write(EOL)
|
||||
} catch (error) {
|
||||
UI.error(`Session not found: ${sessionID!}`)
|
||||
process.exit(1)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Argv } from "yargs"
|
||||
import path from "path"
|
||||
import { Bus } from "../../bus"
|
||||
import { Provider } from "../../provider/provider"
|
||||
import { Session } from "../../session"
|
||||
@@ -12,6 +13,7 @@ import { Identifier } from "../../id/id"
|
||||
import { Agent } from "../../agent/agent"
|
||||
import { Command } from "../../command"
|
||||
import { SessionPrompt } from "../../session/prompt"
|
||||
import { EOL } from "os"
|
||||
|
||||
const TOOL: Record<string, [string, string]> = {
|
||||
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
||||
@@ -70,10 +72,49 @@ export const RunCommand = cmd({
|
||||
default: "default",
|
||||
describe: "format: default (formatted) or json (raw JSON events)",
|
||||
})
|
||||
.option("file", {
|
||||
alias: ["f"],
|
||||
type: "string",
|
||||
array: true,
|
||||
describe: "file(s) to attach to message",
|
||||
})
|
||||
.option("title", {
|
||||
type: "string",
|
||||
describe: "title for the session (uses truncated prompt if no value provided)",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
let message = args.message.join(" ")
|
||||
|
||||
let fileParts: any[] = []
|
||||
if (args.file) {
|
||||
const files = Array.isArray(args.file) ? args.file : [args.file]
|
||||
|
||||
for (const filePath of files) {
|
||||
const resolvedPath = path.resolve(process.cwd(), filePath)
|
||||
const file = Bun.file(resolvedPath)
|
||||
const stats = await file.stat().catch(() => {})
|
||||
if (!stats) {
|
||||
UI.error(`File not found: ${filePath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (!(await file.exists())) {
|
||||
UI.error(`File not found: ${filePath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const stat = await file.stat()
|
||||
const mime = stat.isDirectory() ? "application/x-directory" : "text/plain"
|
||||
|
||||
fileParts.push({
|
||||
type: "file",
|
||||
url: `file://${resolvedPath}`,
|
||||
filename: path.basename(resolvedPath),
|
||||
mime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
|
||||
|
||||
if (message.trim().length === 0 && !args.command) {
|
||||
@@ -106,7 +147,19 @@ export const RunCommand = cmd({
|
||||
|
||||
if (args.session) return Session.get(args.session)
|
||||
|
||||
return Session.create({})
|
||||
const title = (() => {
|
||||
if (args.title !== undefined) {
|
||||
if (args.title === "") {
|
||||
return message.slice(0, 50) + (message.length > 50 ? "..." : "")
|
||||
}
|
||||
return args.title
|
||||
}
|
||||
return undefined
|
||||
})()
|
||||
|
||||
return Session.create({
|
||||
title,
|
||||
})
|
||||
})()
|
||||
|
||||
if (!session) {
|
||||
@@ -158,13 +211,12 @@ export const RunCommand = cmd({
|
||||
sessionID: session?.id,
|
||||
...data,
|
||||
}
|
||||
process.stdout.write(JSON.stringify(jsonEvent) + "\n")
|
||||
process.stdout.write(JSON.stringify(jsonEvent) + EOL)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let text = ""
|
||||
const messageID = Identifier.ascending("message")
|
||||
|
||||
Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
|
||||
@@ -196,15 +248,14 @@ export const RunCommand = cmd({
|
||||
}
|
||||
|
||||
if (part.type === "text") {
|
||||
text = part.text
|
||||
const text = part.text
|
||||
const isPiped = !process.stdout.isTTY
|
||||
|
||||
if (part.time?.end) {
|
||||
if (outputJsonEvent("text", { part })) return
|
||||
UI.empty()
|
||||
UI.println(UI.markdown(text))
|
||||
UI.empty()
|
||||
text = ""
|
||||
return
|
||||
if (!isPiped) UI.println()
|
||||
process.stdout.write((isPiped ? text : UI.markdown(text)) + EOL)
|
||||
if (!isPiped) UI.println()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -218,13 +269,13 @@ export const RunCommand = cmd({
|
||||
if ("data" in error && error.data && "message" in error.data) {
|
||||
err = error.data.message
|
||||
}
|
||||
errorMsg = errorMsg ? errorMsg + "\n" + err : err
|
||||
errorMsg = errorMsg ? errorMsg + EOL + err : err
|
||||
|
||||
if (outputJsonEvent("error", { error })) return
|
||||
UI.error(err)
|
||||
})
|
||||
|
||||
const result = await (async () => {
|
||||
await (async () => {
|
||||
if (args.command) {
|
||||
return await SessionPrompt.command({
|
||||
messageID,
|
||||
@@ -244,6 +295,7 @@ export const RunCommand = cmd({
|
||||
},
|
||||
agent: agent.name,
|
||||
parts: [
|
||||
...fileParts,
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
type: "text",
|
||||
@@ -252,15 +304,6 @@ export const RunCommand = cmd({
|
||||
],
|
||||
})
|
||||
})()
|
||||
|
||||
const isPiped = !process.stdout.isTTY
|
||||
if (isPiped) {
|
||||
const match = result.parts.findLast((x: any) => x.type === "text") as any
|
||||
if (outputJsonEvent("text", { text: match })) return
|
||||
if (match) process.stdout.write(UI.markdown(match.text))
|
||||
if (errorMsg) process.stdout.write(errorMsg)
|
||||
}
|
||||
UI.empty()
|
||||
if (errorMsg) process.exit(1)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -157,7 +157,7 @@ export const TuiCommand = cmd({
|
||||
|
||||
;(async () => {
|
||||
// if (Installation.isLocal()) return
|
||||
const config = await Config.global()
|
||||
const config = await Config.get()
|
||||
if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return
|
||||
const latest = await Installation.latest().catch(() => {})
|
||||
if (!latest) return
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { EOL } from "os"
|
||||
import { NamedError } from "../util/error"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Config } from "../config/config"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { ModelsDev } from "../provider/models"
|
||||
import { mergeDeep, pipe } from "remeda"
|
||||
@@ -12,7 +12,11 @@ import { NamedError } from "../util/error"
|
||||
import matter from "gray-matter"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Auth } from "../auth"
|
||||
import { type ParseError as JsoncParseError, parse as parseJsonc, printParseErrorCode } from "jsonc-parser"
|
||||
import {
|
||||
type ParseError as JsoncParseError,
|
||||
parse as parseJsonc,
|
||||
printParseErrorCode,
|
||||
} from "jsonc-parser"
|
||||
import { Instance } from "../project/instance"
|
||||
import { LSPServer } from "../lsp/server"
|
||||
import { BunProc } from "@/bun"
|
||||
@@ -46,7 +50,10 @@ export namespace Config {
|
||||
if (value.type === "wellknown") {
|
||||
process.env[value.key] = value.token
|
||||
const wellknown = await fetch(`${key}/.well-known/opencode`).then((x) => x.json())
|
||||
result = mergeDeep(result, await load(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
result = mergeDeep(
|
||||
result,
|
||||
await load(JSON.stringify(wellknown.config ?? {}), process.cwd()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +64,19 @@ export namespace Config {
|
||||
const directories = [
|
||||
Global.Path.config,
|
||||
...(await Array.fromAsync(
|
||||
Filesystem.up({ targets: [".opencode"], start: Instance.directory, stop: Instance.worktree }),
|
||||
Filesystem.up({
|
||||
targets: [".opencode"],
|
||||
start: Instance.directory,
|
||||
stop: Instance.worktree,
|
||||
}),
|
||||
)),
|
||||
]
|
||||
|
||||
if (Flag.OPENCODE_CONFIG_DIR) {
|
||||
directories.push(Flag.OPENCODE_CONFIG_DIR)
|
||||
log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
|
||||
}
|
||||
|
||||
for (const dir of directories) {
|
||||
await assertValid(dir)
|
||||
installDependencies(dir)
|
||||
@@ -148,10 +164,18 @@ export namespace Config {
|
||||
|
||||
const gitignore = path.join(dir, ".gitignore")
|
||||
const hasGitIgnore = await Bun.file(gitignore).exists()
|
||||
if (!hasGitIgnore) await Bun.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n"))
|
||||
if (!hasGitIgnore)
|
||||
await Bun.write(
|
||||
gitignore,
|
||||
["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n"),
|
||||
)
|
||||
|
||||
await BunProc.run(
|
||||
["add", "@opencode-ai/plugin@" + (Installation.isLocal() ? "latest" : Installation.VERSION), "--exact"],
|
||||
[
|
||||
"add",
|
||||
"@opencode-ai/plugin@" + (Installation.isLocal() ? "latest" : Installation.VERSION),
|
||||
"--exact",
|
||||
],
|
||||
{
|
||||
cwd: dir,
|
||||
},
|
||||
@@ -161,7 +185,12 @@ export namespace Config {
|
||||
const COMMAND_GLOB = new Bun.Glob("command/**/*.md")
|
||||
async function loadCommand(dir: string) {
|
||||
const result: Record<string, Command> = {}
|
||||
for await (const item of COMMAND_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) {
|
||||
for await (const item of COMMAND_GLOB.scan({
|
||||
absolute: true,
|
||||
followSymlinks: true,
|
||||
dot: true,
|
||||
cwd: dir,
|
||||
})) {
|
||||
const content = await Bun.file(item).text()
|
||||
const md = matter(content)
|
||||
if (!md.data) continue
|
||||
@@ -196,7 +225,12 @@ export namespace Config {
|
||||
async function loadAgent(dir: string) {
|
||||
const result: Record<string, Agent> = {}
|
||||
|
||||
for await (const item of AGENT_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) {
|
||||
for await (const item of AGENT_GLOB.scan({
|
||||
absolute: true,
|
||||
followSymlinks: true,
|
||||
dot: true,
|
||||
cwd: dir,
|
||||
})) {
|
||||
const content = await Bun.file(item).text()
|
||||
const md = matter(content)
|
||||
if (!md.data) continue
|
||||
@@ -234,7 +268,12 @@ export namespace Config {
|
||||
const MODE_GLOB = new Bun.Glob("mode/*.md")
|
||||
async function loadMode(dir: string) {
|
||||
const result: Record<string, Agent> = {}
|
||||
for await (const item of MODE_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) {
|
||||
for await (const item of MODE_GLOB.scan({
|
||||
absolute: true,
|
||||
followSymlinks: true,
|
||||
dot: true,
|
||||
cwd: dir,
|
||||
})) {
|
||||
const content = await Bun.file(item).text()
|
||||
const md = matter(content)
|
||||
if (!md.data) continue
|
||||
@@ -260,7 +299,12 @@ export namespace Config {
|
||||
async function loadPlugin(dir: string) {
|
||||
const plugins: string[] = []
|
||||
|
||||
for await (const item of PLUGIN_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) {
|
||||
for await (const item of PLUGIN_GLOB.scan({
|
||||
absolute: true,
|
||||
followSymlinks: true,
|
||||
dot: true,
|
||||
cwd: dir,
|
||||
})) {
|
||||
plugins.push("file://" + item)
|
||||
}
|
||||
return plugins
|
||||
@@ -275,6 +319,14 @@ export namespace Config {
|
||||
.optional()
|
||||
.describe("Environment variables to set when running the MCP server"),
|
||||
enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
|
||||
timeout: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe(
|
||||
"Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.",
|
||||
),
|
||||
})
|
||||
.strict()
|
||||
.meta({
|
||||
@@ -286,7 +338,18 @@ export namespace Config {
|
||||
type: z.literal("remote").describe("Type of MCP server connection"),
|
||||
url: z.string().describe("URL of the remote MCP server"),
|
||||
enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
|
||||
headers: z.record(z.string(), z.string()).optional().describe("Headers to send with the request"),
|
||||
headers: z
|
||||
.record(z.string(), z.string())
|
||||
.optional()
|
||||
.describe("Headers to send with the request"),
|
||||
timeout: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe(
|
||||
"Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.",
|
||||
),
|
||||
})
|
||||
.strict()
|
||||
.meta({
|
||||
@@ -334,72 +397,148 @@ export namespace Config {
|
||||
|
||||
export const Keybinds = z
|
||||
.object({
|
||||
leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
|
||||
leader: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("ctrl+x")
|
||||
.describe("Leader key for keybind combinations"),
|
||||
app_help: z.string().optional().default("<leader>h").describe("Show help dialog"),
|
||||
app_exit: z.string().optional().default("ctrl+c,<leader>q").describe("Exit the application"),
|
||||
editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
|
||||
theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
|
||||
project_init: z.string().optional().default("<leader>i").describe("Create/update AGENTS.md"),
|
||||
tool_details: z.string().optional().default("<leader>d").describe("Toggle tool details"),
|
||||
thinking_blocks: z.string().optional().default("<leader>b").describe("Toggle thinking blocks"),
|
||||
session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
|
||||
thinking_blocks: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("<leader>b")
|
||||
.describe("Toggle thinking blocks"),
|
||||
session_export: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("<leader>x")
|
||||
.describe("Export session to editor"),
|
||||
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
|
||||
session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
|
||||
session_timeline: z.string().optional().default("<leader>g").describe("Show session timeline"),
|
||||
session_timeline: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("<leader>g")
|
||||
.describe("Show session timeline"),
|
||||
session_share: z.string().optional().default("<leader>s").describe("Share current session"),
|
||||
session_unshare: z.string().optional().default("none").describe("Unshare current session"),
|
||||
session_interrupt: z.string().optional().default("esc").describe("Interrupt current session"),
|
||||
session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
|
||||
session_child_cycle: z.string().optional().default("ctrl+right").describe("Cycle to next child session"),
|
||||
session_child_cycle: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("ctrl+right")
|
||||
.describe("Cycle to next child session"),
|
||||
session_child_cycle_reverse: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("ctrl+left")
|
||||
.describe("Cycle to previous child session"),
|
||||
messages_page_up: z.string().optional().default("pgup").describe("Scroll messages up by one page"),
|
||||
messages_page_down: z.string().optional().default("pgdown").describe("Scroll messages down by one page"),
|
||||
messages_half_page_up: z.string().optional().default("ctrl+alt+u").describe("Scroll messages up by half page"),
|
||||
messages_page_up: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("pgup")
|
||||
.describe("Scroll messages up by one page"),
|
||||
messages_page_down: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("pgdown")
|
||||
.describe("Scroll messages down by one page"),
|
||||
messages_half_page_up: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("ctrl+alt+u")
|
||||
.describe("Scroll messages up by half page"),
|
||||
messages_half_page_down: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("ctrl+alt+d")
|
||||
.describe("Scroll messages down by half page"),
|
||||
messages_first: z.string().optional().default("ctrl+g").describe("Navigate to first message"),
|
||||
messages_last: z.string().optional().default("ctrl+alt+g").describe("Navigate to last message"),
|
||||
messages_last: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("ctrl+alt+g")
|
||||
.describe("Navigate to last message"),
|
||||
messages_copy: z.string().optional().default("<leader>y").describe("Copy message"),
|
||||
messages_undo: z.string().optional().default("<leader>u").describe("Undo message"),
|
||||
messages_redo: z.string().optional().default("<leader>r").describe("Redo message"),
|
||||
model_list: z.string().optional().default("<leader>m").describe("List available models"),
|
||||
model_cycle_recent: z.string().optional().default("f2").describe("Next recent model"),
|
||||
model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recent model"),
|
||||
model_cycle_recent_reverse: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("shift+f2")
|
||||
.describe("Previous recent model"),
|
||||
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
||||
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
||||
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
||||
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
|
||||
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
|
||||
input_submit: z.string().optional().default("enter").describe("Submit input"),
|
||||
input_newline: z.string().optional().default("shift+enter,ctrl+j").describe("Insert newline in input"),
|
||||
input_newline: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("shift+enter,ctrl+j")
|
||||
.describe("Insert newline in input"),
|
||||
// Deprecated commands
|
||||
switch_mode: z.string().optional().default("none").describe("@deprecated use agent_cycle. Next mode"),
|
||||
switch_mode: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated use agent_cycle. Next mode"),
|
||||
switch_mode_reverse: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated use agent_cycle_reverse. Previous mode"),
|
||||
switch_agent: z.string().optional().default("tab").describe("@deprecated use agent_cycle. Next agent"),
|
||||
switch_agent: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("tab")
|
||||
.describe("@deprecated use agent_cycle. Next agent"),
|
||||
switch_agent_reverse: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("shift+tab")
|
||||
.describe("@deprecated use agent_cycle_reverse. Previous agent"),
|
||||
file_list: z.string().optional().default("none").describe("@deprecated Currently not available. List files"),
|
||||
file_list: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated Currently not available. List files"),
|
||||
file_close: z.string().optional().default("none").describe("@deprecated Close file"),
|
||||
file_search: z.string().optional().default("none").describe("@deprecated Search file"),
|
||||
file_diff_toggle: z.string().optional().default("none").describe("@deprecated Split/unified diff"),
|
||||
messages_previous: z.string().optional().default("none").describe("@deprecated Navigate to previous message"),
|
||||
messages_next: z.string().optional().default("none").describe("@deprecated Navigate to next message"),
|
||||
messages_layout_toggle: z.string().optional().default("none").describe("@deprecated Toggle layout"),
|
||||
messages_revert: z.string().optional().default("none").describe("@deprecated use messages_undo. Revert message"),
|
||||
file_diff_toggle: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated Split/unified diff"),
|
||||
messages_previous: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated Navigate to previous message"),
|
||||
messages_next: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated Navigate to next message"),
|
||||
messages_layout_toggle: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated Toggle layout"),
|
||||
messages_revert: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated use messages_undo. Revert message"),
|
||||
})
|
||||
.strict()
|
||||
.meta({
|
||||
@@ -441,13 +580,23 @@ export namespace Config {
|
||||
autoshare: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("@deprecated Use 'share' field instead. Share newly created sessions automatically"),
|
||||
.describe(
|
||||
"@deprecated Use 'share' field instead. Share newly created sessions automatically",
|
||||
),
|
||||
autoupdate: z.boolean().optional().describe("Automatically update to the latest version"),
|
||||
disabled_providers: z.array(z.string()).optional().describe("Disable providers that are loaded automatically"),
|
||||
model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(),
|
||||
disabled_providers: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe("Disable providers that are loaded automatically"),
|
||||
model: z
|
||||
.string()
|
||||
.describe("Model to use in the format of provider/model, eg anthropic/claude-2")
|
||||
.optional(),
|
||||
small_model: z
|
||||
.string()
|
||||
.describe("Small model to use for tasks like title generation in the format of provider/model")
|
||||
.describe(
|
||||
"Small model to use for tasks like title generation in the format of provider/model",
|
||||
)
|
||||
.optional(),
|
||||
username: z
|
||||
.string()
|
||||
@@ -503,7 +652,10 @@ export namespace Config {
|
||||
)
|
||||
.optional()
|
||||
.describe("Custom provider configurations and model overrides"),
|
||||
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
|
||||
mcp: z
|
||||
.record(z.string(), Mcp)
|
||||
.optional()
|
||||
.describe("MCP (Model Context Protocol) server configurations"),
|
||||
formatter: z
|
||||
.record(
|
||||
z.string(),
|
||||
@@ -547,7 +699,10 @@ export namespace Config {
|
||||
error: "For custom LSP servers, 'extensions' array is required.",
|
||||
},
|
||||
),
|
||||
instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
|
||||
instructions: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe("Additional instruction files or patterns to include"),
|
||||
layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
|
||||
permission: z
|
||||
.object({
|
||||
@@ -581,6 +736,10 @@ export namespace Config {
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
chatMaxRetries: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Number of retries for chat completions on failure"),
|
||||
disable_paste_summary: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
@@ -610,7 +769,10 @@ export namespace Config {
|
||||
if (provider && model) result.model = `${provider}/${model}`
|
||||
result["$schema"] = "https://opencode.ai/config.json"
|
||||
result = mergeDeep(result, rest)
|
||||
await Bun.write(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
|
||||
await Bun.write(
|
||||
path.join(Global.Path.config, "config.json"),
|
||||
JSON.stringify(result, null, 2),
|
||||
)
|
||||
await fs.unlink(path.join(Global.Path.config, "config"))
|
||||
})
|
||||
.catch(() => {})
|
||||
@@ -649,7 +811,9 @@ export namespace Config {
|
||||
if (filePath.startsWith("~/")) {
|
||||
filePath = path.join(os.homedir(), filePath.slice(2))
|
||||
}
|
||||
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
|
||||
const resolvedPath = path.isAbsolute(filePath)
|
||||
? filePath
|
||||
: path.resolve(configDir, filePath)
|
||||
const fileContent = (
|
||||
await Bun.file(resolvedPath)
|
||||
.text()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "path"
|
||||
import { Global } from "../global"
|
||||
import fs from "fs/promises"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { NamedError } from "../util/error"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { Log } from "../util/log"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Bus } from "../bus"
|
||||
import { $ } from "bun"
|
||||
import type { BunFile } from "bun"
|
||||
import { formatPatch, structuredPatch } from "diff"
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
@@ -41,6 +42,7 @@ export namespace File {
|
||||
|
||||
export const Content = z
|
||||
.object({
|
||||
type: z.literal("text"),
|
||||
content: z.string(),
|
||||
diff: z.string().optional(),
|
||||
patch: z
|
||||
@@ -61,12 +63,53 @@ export namespace File {
|
||||
index: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
encoding: z.literal("base64").optional(),
|
||||
mimeType: z.string().optional(),
|
||||
})
|
||||
.meta({
|
||||
ref: "FileContent",
|
||||
})
|
||||
export type Content = z.infer<typeof Content>
|
||||
|
||||
async function shouldEncode(file: BunFile): Promise<boolean> {
|
||||
const type = file.type?.toLowerCase()
|
||||
if (!type) return false
|
||||
|
||||
if (type.startsWith("text/")) return false
|
||||
if (type.includes("charset=")) return false
|
||||
|
||||
const parts = type.split("/", 2)
|
||||
const top = parts[0]
|
||||
const rest = parts[1] ?? ""
|
||||
const sub = rest.split(";", 1)[0]
|
||||
|
||||
const tops = ["image", "audio", "video", "font", "model", "multipart"]
|
||||
if (tops.includes(top)) return true
|
||||
|
||||
if (type === "application/octet-stream") return true
|
||||
|
||||
const bins = [
|
||||
"zip",
|
||||
"gzip",
|
||||
"bzip",
|
||||
"compressed",
|
||||
"binary",
|
||||
"stream",
|
||||
"pdf",
|
||||
"msword",
|
||||
"powerpoint",
|
||||
"excel",
|
||||
"ogg",
|
||||
"exe",
|
||||
"dmg",
|
||||
"iso",
|
||||
"rar",
|
||||
]
|
||||
if (bins.some((mark) => sub.includes(mark))) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export const Event = {
|
||||
Edited: Bus.event(
|
||||
"file.edited",
|
||||
@@ -188,14 +231,30 @@ export namespace File {
|
||||
}))
|
||||
}
|
||||
|
||||
export async function read(file: string) {
|
||||
export async function read(file: string): Promise<Content> {
|
||||
using _ = log.time("read", { file })
|
||||
const project = Instance.project
|
||||
const full = path.join(Instance.directory, file)
|
||||
const content = await Bun.file(full)
|
||||
const bunFile = Bun.file(full)
|
||||
|
||||
if (!(await bunFile.exists())) {
|
||||
return { type: "text", content: "" }
|
||||
}
|
||||
|
||||
const encode = await shouldEncode(bunFile)
|
||||
|
||||
if (encode) {
|
||||
const buffer = await bunFile.arrayBuffer().catch(() => new ArrayBuffer(0))
|
||||
const content = Buffer.from(buffer).toString("base64")
|
||||
const mimeType = bunFile.type || "application/octet-stream"
|
||||
return { type: "text", content, mimeType, encoding: "base64" }
|
||||
}
|
||||
|
||||
const content = await bunFile
|
||||
.text()
|
||||
.catch(() => "")
|
||||
.then((x) => x.trim())
|
||||
|
||||
if (project.vcs === "git") {
|
||||
let diff = await $`git diff ${file}`.cwd(Instance.directory).quiet().nothrow().text()
|
||||
if (!diff.trim()) diff = await $`git diff --staged ${file}`.cwd(Instance.directory).quiet().nothrow().text()
|
||||
@@ -206,10 +265,10 @@ export namespace File {
|
||||
ignoreWhitespace: true,
|
||||
})
|
||||
const diff = formatPatch(patch)
|
||||
return { content, patch, diff }
|
||||
return { type: "text", content, patch, diff }
|
||||
}
|
||||
}
|
||||
return { content }
|
||||
return { type: "text", content }
|
||||
}
|
||||
|
||||
export async function list(dir?: string) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import path from "path"
|
||||
import { Global } from "../global"
|
||||
import fs from "fs/promises"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { NamedError } from "../util/error"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { $ } from "bun"
|
||||
@@ -211,6 +211,16 @@ export namespace Ripgrep {
|
||||
}
|
||||
}
|
||||
|
||||
// Bun.spawn should throw this, but it incorrectly reports that the executable does not exist.
|
||||
// See https://github.com/oven-sh/bun/issues/24012
|
||||
if (!(await fs.stat(input.cwd).catch(() => undefined))?.isDirectory()) {
|
||||
throw Object.assign(new Error(`No such file or directory: '${input.cwd}'`), {
|
||||
code: "ENOENT",
|
||||
errno: -2,
|
||||
path: input.cwd,
|
||||
})
|
||||
}
|
||||
|
||||
const proc = Bun.spawn(args, {
|
||||
cwd: input.cwd,
|
||||
stdout: "pipe",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Bus } from "../bus"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export namespace Flag {
|
||||
export const OPENCODE_AUTO_SHARE = truthy("OPENCODE_AUTO_SHARE")
|
||||
export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"]
|
||||
export const OPENCODE_CONFIG_DIR = process.env["OPENCODE_CONFIG_DIR"]
|
||||
export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"]
|
||||
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
|
||||
export const OPENCODE_DISABLE_PRUNE = truthy("OPENCODE_DISABLE_PRUNE")
|
||||
@@ -9,9 +10,11 @@ export namespace Flag {
|
||||
export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
|
||||
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
|
||||
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
|
||||
export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
|
||||
|
||||
// Experimental
|
||||
export const OPENCODE_EXPERIMENTAL_WATCHER = truthy("OPENCODE_EXPERIMENTAL_WATCHER")
|
||||
export const OPENCODE_EXPERIMENTAL_TURN_SUMMARY = truthy("OPENCODE_EXPERIMENTAL_TURN_SUMMARY")
|
||||
export const OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP = truthy("OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP")
|
||||
|
||||
function truthy(key: string) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { randomBytes } from "crypto"
|
||||
|
||||
export namespace Identifier {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { spawn } from "bun"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { NamedError } from "../util/error"
|
||||
import { Log } from "../util/log"
|
||||
import { Bus } from "../bus"
|
||||
|
||||
@@ -19,6 +19,8 @@ import { McpCommand } from "./cli/cmd/mcp"
|
||||
import { GithubCommand } from "./cli/cmd/github"
|
||||
import { ExportCommand } from "./cli/cmd/export"
|
||||
import { AttachCommand } from "./cli/cmd/attach"
|
||||
import { AcpCommand } from "./cli/cmd/acp"
|
||||
import { EOL } from "os"
|
||||
|
||||
const cancel = new AbortController()
|
||||
|
||||
@@ -67,6 +69,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
})
|
||||
})
|
||||
.usage("\n" + UI.logo())
|
||||
.command(AcpCommand)
|
||||
.command(McpCommand)
|
||||
.command(TuiCommand)
|
||||
.command(AttachCommand)
|
||||
@@ -127,7 +130,10 @@ try {
|
||||
Log.Default.error("fatal", data)
|
||||
const formatted = FormatError(e)
|
||||
if (formatted) UI.error(formatted)
|
||||
if (formatted === undefined) UI.error("Unexpected error, check log file at " + Log.file() + " for more details")
|
||||
if (formatted === undefined) {
|
||||
UI.error("Unexpected error, check log file at " + Log.file() + " for more details" + EOL)
|
||||
console.error(e)
|
||||
}
|
||||
process.exitCode = 1
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "path"
|
||||
import { $ } from "bun"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { NamedError } from "../util/error"
|
||||
import { Bus } from "../bus"
|
||||
import { Log } from "../util/log"
|
||||
@@ -142,7 +142,9 @@ export namespace Installation {
|
||||
export const USER_AGENT = `opencode/${CHANNEL}/${VERSION}`
|
||||
|
||||
export async function latest() {
|
||||
return fetch(`https://registry.npmjs.org/opencode-ai/${CHANNEL}`)
|
||||
const [major] = VERSION.split(".").map((x) => Number(x))
|
||||
const channel = CHANNEL === "latest" ? `latest-${major}` : CHANNEL
|
||||
return fetch(`https://registry.npmjs.org/opencode-ai/${channel}`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
return res.json()
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types
|
||||
import { Log } from "../util/log"
|
||||
import { LANGUAGE_EXTENSIONS } from "./language"
|
||||
import { Bus } from "../bus"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import type { LSPServer } from "./server"
|
||||
import { NamedError } from "../util/error"
|
||||
import { withTimeout } from "../util/timeout"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Log } from "../util/log"
|
||||
import { LSPClient } from "./client"
|
||||
import path from "path"
|
||||
import { LSPServer } from "./server"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Config } from "../config/config"
|
||||
import { spawn } from "child_process"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
@@ -838,4 +838,135 @@ export namespace LSPServer {
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const LuaLS: Info = {
|
||||
id: "lua-ls",
|
||||
root: NearestRoot([
|
||||
".luarc.json",
|
||||
".luarc.jsonc",
|
||||
".luacheckrc",
|
||||
".stylua.toml",
|
||||
"stylua.toml",
|
||||
"selene.toml",
|
||||
"selene.yml",
|
||||
]),
|
||||
extensions: [".lua"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("lua-language-server", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
log.info("downloading lua-language-server from GitHub releases")
|
||||
|
||||
const releaseResponse = await fetch("https://api.github.com/repos/LuaLS/lua-language-server/releases/latest")
|
||||
if (!releaseResponse.ok) {
|
||||
log.error("Failed to fetch lua-language-server release info")
|
||||
return
|
||||
}
|
||||
|
||||
const release = await releaseResponse.json()
|
||||
|
||||
const platform = process.platform
|
||||
const arch = process.arch
|
||||
let assetName = ""
|
||||
|
||||
let lualsArch: string = arch
|
||||
if (arch === "arm64") lualsArch = "arm64"
|
||||
else if (arch === "x64") lualsArch = "x64"
|
||||
else if (arch === "ia32") lualsArch = "ia32"
|
||||
|
||||
let lualsPlatform: string = platform
|
||||
if (platform === "darwin") lualsPlatform = "darwin"
|
||||
else if (platform === "linux") lualsPlatform = "linux"
|
||||
else if (platform === "win32") lualsPlatform = "win32"
|
||||
|
||||
const ext = platform === "win32" ? "zip" : "tar.gz"
|
||||
|
||||
assetName = `lua-language-server-${release.tag_name}-${lualsPlatform}-${lualsArch}.${ext}`
|
||||
|
||||
const supportedCombos = [
|
||||
"darwin-arm64.tar.gz",
|
||||
"darwin-x64.tar.gz",
|
||||
"linux-x64.tar.gz",
|
||||
"linux-arm64.tar.gz",
|
||||
"win32-x64.zip",
|
||||
"win32-ia32.zip",
|
||||
]
|
||||
|
||||
const assetSuffix = `${lualsPlatform}-${lualsArch}.${ext}`
|
||||
if (!supportedCombos.includes(assetSuffix)) {
|
||||
log.error(`Platform ${platform} and architecture ${arch} is not supported by lua-language-server`)
|
||||
return
|
||||
}
|
||||
|
||||
const asset = release.assets.find((a: any) => a.name === assetName)
|
||||
if (!asset) {
|
||||
log.error(`Could not find asset ${assetName} in latest lua-language-server release`)
|
||||
return
|
||||
}
|
||||
|
||||
const downloadUrl = asset.browser_download_url
|
||||
const downloadResponse = await fetch(downloadUrl)
|
||||
if (!downloadResponse.ok) {
|
||||
log.error("Failed to download lua-language-server")
|
||||
return
|
||||
}
|
||||
|
||||
const tempPath = path.join(Global.Path.bin, assetName)
|
||||
await Bun.file(tempPath).write(downloadResponse)
|
||||
|
||||
// Unlike zls which is a single self-contained binary,
|
||||
// lua-language-server needs supporting files (meta/, locale/, etc.)
|
||||
// Extract entire archive to dedicated directory to preserve all files
|
||||
const installDir = path.join(Global.Path.bin, `lua-language-server-${lualsArch}-${lualsPlatform}`)
|
||||
|
||||
// Remove old installation if exists
|
||||
const stats = await fs.stat(installDir).catch(() => undefined)
|
||||
if (stats) {
|
||||
await fs.rm(installDir, { force: true, recursive: true })
|
||||
}
|
||||
|
||||
await fs.mkdir(installDir, { recursive: true })
|
||||
|
||||
if (ext === "zip") {
|
||||
const ok = await $`unzip -o -q ${tempPath} -d ${installDir}`.quiet().catch((error) => {
|
||||
log.error("Failed to extract lua-language-server archive", { error })
|
||||
})
|
||||
if (!ok) return
|
||||
} else {
|
||||
const ok = await $`tar -xzf ${tempPath} -C ${installDir}`.quiet().catch((error) => {
|
||||
log.error("Failed to extract lua-language-server archive", { error })
|
||||
})
|
||||
if (!ok) return
|
||||
}
|
||||
|
||||
await fs.rm(tempPath, { force: true })
|
||||
|
||||
// Binary is located in bin/ subdirectory within the extracted archive
|
||||
bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : ""))
|
||||
|
||||
if (!(await Bun.file(bin).exists())) {
|
||||
log.error("Failed to extract lua-language-server binary")
|
||||
return
|
||||
}
|
||||
|
||||
if (platform !== "win32") {
|
||||
const ok = await $`chmod +x ${bin}`.quiet().catch((error) => {
|
||||
log.error("Failed to set executable permission for lua-language-server binary", { error })
|
||||
})
|
||||
if (!ok) return
|
||||
}
|
||||
|
||||
log.info(`installed lua-language-server`, { bin })
|
||||
}
|
||||
|
||||
return {
|
||||
process: spawn(bin, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+138
-110
@@ -5,7 +5,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
||||
import { Config } from "../config/config"
|
||||
import { Log } from "../util/log"
|
||||
import { NamedError } from "../util/error"
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Session } from "../session"
|
||||
import { Bus } from "../bus"
|
||||
import { Instance } from "../project/instance"
|
||||
@@ -26,122 +26,22 @@ export namespace MCP {
|
||||
const state = Instance.state(
|
||||
async () => {
|
||||
const cfg = await Config.get()
|
||||
const config = cfg.mcp ?? {}
|
||||
const clients: {
|
||||
[name: string]: MCPClient
|
||||
} = {}
|
||||
for (const [key, mcp] of Object.entries(cfg.mcp ?? {})) {
|
||||
if (mcp.enabled === false) {
|
||||
log.info("mcp server disabled", { key })
|
||||
continue
|
||||
}
|
||||
log.info("found", { key, type: mcp.type })
|
||||
if (mcp.type === "remote") {
|
||||
const transports = [
|
||||
{
|
||||
name: "StreamableHTTP",
|
||||
transport: new StreamableHTTPClientTransport(new URL(mcp.url), {
|
||||
requestInit: {
|
||||
headers: mcp.headers,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "SSE",
|
||||
transport: new SSEClientTransport(new URL(mcp.url), {
|
||||
requestInit: {
|
||||
headers: mcp.headers,
|
||||
},
|
||||
}),
|
||||
},
|
||||
]
|
||||
let lastError: Error | undefined
|
||||
for (const { name, transport } of transports) {
|
||||
const client = await experimental_createMCPClient({
|
||||
name: "opencode",
|
||||
transport,
|
||||
}).catch((error) => {
|
||||
lastError = error instanceof Error ? error : new Error(String(error))
|
||||
log.debug("transport connection failed", {
|
||||
key,
|
||||
transport: name,
|
||||
url: mcp.url,
|
||||
error: lastError.message,
|
||||
})
|
||||
return null
|
||||
})
|
||||
if (client) {
|
||||
log.debug("transport connection succeeded", { key, transport: name })
|
||||
clients[key] = client
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!clients[key]) {
|
||||
const errorMessage = lastError
|
||||
? `MCP server ${key} failed to connect: ${lastError.message}`
|
||||
: `MCP server ${key} failed to connect to ${mcp.url}`
|
||||
log.error("remote mcp connection failed", { key, url: mcp.url, error: lastError?.message })
|
||||
Bus.publish(Session.Event.Error, {
|
||||
error: {
|
||||
name: "UnknownError",
|
||||
data: {
|
||||
message: errorMessage,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (mcp.type === "local") {
|
||||
const [cmd, ...args] = mcp.command
|
||||
const client = await experimental_createMCPClient({
|
||||
name: "opencode",
|
||||
transport: new StdioClientTransport({
|
||||
stderr: "ignore",
|
||||
command: cmd,
|
||||
args,
|
||||
env: {
|
||||
...process.env,
|
||||
...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}),
|
||||
...mcp.environment,
|
||||
},
|
||||
}),
|
||||
}).catch((error) => {
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? `MCP server ${key} failed to start: ${error.message}`
|
||||
: `MCP server ${key} failed to start`
|
||||
log.error("local mcp startup failed", {
|
||||
key,
|
||||
command: mcp.command,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
Bus.publish(Session.Event.Error, {
|
||||
error: {
|
||||
name: "UnknownError",
|
||||
data: {
|
||||
message: errorMessage,
|
||||
},
|
||||
},
|
||||
})
|
||||
return null
|
||||
})
|
||||
if (client) {
|
||||
clients[key] = client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, client] of Object.entries(clients)) {
|
||||
const result = await withTimeout(client.tools(), 5000).catch(() => {})
|
||||
if (!result) {
|
||||
log.warn("mcp client verification failed, removing client", { key })
|
||||
delete clients[key]
|
||||
}
|
||||
}
|
||||
await Promise.all(
|
||||
Object.entries(config).map(async ([key, mcp]) => {
|
||||
const result = await create(key, mcp).catch(() => undefined)
|
||||
if (!result) return
|
||||
clients[key] = result.client
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
clients,
|
||||
config: cfg.mcp ?? {},
|
||||
config,
|
||||
}
|
||||
},
|
||||
async (state) => {
|
||||
@@ -151,6 +51,133 @@ export namespace MCP {
|
||||
},
|
||||
)
|
||||
|
||||
export async function add(name: string, mcp: Config.Mcp) {
|
||||
const s = await state()
|
||||
const result = await create(name, mcp)
|
||||
if (!result) return
|
||||
s.clients[name] = result.client
|
||||
}
|
||||
|
||||
async function create(name: string, mcp: Config.Mcp) {
|
||||
if (mcp.enabled === false) {
|
||||
log.info("mcp server disabled", { name })
|
||||
return
|
||||
}
|
||||
log.info("found", { name, type: mcp.type })
|
||||
|
||||
let mcpClient: MCPClient | undefined
|
||||
|
||||
if (mcp.type === "remote") {
|
||||
const transports = [
|
||||
{
|
||||
name: "StreamableHTTP",
|
||||
transport: new StreamableHTTPClientTransport(new URL(mcp.url), {
|
||||
requestInit: {
|
||||
headers: mcp.headers,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "SSE",
|
||||
transport: new SSEClientTransport(new URL(mcp.url), {
|
||||
requestInit: {
|
||||
headers: mcp.headers,
|
||||
},
|
||||
}),
|
||||
},
|
||||
]
|
||||
let lastError: Error | undefined
|
||||
for (const { name, transport } of transports) {
|
||||
const client = await experimental_createMCPClient({
|
||||
name: "opencode",
|
||||
transport,
|
||||
}).catch((error) => {
|
||||
lastError = error instanceof Error ? error : new Error(String(error))
|
||||
log.debug("transport connection failed", {
|
||||
name,
|
||||
transport: name,
|
||||
url: mcp.url,
|
||||
error: lastError.message,
|
||||
})
|
||||
return null
|
||||
})
|
||||
if (client) {
|
||||
log.debug("transport connection succeeded", { name, transport: name })
|
||||
mcpClient = client
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!mcpClient) {
|
||||
const errorMessage = lastError
|
||||
? `MCP server ${name} failed to connect: ${lastError.message}`
|
||||
: `MCP server ${name} failed to connect to ${mcp.url}`
|
||||
log.error("remote mcp connection failed", { name, url: mcp.url, error: lastError?.message })
|
||||
Bus.publish(Session.Event.Error, {
|
||||
error: {
|
||||
name: "UnknownError",
|
||||
data: {
|
||||
message: errorMessage,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (mcp.type === "local") {
|
||||
const [cmd, ...args] = mcp.command
|
||||
const client = await experimental_createMCPClient({
|
||||
name: "opencode",
|
||||
transport: new StdioClientTransport({
|
||||
stderr: "ignore",
|
||||
command: cmd,
|
||||
args,
|
||||
env: {
|
||||
...process.env,
|
||||
...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}),
|
||||
...mcp.environment,
|
||||
},
|
||||
}),
|
||||
}).catch((error) => {
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? `MCP server ${name} failed to start: ${error.message}`
|
||||
: `MCP server ${name} failed to start`
|
||||
log.error("local mcp startup failed", {
|
||||
name,
|
||||
command: mcp.command,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
Bus.publish(Session.Event.Error, {
|
||||
error: {
|
||||
name: "UnknownError",
|
||||
data: {
|
||||
message: errorMessage,
|
||||
},
|
||||
},
|
||||
})
|
||||
return null
|
||||
})
|
||||
if (client) {
|
||||
mcpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
if (!mcpClient) {
|
||||
log.warn("mcp client not initialized", { name })
|
||||
return
|
||||
}
|
||||
|
||||
const result = await withTimeout(mcpClient.tools(), mcp.timeout ?? 5000).catch(() => { })
|
||||
if (!result) {
|
||||
log.warn("mcp client verification failed, dropping client", { name })
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
client: mcpClient,
|
||||
}
|
||||
}
|
||||
|
||||
export async function status() {
|
||||
return state().then((state) => {
|
||||
const result: Record<string, "connected" | "failed" | "disabled"> = {}
|
||||
@@ -161,6 +188,7 @@ export namespace MCP {
|
||||
}
|
||||
if (state.clients[key]) {
|
||||
result[key] = "connected"
|
||||
continue
|
||||
}
|
||||
result[key] = "failed"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod/v4"
|
||||
import z from "zod"
|
||||
import { Bus } from "../bus"
|
||||
import { Log } from "../util/log"
|
||||
import { Identifier } from "../id/id"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user