mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-09 18:36:22 +00:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f1dbd8189 | ||
|
|
33a323b2a5 | ||
|
|
37218177cf | ||
|
|
fbb70f1317 | ||
|
|
68c1138585 | ||
|
|
04e3fc280d | ||
|
|
444be68cc2 | ||
|
|
ebe5420755 | ||
|
|
a531a61a72 | ||
|
|
d4539ff55c | ||
|
|
7acdea1437 | ||
|
|
890f8fe863 | ||
|
|
fa4bce6f7a | ||
|
|
d8804ffd65 | ||
|
|
7bb391582b | ||
|
|
07dcde43c6 | ||
|
|
66b16d40f0 | ||
|
|
de7ab3b082 | ||
|
|
29a05fa343 | ||
|
|
0dc7aa2411 | ||
|
|
b5222ef904 | ||
|
|
30c25008ac | ||
|
|
7466567c51 | ||
|
|
39310ee609 | ||
|
|
cc40f5844c |
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
- name: Deploy update service
|
||||
if: github.ref_name == 'v2'
|
||||
working-directory: services/update
|
||||
working-directory: services/updates
|
||||
run: bun run deploy
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# Slash-command follow-up behavior
|
||||
|
||||
Both screenshots submit `/review current changes` with the follow-up preference set to **Queue** while a session is running.
|
||||
|
||||
- `before.png`: production build of `v2` at `1dcc6551d9`; the command is sent as Steer.
|
||||
- `after.png`: production build with this fix; the command appears in the queue above the composer.
|
||||
|
||||
Captured with the fixture-backed `slash commands respect queue preference and alternate submit` Playwright scenario in `packages/app/e2e/regression/session-queue.spec.ts`. The production UI is rendered against an isolated command/session fixture.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
@@ -63,7 +63,9 @@
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/plugin-browser": "workspace:*",
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/plugin-context-desktop": "workspace:*",
|
||||
"@opencode/plugin-review-desktop": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
"@opencode/session-ui": "workspace:*",
|
||||
"@opencode/ui": "workspace:*",
|
||||
@@ -168,7 +170,6 @@
|
||||
"@yuuang/ffi-rs-linux-x64-gnu": "1.3.2",
|
||||
"@yuuang/ffi-rs-win32-arm64-msvc": "1.3.2",
|
||||
"@yuuang/ffi-rs-win32-x64-msvc": "1.3.2",
|
||||
"solid-refresh": "0.6.3",
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-solid": "catalog:",
|
||||
},
|
||||
@@ -427,7 +428,9 @@
|
||||
"@lydell/node-pty": "catalog:",
|
||||
"@opencode/app": "workspace:*",
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/plugin-browser": "workspace:*",
|
||||
"@opencode/plugin-browser-desktop": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
"@opencode/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
@@ -598,8 +601,12 @@
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"electron": "42.10.1",
|
||||
"solid-js": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "8.2.2",
|
||||
"vite-plugin-solid": "2.11.14",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opencode/theme": "workspace:*",
|
||||
@@ -629,6 +636,66 @@
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/plugin-browser-desktop": {
|
||||
"name": "@opencode/plugin-browser-desktop",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/plugin-browser": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
"@opencode/ui": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
"lighthouse": "13.4.1",
|
||||
"solid-js": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"devtools-protocol": "0.0.1687809",
|
||||
"electron": "42.10.1",
|
||||
"puppeteer-core": "25.9.0",
|
||||
},
|
||||
},
|
||||
"packages/plugin-context-desktop": {
|
||||
"name": "@opencode/plugin-context-desktop",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/session-ui": "workspace:*",
|
||||
"@opencode/ui": "workspace:*",
|
||||
"@opencode/util": "workspace:*",
|
||||
"luxon": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "catalog:",
|
||||
"@types/luxon": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/plugin-review-desktop": {
|
||||
"name": "@opencode/plugin-review-desktop",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/session-ui": "workspace:*",
|
||||
"@opencode/ui": "workspace:*",
|
||||
"@opencode/util": "workspace:*",
|
||||
"@solid-primitives/event-listener": "catalog:",
|
||||
"@solid-primitives/scheduled": "1.5.3",
|
||||
"@tanstack/solid-query": "5.91.4",
|
||||
"@tanstack/solid-virtual": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/posts": {
|
||||
"name": "@opencode/posts",
|
||||
"dependencies": {
|
||||
@@ -1038,8 +1105,8 @@
|
||||
"wrangler": "4.110.0",
|
||||
},
|
||||
},
|
||||
"services/update": {
|
||||
"name": "@opencode/update",
|
||||
"services/updates": {
|
||||
"name": "@opencode/updates",
|
||||
"version": "1.18.4",
|
||||
"dependencies": {
|
||||
"jose": "6.0.11",
|
||||
@@ -2216,6 +2283,12 @@
|
||||
|
||||
"@opencode/plugin-browser": ["@opencode/plugin-browser@workspace:packages/plugin-browser"],
|
||||
|
||||
"@opencode/plugin-browser-desktop": ["@opencode/plugin-browser-desktop@workspace:packages/plugin-browser-desktop"],
|
||||
|
||||
"@opencode/plugin-context-desktop": ["@opencode/plugin-context-desktop@workspace:packages/plugin-context-desktop"],
|
||||
|
||||
"@opencode/plugin-review-desktop": ["@opencode/plugin-review-desktop@workspace:packages/plugin-review-desktop"],
|
||||
|
||||
"@opencode/posts": ["@opencode/posts@workspace:packages/posts"],
|
||||
|
||||
"@opencode/protocol": ["@opencode/protocol@workspace:packages/protocol"],
|
||||
@@ -2246,7 +2319,7 @@
|
||||
|
||||
"@opencode/ui": ["@opencode/ui@workspace:packages/ui"],
|
||||
|
||||
"@opencode/update": ["@opencode/update@workspace:services/update"],
|
||||
"@opencode/updates": ["@opencode/updates@workspace:services/updates"],
|
||||
|
||||
"@opencode/util": ["@opencode/util@workspace:packages/util"],
|
||||
|
||||
@@ -6420,11 +6493,11 @@
|
||||
|
||||
"@opencode/ui/vite-plugin-solid": ["vite-plugin-solid@2.11.10", "", { "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-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw=="],
|
||||
|
||||
"@opencode/update/@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
|
||||
"@opencode/updates/@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
|
||||
|
||||
"@opencode/update/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
"@opencode/updates/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@opencode/update/wrangler": ["wrangler@4.110.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "4.20260708.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260708.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20260708.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-xZeXKYi7hxQRF5anL+v77RkufJNpF9f3Eqeyqq2QBsETpLZgh0Agj0jJ6JPtkbgn6ukZdh8OK5egsGPWIditgg=="],
|
||||
"@opencode/updates/wrangler": ["wrangler@4.110.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "4.20260708.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260708.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20260708.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-xZeXKYi7hxQRF5anL+v77RkufJNpF9f3Eqeyqq2QBsETpLZgh0Agj0jJ6JPtkbgn6ukZdh8OK5egsGPWIditgg=="],
|
||||
|
||||
"@opencode/web/@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
|
||||
|
||||
@@ -7318,15 +7391,15 @@
|
||||
|
||||
"@opencode/storybook/@tailwindcss/vite/tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],
|
||||
|
||||
"@opencode/update/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],
|
||||
"@opencode/updates/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],
|
||||
|
||||
"@opencode/update/wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="],
|
||||
"@opencode/updates/wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||
"@opencode/updates/wrangler/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare": ["miniflare@4.20260708.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.34.5", "undici": "7.28.0", "workerd": "1.20260708.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-c94O9zRDISdqO18EHt6l0iF/fWgWt8p18PJvRsA/L/NJZ9Cfke3s/F5Blg1XXF7WDutVRzWVWy8Vy4LaT5ifsA=="],
|
||||
"@opencode/updates/wrangler/miniflare": ["miniflare@4.20260708.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.34.5", "undici": "7.28.0", "workerd": "1.20260708.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-c94O9zRDISdqO18EHt6l0iF/fWgWt8p18PJvRsA/L/NJZ9Cfke3s/F5Blg1XXF7WDutVRzWVWy8Vy4LaT5ifsA=="],
|
||||
|
||||
"@opencode/update/wrangler/workerd": ["workerd@1.20260708.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260708.1", "@cloudflare/workerd-darwin-arm64": "1.20260708.1", "@cloudflare/workerd-linux-64": "1.20260708.1", "@cloudflare/workerd-linux-arm64": "1.20260708.1", "@cloudflare/workerd-windows-64": "1.20260708.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-WAK+Kt/VVCSldH2qSr8lx46XCJ4Q+bdlHNaFqUtOHthBEIB8C1N8HVW+VOLrxDoTCk0NGNv0zajnBeQK4JOB9w=="],
|
||||
"@opencode/updates/wrangler/workerd": ["workerd@1.20260708.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260708.1", "@cloudflare/workerd-darwin-arm64": "1.20260708.1", "@cloudflare/workerd-linux-64": "1.20260708.1", "@cloudflare/workerd-linux-arm64": "1.20260708.1", "@cloudflare/workerd-windows-64": "1.20260708.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-WAK+Kt/VVCSldH2qSr8lx46XCJ4Q+bdlHNaFqUtOHthBEIB8C1N8HVW+VOLrxDoTCk0NGNv0zajnBeQK4JOB9w=="],
|
||||
|
||||
"@opencode/web/@astrojs/cloudflare/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.1", "", {}, "sha512-7dwEVigz9vUWDw3nRwLQ/yH/xYovlUA0ZD86xoeKEBmkz9O6iELG1yri67PgAPW6VLL/xInA4t7H0CK6VmtkKQ=="],
|
||||
|
||||
@@ -8254,71 +8327,71 @@
|
||||
|
||||
"@opencode/storybook/@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
|
||||
|
||||
"@opencode/update/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
||||
"@opencode/updates/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="],
|
||||
"@opencode/updates/wrangler/miniflare/undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="],
|
||||
|
||||
"@opencode/update/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260708.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-HXFCvhS1wpg3uXO0CLUwmwC41i2loM5FSK69EUchOBpmYBAXxT1oHLm6EOA5lqhTk5Mu9kjRiQYxa1GwKPwfJg=="],
|
||||
"@opencode/updates/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260708.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-HXFCvhS1wpg3uXO0CLUwmwC41i2loM5FSK69EUchOBpmYBAXxT1oHLm6EOA5lqhTk5Mu9kjRiQYxa1GwKPwfJg=="],
|
||||
|
||||
"@opencode/update/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260708.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JVlJaKDoRTVKSroHIlf8g3UCPjKj4iDbMZE2CNYht5qQ+2rL0FAUiVlV82G3BqKnnw9kHYnnsMzC08b9zVtdzA=="],
|
||||
"@opencode/updates/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260708.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JVlJaKDoRTVKSroHIlf8g3UCPjKj4iDbMZE2CNYht5qQ+2rL0FAUiVlV82G3BqKnnw9kHYnnsMzC08b9zVtdzA=="],
|
||||
|
||||
"@opencode/update/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260708.1", "", { "os": "linux", "cpu": "x64" }, "sha512-3daE60YdD7YX0Jtuzc9DE/r/qMkmx8ZvHTkF8Mzmp3F5tbzlV0DAzmu5PFUPF2WuvtKbAhZKbvC2cHmWpQYxnA=="],
|
||||
"@opencode/updates/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260708.1", "", { "os": "linux", "cpu": "x64" }, "sha512-3daE60YdD7YX0Jtuzc9DE/r/qMkmx8ZvHTkF8Mzmp3F5tbzlV0DAzmu5PFUPF2WuvtKbAhZKbvC2cHmWpQYxnA=="],
|
||||
|
||||
"@opencode/update/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260708.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-VLdNYOx5Hj+9C6isy0ACWZsbMtSxex2DIJWEe7cZxUdlphZ58ZT8zxNXK8yunFiowd34hn3VwGMopdvdj8lvmA=="],
|
||||
"@opencode/updates/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260708.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-VLdNYOx5Hj+9C6isy0ACWZsbMtSxex2DIJWEe7cZxUdlphZ58ZT8zxNXK8yunFiowd34hn3VwGMopdvdj8lvmA=="],
|
||||
|
||||
"@opencode/update/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260708.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bC/aSAwLy16Vjo24i9XU3aWH+eRgz7NeR5xPKavGbembO18ZywYTQbXh14eXtY6fAqN3RzRG8psijTdhX4xydA=="],
|
||||
"@opencode/updates/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260708.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bC/aSAwLy16Vjo24i9XU3aWH+eRgz7NeR5xPKavGbembO18ZywYTQbXh14eXtY6fAqN3RzRG8psijTdhX4xydA=="],
|
||||
|
||||
"@opencode/web/@astrojs/cloudflare/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],
|
||||
|
||||
@@ -8922,55 +8995,55 @@
|
||||
|
||||
"@opencode/storybook/@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"@opencode/web/@astrojs/cloudflare/wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||
|
||||
@@ -9154,7 +9227,7 @@
|
||||
|
||||
"@opencode/storybook/@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
|
||||
|
||||
"@opencode/update/wrangler/miniflare/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
|
||||
"@opencode/updates/wrangler/miniflare/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
|
||||
|
||||
"@opencode/web/@astrojs/cloudflare/wrangler/miniflare/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
||||
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-yzCk746pospz8EVakHRcDhYJkhGYGSt9dHOPbzO4OYo=",
|
||||
"aarch64-linux": "sha256-MFJVLos4v2r9jazmmr3ldVCJKLrO+Qp/BpGpyM/1lf8=",
|
||||
"aarch64-darwin": "sha256-k8r/HVSgdRSTlJ1lI7EebqbxeA3AElnaw1sDYOPEdQw=",
|
||||
"x86_64-darwin": "sha256-ACJdJfz12xLBQvWIkbuve86znSoGJ2PLDQbgh0cT6/g="
|
||||
"x86_64-linux": "sha256-EKhY3iZDrbNrBhntWpSdtLcmNLte6yVBxpIrCxr1uNM=",
|
||||
"aarch64-linux": "sha256-0OjDGZHgcnnk6IxkfK6ogeeqsGTqY/dcaZ/XzT23sgA=",
|
||||
"aarch64-darwin": "sha256-Zk51gnOicaLtPuqCYfgARhm2TjL222w1Y0Em288o0YY=",
|
||||
"x86_64-darwin": "sha256-hvDZ9zCV6zOSx6i7JZ1kVUMht+JI/jc8/y+aYrNHQ1E="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
"scripts": {
|
||||
"dev": "bun run --cwd packages/cli src/index.ts",
|
||||
"dev:live": "sh -c 'OPENCODE_TUI_CHANNEL=dev OPENCODE_PASSWORD=\"$(opencode2 service get password)\" exec bun run dev \"$@\" --server \"$(opencode2 service status)\"' --",
|
||||
"dev:vite": "bun run --cwd packages/cli --conditions=browser dev/vite.ts",
|
||||
"dev:vite:live": "sh -c 'OPENCODE_TUI_CHANNEL=dev OPENCODE_PASSWORD=\"$(opencode2 service get password)\" exec bun run dev:vite \"$@\" --server \"$(opencode2 service status)\"' --",
|
||||
"dev:desktop": "bun --cwd packages/desktop dev",
|
||||
"dev:web": "bun --cwd packages/app dev",
|
||||
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
||||
|
||||
@@ -29,78 +29,6 @@ await Effect.runPromise(program.pipe(Effect.provide(llmLayer)))
|
||||
|
||||
Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
|
||||
|
||||
## Alibaba Cloud Model Studio
|
||||
|
||||
`Alibaba` provides standard Model Studio inference. Configure a region explicitly, then select
|
||||
Chat Completions (`.model` or `.chat`), Anthropic-compatible Messages (`.messages`), or OpenAI-compatible
|
||||
Responses (`.responses`). These routes use HTTP/SSE.
|
||||
|
||||
```ts
|
||||
import { LLM } from "@opencode/ai"
|
||||
import { Alibaba } from "@opencode/ai/providers"
|
||||
|
||||
const alibaba = Alibaba.configure({
|
||||
region: "ap-southeast-1", // Singapore
|
||||
apiKey: process.env.DASHSCOPE_API_KEY,
|
||||
// workspaceID: "llm-your-workspace", // use a workspace-dedicated endpoint
|
||||
})
|
||||
|
||||
const request = LLM.request({
|
||||
model: alibaba.model("qwen3.8-max"),
|
||||
prompt: "Explain this design.",
|
||||
providerOptions: { reasoningEffort: "medium" },
|
||||
})
|
||||
```
|
||||
|
||||
### Regions and credentials
|
||||
|
||||
| Region | `region` | Shared host when `workspaceID` is omitted |
|
||||
| ------------------- | ---------------- | ----------------------------------------- |
|
||||
| Singapore | `ap-southeast-1` | `dashscope-intl.aliyuncs.com` |
|
||||
| China (Beijing) | `cn-beijing` | `dashscope.aliyuncs.com` |
|
||||
| China (Hong Kong) | `cn-hongkong` | `cn-hongkong.dashscope.aliyuncs.com` |
|
||||
| US (Virginia) | `us-east-1` | `dashscope-us.aliyuncs.com` |
|
||||
| Germany (Frankfurt) | `eu-central-1` | Supply `workspaceID` or `baseURL` |
|
||||
| Japan (Tokyo) | `ap-northeast-1` | Supply `workspaceID` or `baseURL` |
|
||||
|
||||
With `workspaceID`, the host is `{workspaceID}.{region}.maas.aliyuncs.com`. A complete `baseURL`
|
||||
overrides regional setup, including the API prefix: `/compatible-mode/v1` for Chat/Responses,
|
||||
or `/apps/anthropic/v1` for Messages. The selector appends its operation path.
|
||||
|
||||
Keys and model availability are region-specific. Auth resolves from explicit `auth` or `apiKey`,
|
||||
then `DASHSCOPE_API_KEY`, then `ALIBABA_API_KEY`.
|
||||
|
||||
The access region and inference scope differ: Virginia's `-us` model IDs request US-only inference;
|
||||
some regions select scope through their workspace. Model IDs pass through unchanged.
|
||||
Alibaba's [regional guide](https://www.alibabacloud.com/help/en/model-studio/regions) and
|
||||
[base URL table](https://www.alibabacloud.com/help/en/model-studio/base-url) disagree about Virginia's
|
||||
shared host; the entry above follows the base URL table. Dedicated hosts can be copied from the console.
|
||||
|
||||
### Native options
|
||||
|
||||
- **Chat:** `reasoningEffort` → `reasoning_effort`, `enableThinking` → `enable_thinking`,
|
||||
`thinkingBudget` → `thinking_budget`, and `preserveThinking` → `preserve_thinking`.
|
||||
Replay complete `response.message` values to retain `reasoning_content` separately from answer text.
|
||||
Qwen 3.8 defaults to preserving thinking; older models have different defaults.
|
||||
Additional options include `toolStream`, `parallelToolCalls`, `repetitionPenalty`, `responseFormat`,
|
||||
`enableSearch`, and native `searchOptions`. `generation.topK` lowers to `top_k`.
|
||||
`clearThinking` is a hosted GLM control, and `thinking.type` is available for hosted MiniMax models.
|
||||
- **Messages:** `effort` → `output_config.effort`. `thinking.type` accepts enabled/disabled with an
|
||||
optional `budgetTokens` (or native `budget_tokens`). `outputConfig.format` accepts a JSON schema.
|
||||
Model Studio's empty thinking signatures are accepted; supplied signatures are replayed unchanged.
|
||||
- **Responses:** `reasoningEffort` → `reasoning.effort`, plus `enableThinking`, `store`,
|
||||
`previousResponseId`, and `conversation`. Omitted `store` retains the API's default (`true`);
|
||||
set it to `false` for client-managed history. `previousResponseId` requires a stored response.
|
||||
Hosted tools are `Alibaba.webSearch()`, `Alibaba.webExtractor()`, and `Alibaba.codeInterpreter()`.
|
||||
Web extraction is used together with web search. Hosted calls/results carry `providerExecuted: true`.
|
||||
|
||||
Omitted options preserve provider defaults. Effort values pass through unchanged and accept future
|
||||
strings. Qwen 3.8 Chat rejects requests combining a thinking budget with effort.
|
||||
|
||||
Package entrypoints are `@opencode/ai/providers/alibaba`, `alibaba/chat`, `alibaba/messages`,
|
||||
and `alibaba/responses`. Live recordings cover all three APIs in Singapore; regional URL construction
|
||||
is unit-tested for all six regions.
|
||||
|
||||
## Z.AI
|
||||
|
||||
`ZAI` uses the standard API. Chat Completions is the default language-model API;
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import { Effect, Schema } from "effect"
|
||||
import { Protocol } from "../route/protocol.js"
|
||||
import type { LanguageModelCompatibility } from "../schema/index.js"
|
||||
import { OpenAIChat } from "./openai-chat.js"
|
||||
import { JsonObject, ProviderShared } from "./shared.js"
|
||||
import { OpenResponsesOptions } from "./utils/open-responses-options.js"
|
||||
|
||||
export type ReasoningEffort = OpenResponsesOptions.ReasoningEffort
|
||||
|
||||
const Options = Schema.Struct({
|
||||
reasoningEffort: OpenResponsesOptions.Options.fields.reasoningEffort,
|
||||
enableThinking: Schema.optional(Schema.Boolean),
|
||||
thinkingBudget: Schema.optional(Schema.Int),
|
||||
preserveThinking: Schema.optional(Schema.Boolean),
|
||||
clearThinking: Schema.optional(Schema.Boolean),
|
||||
thinking: Schema.optional(
|
||||
Schema.Struct({
|
||||
type: Schema.declare<"adaptive" | "disabled" | (string & {})>(Schema.is(Schema.String)),
|
||||
}),
|
||||
),
|
||||
toolStream: Schema.optional(Schema.Boolean),
|
||||
parallelToolCalls: OpenResponsesOptions.Options.fields.parallelToolCalls,
|
||||
repetitionPenalty: Schema.optional(Schema.Number),
|
||||
responseFormat: Schema.optional(
|
||||
Schema.Struct({
|
||||
type: Schema.declare<"text" | "json_object" | "json_schema" | (string & {})>(Schema.is(Schema.String)),
|
||||
json_schema: Schema.optional(JsonObject),
|
||||
}),
|
||||
),
|
||||
enableSearch: Schema.optional(Schema.Boolean),
|
||||
searchOptions: Schema.optional(
|
||||
Schema.Struct({
|
||||
forced_search: Schema.optional(Schema.Boolean),
|
||||
search_strategy: Schema.optional(
|
||||
Schema.declare<"turbo" | "max" | "agent" | "agent_max" | (string & {})>(Schema.is(Schema.String)),
|
||||
),
|
||||
enable_search_extension: Schema.optional(Schema.Boolean),
|
||||
}),
|
||||
),
|
||||
})
|
||||
export type OptionsInput = typeof Options.Type
|
||||
|
||||
export const compatibility = {
|
||||
maxTokensField: "max_completion_tokens",
|
||||
supportsStore: false,
|
||||
supportsStrictMode: false,
|
||||
reasoningField: "reasoning_content",
|
||||
zaiToolStream: false,
|
||||
} satisfies LanguageModelCompatibility
|
||||
|
||||
export const protocol = Protocol.make({
|
||||
id: "alibaba-chat",
|
||||
body: {
|
||||
schema: Schema.Struct({
|
||||
...OpenAIChat.bodyFields,
|
||||
enable_thinking: Options.fields.enableThinking,
|
||||
thinking_budget: Options.fields.thinkingBudget,
|
||||
preserve_thinking: Options.fields.preserveThinking,
|
||||
clear_thinking: Options.fields.clearThinking,
|
||||
thinking: Options.fields.thinking,
|
||||
parallel_tool_calls: Options.fields.parallelToolCalls,
|
||||
repetition_penalty: Options.fields.repetitionPenalty,
|
||||
top_k: Schema.optional(Schema.Int),
|
||||
response_format: Options.fields.responseFormat,
|
||||
enable_search: Options.fields.enableSearch,
|
||||
search_options: Options.fields.searchOptions,
|
||||
}),
|
||||
from: Effect.fn("AlibabaChat.fromRequest")(function* (req) {
|
||||
const opts = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(req.providerOptions ?? {})
|
||||
return {
|
||||
...(yield* OpenAIChat.protocol.body.from(req)),
|
||||
enable_thinking: opts.enableThinking,
|
||||
thinking_budget: opts.thinkingBudget,
|
||||
preserve_thinking: opts.preserveThinking,
|
||||
clear_thinking: opts.clearThinking,
|
||||
thinking: opts.thinking,
|
||||
tool_stream: opts.toolStream,
|
||||
parallel_tool_calls:
|
||||
opts.parallelToolCalls ??
|
||||
(req.toolChoice?.disableParallelToolUse === undefined ? undefined : !req.toolChoice.disableParallelToolUse),
|
||||
repetition_penalty: opts.repetitionPenalty,
|
||||
top_k: req.generation?.topK,
|
||||
response_format: opts.responseFormat,
|
||||
enable_search: opts.enableSearch,
|
||||
search_options: opts.searchOptions,
|
||||
}
|
||||
}),
|
||||
},
|
||||
stream: OpenAIChat.protocol.stream,
|
||||
})
|
||||
|
||||
export * as AlibabaChat from "./alibaba-chat.js"
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Effect, Schema } from "effect"
|
||||
import { Protocol } from "../route/protocol.js"
|
||||
import { LLMRequest } from "../schema/index.js"
|
||||
import { AnthropicMessages } from "./anthropic-messages.js"
|
||||
import { ProviderShared } from "./shared.js"
|
||||
import { OpenResponsesOptions } from "./utils/open-responses-options.js"
|
||||
|
||||
const Options = Schema.Struct({
|
||||
effort: Schema.optional(OpenResponsesOptions.ReasoningEffort),
|
||||
thinking: Schema.optional(
|
||||
Schema.Struct({
|
||||
type: Schema.declare<"enabled" | "disabled" | (string & {})>(Schema.is(Schema.String)),
|
||||
budgetTokens: Schema.optional(Schema.Int),
|
||||
budget_tokens: Schema.optional(Schema.Int),
|
||||
}),
|
||||
),
|
||||
})
|
||||
export type OptionsInput = typeof Options.Type & Pick<AnthropicMessages.OptionsInput, "outputConfig">
|
||||
export const protocol = Protocol.make({
|
||||
id: "alibaba-messages",
|
||||
body: {
|
||||
schema: Schema.Struct({
|
||||
...AnthropicMessages.AnthropicMessagesBody.fields,
|
||||
thinking: Schema.optional(Schema.Struct({ type: Schema.String, budget_tokens: Schema.optional(Schema.Int) })),
|
||||
}),
|
||||
from: Effect.fn("AlibabaMessages.fromRequest")(function* (req) {
|
||||
const opts = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(req.providerOptions ?? {})
|
||||
// Model Studio accepts enabled thinking without Anthropic's mandatory token budget.
|
||||
return {
|
||||
...(yield* AnthropicMessages.protocol.body.from(
|
||||
LLMRequest.update(req, {
|
||||
providerOptions: { ...req.providerOptions, thinking: undefined },
|
||||
}),
|
||||
)),
|
||||
thinking:
|
||||
opts.thinking === undefined
|
||||
? undefined
|
||||
: {
|
||||
type: opts.thinking.type,
|
||||
budget_tokens: opts.thinking.budgetTokens ?? opts.thinking.budget_tokens,
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
stream: AnthropicMessages.protocol.stream,
|
||||
})
|
||||
|
||||
export * as AlibabaMessages from "./alibaba-messages.js"
|
||||
@@ -1,98 +0,0 @@
|
||||
import { Effect, Schema } from "effect"
|
||||
import { Protocol } from "../route/protocol.js"
|
||||
import { OpenResponses } from "./open-responses.js"
|
||||
import { JsonObject, optionalArray, ProviderShared } from "./shared.js"
|
||||
import { OpenResponsesOptions } from "./utils/open-responses-options.js"
|
||||
import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js"
|
||||
|
||||
const Options = Schema.Struct({
|
||||
reasoningEffort: OpenResponsesOptions.Options.fields.reasoningEffort,
|
||||
enableThinking: Schema.optional(Schema.Boolean),
|
||||
store: OpenResponsesOptions.Options.fields.store,
|
||||
previousResponseId: Schema.optional(Schema.String),
|
||||
conversation: Schema.optional(Schema.String),
|
||||
})
|
||||
export type OptionsInput = typeof Options.Type
|
||||
const NativeTool = Schema.Struct({ type: Schema.Literals(["web_search", "web_extractor", "code_interpreter"]) })
|
||||
const WebExtractorItem = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("web_extractor_call"),
|
||||
id: Schema.String,
|
||||
urls: Schema.optional(Schema.Array(Schema.String)),
|
||||
goal: Schema.optional(Schema.String),
|
||||
}),
|
||||
[JsonObject],
|
||||
)
|
||||
const Body = Schema.Struct({
|
||||
...OpenResponses.coreFields,
|
||||
input: Schema.Array(Schema.Union([OpenResponses.InputItem, WebExtractorItem])),
|
||||
tools: optionalArray(Schema.Union([OpenResponses.Tool, NativeTool])),
|
||||
enable_thinking: Options.fields.enableThinking,
|
||||
previous_response_id: Options.fields.previousResponseId,
|
||||
conversation: Options.fields.conversation,
|
||||
stream: Schema.Literal(true),
|
||||
})
|
||||
const adapter = {
|
||||
id: "alibaba-responses",
|
||||
name: "Alibaba Responses",
|
||||
nativeTool: (native) => ProviderShared.validateWith(Schema.decodeUnknownEffect(NativeTool))(native.alibaba),
|
||||
restoreHostedToolItem: (item: unknown) => (Schema.is(WebExtractorItem)(item) ? item : undefined),
|
||||
} satisfies OpenResponses.ProviderAdapter
|
||||
|
||||
const tools = {
|
||||
web_search_call: { name: "web_search", input: (item) => item.action ?? {} },
|
||||
code_interpreter_call: { name: "code_interpreter", input: (item) => ({ code: item.code }) },
|
||||
} satisfies ResponsesHostedTools.Definitions
|
||||
|
||||
export const protocol = Protocol.make({
|
||||
id: adapter.id,
|
||||
body: {
|
||||
schema: Body,
|
||||
from: Effect.fn("AlibabaResponses.fromRequest")(function* (req) {
|
||||
const opts = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(req.providerOptions ?? {})
|
||||
const body = yield* OpenResponses.fromRequestWithAdapter(req, adapter)
|
||||
const choice = body.tool_choice
|
||||
return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Body))({
|
||||
...body,
|
||||
enable_thinking: opts.enableThinking,
|
||||
previous_response_id: opts.previousResponseId,
|
||||
conversation: opts.conversation,
|
||||
// Model Studio expresses named selection through allowed_tools.
|
||||
tool_choice:
|
||||
typeof choice === "object" && choice.type === "function"
|
||||
? { type: "allowed_tools" as const, mode: "required" as const, tools: [choice] }
|
||||
: choice,
|
||||
})
|
||||
}),
|
||||
},
|
||||
stream: {
|
||||
event: OpenResponses.protocol.stream.event,
|
||||
initial: (req) => OpenResponses.initial(req, adapter),
|
||||
step: (state, input) =>
|
||||
Effect.gen(function* () {
|
||||
const event = OpenResponses.normalize(state, input)
|
||||
if (event.type !== "response.output_item.done" || !event.item) return yield* OpenResponses.step(state, event)
|
||||
if (event.item.type === "web_extractor_call") {
|
||||
const item = yield* Schema.decodeUnknownEffect(WebExtractorItem)(event.item).pipe(
|
||||
Effect.mapError((cause) =>
|
||||
ProviderShared.eventError(
|
||||
adapter.id,
|
||||
"Alibaba returned an invalid web extraction item",
|
||||
ProviderShared.encodeJson(event),
|
||||
cause,
|
||||
),
|
||||
),
|
||||
)
|
||||
return yield* ResponsesHostedTools.onDone(state, item, {
|
||||
web_extractor_call: { name: "web_extractor", input: () => ({ urls: item.urls, goal: item.goal }) },
|
||||
})
|
||||
}
|
||||
if (ResponsesHostedTools.isItem(event.item, tools))
|
||||
return yield* ResponsesHostedTools.onDone(state, event.item, tools)
|
||||
return yield* OpenResponses.step(state, event)
|
||||
}),
|
||||
terminal: OpenResponses.terminal,
|
||||
},
|
||||
})
|
||||
|
||||
export * as AlibabaResponses from "./alibaba-responses.js"
|
||||
@@ -408,9 +408,6 @@ export type NormalizedEvent = Event & { readonly item?: OutputItem | null }
|
||||
export interface ProviderAdapter {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly nativeTool?: (
|
||||
native: NonNullable<ToolDefinition["native"]>,
|
||||
) => Effect.Effect<{ readonly type: string }, AIError>
|
||||
readonly lowerMedia?: (input: {
|
||||
readonly part: MediaPart
|
||||
readonly media: ProviderShared.NormalizedMedia
|
||||
@@ -822,13 +819,11 @@ export const fromRequestWithAdapter = Effect.fn("OpenResponses.fromRequestWithAd
|
||||
projected.tools.length === 0
|
||||
? undefined
|
||||
: yield* Effect.forEach(projected.tools, (tool) =>
|
||||
tool.native !== undefined && adapter.nativeTool
|
||||
? adapter.nativeTool(tool.native)
|
||||
: lowerTool(
|
||||
adapter.name,
|
||||
tool,
|
||||
ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility),
|
||||
),
|
||||
lowerTool(
|
||||
adapter.name,
|
||||
tool,
|
||||
ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility),
|
||||
),
|
||||
),
|
||||
tool_choice:
|
||||
allowedToolChoice(request) ??
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
import type { ProviderPackage } from "../provider-package.js"
|
||||
import { AlibabaChat } from "../protocols/alibaba-chat.js"
|
||||
import { AlibabaMessages } from "../protocols/alibaba-messages.js"
|
||||
import { AlibabaResponses } from "../protocols/alibaba-responses.js"
|
||||
import { AuthOptions, type AtLeastOne, type ProviderAuthOption } from "../route/auth-options.js"
|
||||
import { Route, type RouteDefaultsInput } from "../route/client.js"
|
||||
import { Endpoint } from "../route/endpoint.js"
|
||||
import { Framing } from "../route/framing.js"
|
||||
import { ProviderID, ToolDefinition, type ModelID } from "../schema/index.js"
|
||||
|
||||
export const id = ProviderID.make("alibaba")
|
||||
|
||||
export type Region =
|
||||
| "ap-southeast-1"
|
||||
| "cn-beijing"
|
||||
| "cn-hongkong"
|
||||
| "us-east-1"
|
||||
| "eu-central-1"
|
||||
| "ap-northeast-1"
|
||||
| (string & {})
|
||||
export type ChatOptionsInput = AlibabaChat.OptionsInput
|
||||
export type MessagesOptionsInput = AlibabaMessages.OptionsInput
|
||||
export type ResponsesOptionsInput = AlibabaResponses.OptionsInput
|
||||
|
||||
type Location = AtLeastOne<{
|
||||
readonly region: Region
|
||||
/** Overrides the selected API's complete base URL, including its version prefix. */
|
||||
readonly baseURL: string
|
||||
}> & { readonly workspaceID?: string }
|
||||
|
||||
export type Config = Location &
|
||||
Omit<RouteDefaultsInput, "providerOptions"> &
|
||||
ProviderAuthOption<"optional"> & {
|
||||
readonly providerOptions?: ChatOptionsInput | MessagesOptionsInput | ResponsesOptionsInput
|
||||
}
|
||||
export type Settings<Options = ChatOptionsInput> = Location &
|
||||
ProviderPackage.Settings & {
|
||||
readonly apiKey?: string
|
||||
readonly providerOptions?: Options
|
||||
}
|
||||
|
||||
const hosts = new Map<string, string>([
|
||||
["ap-southeast-1", "dashscope-intl.aliyuncs.com"],
|
||||
["cn-beijing", "dashscope.aliyuncs.com"],
|
||||
["cn-hongkong", "cn-hongkong.dashscope.aliyuncs.com"],
|
||||
["us-east-1", "dashscope-us.aliyuncs.com"],
|
||||
])
|
||||
const chatRoute = Route.make({
|
||||
id: "alibaba-chat",
|
||||
provider: id,
|
||||
providerMetadataKey: "alibaba",
|
||||
protocol: AlibabaChat.protocol,
|
||||
endpoint: Endpoint.path("/chat/completions"),
|
||||
framing: Framing.sse,
|
||||
})
|
||||
const messagesRoute = Route.make({
|
||||
id: "alibaba-messages",
|
||||
provider: id,
|
||||
providerMetadataKey: "alibaba",
|
||||
protocol: AlibabaMessages.protocol,
|
||||
endpoint: Endpoint.path("/messages"),
|
||||
framing: Framing.sse,
|
||||
headers: () => ({ "anthropic-version": "2023-06-01" }),
|
||||
})
|
||||
const responsesRoute = Route.make({
|
||||
id: "alibaba-responses",
|
||||
provider: id,
|
||||
providerMetadataKey: "alibaba",
|
||||
protocol: AlibabaResponses.protocol,
|
||||
endpoint: Endpoint.path("/responses"),
|
||||
framing: Framing.sse,
|
||||
})
|
||||
|
||||
export const routes = [chatRoute, messagesRoute, responsesRoute]
|
||||
|
||||
export const configure = (input: Config) => {
|
||||
const { apiKey: _key, auth: _auth, region, workspaceID, baseURL, ...rest } = input
|
||||
const host =
|
||||
region === undefined
|
||||
? undefined
|
||||
: workspaceID === undefined
|
||||
? hosts.get(region)
|
||||
: `${workspaceID}.${region}.maas.aliyuncs.com`
|
||||
if (baseURL === undefined) {
|
||||
if (region === undefined) throw new Error("Alibaba requires region or baseURL")
|
||||
if (host === undefined) throw new Error(`Alibaba region ${region} requires workspaceID or baseURL`)
|
||||
}
|
||||
const opts = { ...rest, auth: AuthOptions.bearer(input, ["DASHSCOPE_API_KEY", "ALIBABA_API_KEY"]) }
|
||||
const common = { ...opts, endpoint: { baseURL: baseURL ?? `https://${host}/compatible-mode/v1` } }
|
||||
const chat = (id: string | ModelID) =>
|
||||
chatRoute.with(common).model<ChatOptionsInput>({ id, compatibility: AlibabaChat.compatibility })
|
||||
const messages = (id: string | ModelID) =>
|
||||
messagesRoute
|
||||
.with({
|
||||
...opts,
|
||||
endpoint: { baseURL: baseURL ?? `https://${host}/apps/anthropic/v1` },
|
||||
})
|
||||
.model<MessagesOptionsInput>({ id, compatibility: { requireSignature: false } })
|
||||
const responses = (id: string | ModelID) => responsesRoute.with(common).model<ResponsesOptionsInput>({ id })
|
||||
return { id, model: chat, chat, messages, responses, configure }
|
||||
}
|
||||
|
||||
export const provider = { id, configure }
|
||||
|
||||
export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["model"] = (id, input) =>
|
||||
fromSettings(input).chat(id)
|
||||
export const messagesModel: ProviderPackage.Definition<
|
||||
Settings<MessagesOptionsInput>,
|
||||
MessagesOptionsInput
|
||||
>["model"] = (id, input) => fromSettings(input).messages(id)
|
||||
export const responsesModel: ProviderPackage.Definition<
|
||||
Settings<ResponsesOptionsInput>,
|
||||
ResponsesOptionsInput
|
||||
>["model"] = (id, input) => fromSettings(input).responses(id)
|
||||
|
||||
function fromSettings(input: Settings<Config["providerOptions"]>) {
|
||||
const { body, ...rest } = input
|
||||
return configure({ ...rest, http: body === undefined ? undefined : { body } })
|
||||
}
|
||||
|
||||
export const webSearch = () => hostedTool("web_search", "Search the web with Alibaba's hosted search tool.")
|
||||
export const webExtractor = () => hostedTool("web_extractor", "Extract web page content with Alibaba's hosted tool.")
|
||||
export const codeInterpreter = () => hostedTool("code_interpreter", "Execute code with Alibaba's hosted interpreter.")
|
||||
|
||||
function hostedTool(type: "web_search" | "web_extractor" | "code_interpreter", description: string) {
|
||||
return ToolDefinition.make({
|
||||
name: type,
|
||||
description,
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
native: { alibaba: { type } },
|
||||
})
|
||||
}
|
||||
|
||||
export * as Alibaba from "./alibaba.js"
|
||||
@@ -1 +0,0 @@
|
||||
export { model, type Settings } from "../alibaba.js"
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { Alibaba } from "../alibaba.js"
|
||||
export { messagesModel as model } from "../alibaba.js"
|
||||
export type Settings = Alibaba.Settings<Alibaba.MessagesOptionsInput>
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { Alibaba } from "../alibaba.js"
|
||||
export { responsesModel as model } from "../alibaba.js"
|
||||
export type Settings = Alibaba.Settings<Alibaba.ResponsesOptionsInput>
|
||||
@@ -1,4 +1,3 @@
|
||||
export * as Alibaba from "./alibaba.js"
|
||||
export * as Anthropic from "./anthropic.js"
|
||||
export * as AnthropicCompatible from "./anthropic-compatible.js"
|
||||
export * as AmazonBedrock from "./amazon-bedrock.js"
|
||||
|
||||
Vendored
-35
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-chat",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-chat",
|
||||
"region:ap-southeast-1",
|
||||
"thinking",
|
||||
"usage"
|
||||
],
|
||||
"name": "alibaba-chat/qwen-3-7-plus-streams-thinking-disabled",
|
||||
"recordedAt": "2026-09-08T03:10:42.782Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.7-plus\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"enable_thinking\":false}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream;charset=utf-8"
|
||||
},
|
||||
"body": "data: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"created\":1788837041,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"choices\":[{\"delta\":{\"content\":\"3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837041,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"choices\":[{\"delta\":{\"content\":\"7887\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837041,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.7-plus\",\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788837041,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788837041,\"id\":\"chatcmpl-047bcb67-b193-9a4f-9d77-ea0325be6d7c\",\"model\":\"qwen3.7-plus\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":37}}\n\ndata: [DONE]\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-35
File diff suppressed because one or more lines are too long
-28
File diff suppressed because one or more lines are too long
Vendored
-35
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-chat",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-chat",
|
||||
"region:ap-southeast-1",
|
||||
"tool",
|
||||
"tool-choice"
|
||||
],
|
||||
"name": "alibaba-chat/qwen-3-8-max-obeys-named-tool-choice",
|
||||
"recordedAt": "2026-09-08T03:10:56.576Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"Find the current weather in Paris.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Get weather in a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"]}}}],\"tool_choice\":{\"type\":\"function\",\"function\":{\"name\":\"get_weather\"}},\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":4096}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream;charset=utf-8"
|
||||
},
|
||||
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"id\":\"call_4eb823cb28d141c8befbb331\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"\"}}]},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"index\":0,\"id\":\"\",\"type\":\"function\",\"function\":{\"arguments\":\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"{\\\"city\\\": \\\"Paris\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"\\\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"}\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"content\":\"\",\"tool_calls\":[{\"type\":\"function\",\"index\":0,\"function\":{\"arguments\":\"\"}}]},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"function\":{\"arguments\":\"\"},\"index\":0,\"id\":null,\"type\":\"function\"}],\"content\":\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"choices\":[{\"delta\":{},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788837055,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788837055,\"id\":\"chatcmpl-681dfa5a-ae08-98da-ba0d-4b1d0d364b6b\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":19,\"prompt_tokens\":288,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":288},\"total_tokens\":307}}\n\ndata: [DONE]\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
-73
File diff suppressed because one or more lines are too long
-34
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-chat",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-chat",
|
||||
"region:ap-southeast-1",
|
||||
"structured-output"
|
||||
],
|
||||
"name": "alibaba-chat/qwen-3-8-max-returns-a-json-object",
|
||||
"recordedAt": "2026-09-08T03:11:29.462Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"Return a JSON object with one key \\\"city\\\" set to the capital city of France.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":1024,\"response_format\":{\"type\":\"json_object\"}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream;charset=utf-8"
|
||||
},
|
||||
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"choices\":[{\"delta\":{\"content\":\"{\\\"\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"choices\":[{\"delta\":{\"content\":\"city\\\":\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"choices\":[{\"delta\":{\"content\":\" \\\"Paris\\\"}\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788837088,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788837088,\"id\":\"chatcmpl-de34af77-7b1e-9999-a48f-e564c03d4b6b\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":6,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":38}}\n\ndata: [DONE]\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
-36
File diff suppressed because one or more lines are too long
-36
File diff suppressed because one or more lines are too long
-36
File diff suppressed because one or more lines are too long
-36
File diff suppressed because one or more lines are too long
-36
File diff suppressed because one or more lines are too long
-36
File diff suppressed because one or more lines are too long
-36
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-chat",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-chat",
|
||||
"region:ap-southeast-1",
|
||||
"text",
|
||||
"reasoning",
|
||||
"usage"
|
||||
],
|
||||
"name": "alibaba-chat/qwen-3-8-max-streams-none-effort",
|
||||
"recordedAt": "2026-09-08T03:09:27.584Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"none\",\"max_completion_tokens\":4096}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream;charset=utf-8"
|
||||
},
|
||||
"body": "data: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null,\"choices\":[{\"logprobs\":null,\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":null}]}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"choices\":[{\"delta\":{\"content\":\"3\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"choices\":[{\"delta\":{\"content\":\"78\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"choices\":[{\"delta\":{\"content\":\"87\"},\"index\":0,\"finish_reason\":null,\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"model\":\"qwen3.8-max\",\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"choices\":[{\"delta\":{\"content\":\"\"},\"index\":0,\"finish_reason\":\"stop\",\"logprobs\":null}],\"created\":1788836967,\"object\":\"chat.completion.chunk\",\"usage\":null}\n\ndata: {\"choices\":[],\"created\":1788836967,\"id\":\"chatcmpl-d8fbc8fe-4d5a-9dc9-84c6-ed0c7b9dd3bb\",\"model\":\"qwen3.8-max\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":32,\"prompt_tokens_details\":{\"cached_tokens\":0,\"text_tokens\":32},\"total_tokens\":37}}\n\ndata: [DONE]\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
-36
File diff suppressed because one or more lines are too long
Vendored
-35
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-messages",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-messages",
|
||||
"region:ap-southeast-1",
|
||||
"thinking",
|
||||
"usage"
|
||||
],
|
||||
"name": "alibaba-messages/qwen-3-7-plus-streams-thinking-disabled",
|
||||
"recordedAt": "2026-09-08T03:10:57.819Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/apps/anthropic/v1/messages",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.7-plus\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":4096,\"thinking\":{\"type\":\"disabled\"}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.7-plus\",\"id\":\"msg_c4d58b4f-a61d-9d0c-a9e4-cb45d34b1120\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":20,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"3\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"7887\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"output_tokens\":5,\"cache_creation_input_tokens\":0,\"input_tokens\":32,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-35
File diff suppressed because one or more lines are too long
Vendored
-34
File diff suppressed because one or more lines are too long
Vendored
-34
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-messages",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-messages",
|
||||
"region:ap-southeast-1",
|
||||
"structured-output"
|
||||
],
|
||||
"name": "alibaba-messages/qwen-3-8-max-follows-a-json-schema",
|
||||
"recordedAt": "2026-09-08T03:11:30.530Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/apps/anthropic/v1/messages",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Return a JSON object with one key \\\"city\\\" set to the capital city of France.\"}]}],\"stream\":true,\"max_tokens\":1024,\"thinking\":{\"type\":\"disabled\"},\"output_config\":{\"format\":{\"type\":\"json_schema\",\"schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.8-max\",\"id\":\"msg_16e067c1-984b-94d3-9abb-11792d794271\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":18,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"{\\\"\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"city\\\":\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"Paris\\\"}\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"output_tokens\":6,\"cache_creation_input_tokens\":0,\"input_tokens\":32,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-35
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-messages",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-messages",
|
||||
"region:ap-southeast-1",
|
||||
"tool",
|
||||
"tool-choice"
|
||||
],
|
||||
"name": "alibaba-messages/qwen-3-8-max-obeys-named-tool-choice",
|
||||
"recordedAt": "2026-09-08T03:11:06.590Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/apps/anthropic/v1/messages",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Find the current weather in Paris.\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get weather in a city\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"]}}],\"tool_choice\":{\"type\":\"tool\",\"name\":\"get_weather\"},\"stream\":true,\"max_tokens\":4096,\"thinking\":{\"type\":\"disabled\"}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.8-max\",\"id\":\"msg_0e8f1abf-f2bc-9a86-a6a7-14804ac17eac\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":45,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"name\":\"get_weather\",\"input\":{},\"id\":\"toolu_cf9cab33261f4709ae096d8a\",\"type\":\"tool_use\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"{\\\"city\\\": \\\"Paris\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"\\\"\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"}\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"output_tokens\":19,\"cache_creation_input_tokens\":0,\"input_tokens\":288,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
-73
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
-36
File diff suppressed because one or more lines are too long
-36
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-messages",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-messages",
|
||||
"region:ap-southeast-1",
|
||||
"text",
|
||||
"reasoning",
|
||||
"usage"
|
||||
],
|
||||
"name": "alibaba-messages/qwen-3-8-max-streams-max-effort",
|
||||
"recordedAt": "2026-09-08T03:12:36.479Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/apps/anthropic/v1/messages",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":4096,\"output_config\":{\"effort\":\"max\"}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.8-max\",\"id\":\"msg_7952446b-0176-9526-ad7d-b1f0184bc106\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":20,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"thinking\",\"signature\":\"\",\"thinking\":\"\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"We\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" need answer user\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"'s simple multiplication with\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" only final integer.\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" Need compute 1\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"73*2\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"19. \"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173*\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"200=\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"3460\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"0; 1\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"73*1\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"9=32\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"87 (\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173*\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"20=3\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"460-\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173=\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"3287\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"); sum=3\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"7887\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\". Final only integer\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\".\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"signature_delta\",\"signature\":\"\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"},\"index\":1}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"37\"},\"type\":\"content_block_delta\",\"index\":1}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"887\"},\"type\":\"content_block_delta\",\"index\":1}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":1}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"output_tokens\":92,\"cache_creation_input_tokens\":0,\"input_tokens\":81,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-messages",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-messages",
|
||||
"region:ap-southeast-1",
|
||||
"text",
|
||||
"reasoning",
|
||||
"usage"
|
||||
],
|
||||
"name": "alibaba-messages/qwen-3-8-max-streams-xhigh-effort",
|
||||
"recordedAt": "2026-09-08T03:10:04.633Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/apps/anthropic/v1/messages",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":4096,\"output_config\":{\"effort\":\"xhigh\"}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.8-max\",\"id\":\"msg_a57b8563-71fb-9248-a31e-c49d6ba38717\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":20,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"thinking\",\"signature\":\"\",\"thinking\":\"\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"We\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" need answer simple multiplication\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\". We\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" already call\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\". Need compute \"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173*\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"219.\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 173\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"*200\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"=346\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"00; *\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"19=3\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"287;\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" sum 37\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"88\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"7. Final only\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" integer. Ensure\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" no extra.\\n\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"signature_delta\",\"signature\":\"\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"},\"index\":1}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"378\"},\"type\":\"content_block_delta\",\"index\":1}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"87\"},\"type\":\"content_block_delta\",\"index\":1}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":1}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"output_tokens\":69,\"cache_creation_input_tokens\":0,\"input_tokens\":81,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-35
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-responses",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-responses",
|
||||
"region:ap-southeast-1",
|
||||
"thinking",
|
||||
"usage"
|
||||
],
|
||||
"name": "alibaba-responses/qwen-3-7-plus-streams-thinking-disabled",
|
||||
"recordedAt": "2026-09-08T03:11:07.495Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/responses",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.7-plus\",\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"max_output_tokens\":4096,\"enable_thinking\":false,\"stream\":true}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream;charset=UTF-8"
|
||||
},
|
||||
"body": "id:1\nevent:response.created\n:HTTP_STATUS/200\ndata:{\"sequence_number\":0,\"type\":\"response.created\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"created_at\":1788837067,\"store\":true,\"tools\":[],\"output\":[],\"top_p\":1.0,\"completed_at\":1788837067,\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.7-plus\",\"service_tier\":\"default\",\"id\":\"resp_493607bf-baef-9ced-9e36-2e2a583b6177\",\"max_output_tokens\":4096,\"object\":\"response\",\"status\":\"queued\"}}\n\nid:2\nevent:response.in_progress\n:HTTP_STATUS/200\ndata:{\"sequence_number\":1,\"type\":\"response.in_progress\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"created_at\":1788837067,\"store\":true,\"tools\":[],\"output\":[],\"top_p\":1.0,\"completed_at\":1788837067,\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.7-plus\",\"service_tier\":\"default\",\"id\":\"resp_493607bf-baef-9ced-9e36-2e2a583b6177\",\"max_output_tokens\":4096,\"object\":\"response\",\"status\":\"in_progress\"}}\n\nid:3\nevent:response.output_item.added\n:HTTP_STATUS/200\ndata:{\"sequence_number\":2,\"item\":{\"id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"status\":\"in_progress\"},\"output_index\":0,\"type\":\"response.output_item.added\"}\n\nid:4\nevent:response.content_part.added\n:HTTP_STATUS/200\ndata:{\"sequence_number\":3,\"output_index\":0,\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"part\":{\"type\":\"output_text\",\"annotations\":[],\"text\":\"\"}}\n\nid:5\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":4,\"content_index\":0,\"item_id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"delta\":\"3\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:6\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":5,\"content_index\":0,\"item_id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"delta\":\"7887\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:7\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":6,\"content_index\":0,\"item_id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"delta\":\"\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:8\nevent:response.output_text.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":7,\"content_index\":0,\"item_id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"text\":\"37887\",\"output_index\":0,\"type\":\"response.output_text.done\",\"logprobs\":[]}\n\nid:9\nevent:response.content_part.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":8,\"output_index\":0,\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"part\":{\"type\":\"output_text\",\"annotations\":[],\"text\":\"37887\"}}\n\nid:10\nevent:response.output_item.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":9,\"item\":{\"id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"text\":\"37887\"}],\"status\":\"completed\"},\"output_index\":0,\"type\":\"response.output_item.done\"}\n\nid:11\nevent:response.completed\n:HTTP_STATUS/200\ndata:{\"sequence_number\":10,\"type\":\"response.completed\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"usage\":{\"total_tokens\":73,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":5,\"input_tokens\":68,\"output_tokens_details\":{\"reasoning_tokens\":0},\"x_details\":[{\"total_tokens\":73,\"x_billing_type\":\"response_api\",\"output_tokens\":5,\"input_tokens\":68,\"prompt_tokens_details\":{\"cached_tokens\":0}}]},\"created_at\":1788837067,\"store\":true,\"tools\":[],\"output\":[{\"id\":\"msg_509c6dec-e527-41b5-a9c9-7f02fecb7a3f\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"text\":\"37887\"}],\"status\":\"completed\"}],\"top_p\":1.0,\"completed_at\":1788837067,\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.7-plus\",\"service_tier\":\"default\",\"id\":\"resp_493607bf-baef-9ced-9e36-2e2a583b6177\",\"max_output_tokens\":4096,\"object\":\"response\",\"status\":\"completed\"}}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-35
File diff suppressed because one or more lines are too long
Vendored
-34
File diff suppressed because one or more lines are too long
packages/ai/test/fixtures/recordings/alibaba-responses/qwen-3-8-max-continues-a-stored-response.json
Vendored
-53
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:alibaba-responses",
|
||||
"provider:alibaba",
|
||||
"protocol:alibaba-responses",
|
||||
"region:ap-southeast-1",
|
||||
"continuation",
|
||||
"storage"
|
||||
],
|
||||
"name": "alibaba-responses/qwen-3-8-max-continues-a-stored-response",
|
||||
"recordedAt": "2026-09-08T03:12:42.429Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/responses",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Remember the password word apricot. Reply OK.\"}]}],\"store\":true,\"reasoning\":{\"effort\":\"none\"},\"max_output_tokens\":1024,\"stream\":true}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream;charset=UTF-8"
|
||||
},
|
||||
"body": "id:1\nevent:response.created\n:HTTP_STATUS/200\ndata:{\"sequence_number\":0,\"type\":\"response.created\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"reasoning\":{\"effort\":\"none\"},\"created_at\":1788837161,\"store\":true,\"tools\":[],\"output\":[],\"top_p\":1.0,\"completed_at\":1788837161,\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.8-max\",\"service_tier\":\"default\",\"id\":\"resp_97f8cd28-c7ab-9d51-9c14-4fc531decdc8\",\"max_output_tokens\":1024,\"object\":\"response\",\"status\":\"queued\"}}\n\nid:2\nevent:response.in_progress\n:HTTP_STATUS/200\ndata:{\"sequence_number\":1,\"type\":\"response.in_progress\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"reasoning\":{\"effort\":\"none\"},\"created_at\":1788837161,\"store\":true,\"tools\":[],\"output\":[],\"top_p\":1.0,\"completed_at\":1788837161,\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.8-max\",\"service_tier\":\"default\",\"id\":\"resp_97f8cd28-c7ab-9d51-9c14-4fc531decdc8\",\"max_output_tokens\":1024,\"object\":\"response\",\"status\":\"in_progress\"}}\n\nid:3\nevent:response.output_item.added\n:HTTP_STATUS/200\ndata:{\"sequence_number\":2,\"item\":{\"id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"status\":\"in_progress\"},\"output_index\":0,\"type\":\"response.output_item.added\"}\n\nid:4\nevent:response.content_part.added\n:HTTP_STATUS/200\ndata:{\"sequence_number\":3,\"output_index\":0,\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"part\":{\"type\":\"output_text\",\"annotations\":[],\"text\":\"\"}}\n\nid:5\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":4,\"content_index\":0,\"item_id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"delta\":\"OK\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:6\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":5,\"content_index\":0,\"item_id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"delta\":\".\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:7\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":6,\"content_index\":0,\"item_id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"delta\":\"\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:8\nevent:response.output_text.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":7,\"content_index\":0,\"item_id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"text\":\"OK.\",\"output_index\":0,\"type\":\"response.output_text.done\",\"logprobs\":[]}\n\nid:9\nevent:response.content_part.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":8,\"output_index\":0,\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"part\":{\"type\":\"output_text\",\"annotations\":[],\"text\":\"OK.\"}}\n\nid:10\nevent:response.output_item.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":9,\"item\":{\"id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"text\":\"OK.\"}],\"status\":\"completed\"},\"output_index\":0,\"type\":\"response.output_item.done\"}\n\nid:11\nevent:response.completed\n:HTTP_STATUS/200\ndata:{\"sequence_number\":10,\"type\":\"response.completed\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"reasoning\":{\"effort\":\"none\"},\"usage\":{\"total_tokens\":60,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":2,\"input_tokens\":58,\"output_tokens_details\":{\"reasoning_tokens\":0},\"x_details\":[{\"total_tokens\":60,\"x_billing_type\":\"response_api\",\"output_tokens\":2,\"input_tokens\":58,\"prompt_tokens_details\":{\"cached_tokens\":0}}]},\"created_at\":1788837161,\"store\":true,\"tools\":[],\"output\":[{\"id\":\"msg_a908ad8e-6220-444b-880c-02aecbaf8fac\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"text\":\"OK.\"}],\"status\":\"completed\"}],\"top_p\":1.0,\"completed_at\":1788837161,\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.8-max\",\"service_tier\":\"default\",\"id\":\"resp_97f8cd28-c7ab-9d51-9c14-4fc531decdc8\",\"max_output_tokens\":1024,\"object\":\"response\",\"status\":\"completed\"}}\n\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/responses",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What word did I ask you to remember? Reply with only the word.\"}]}],\"store\":true,\"reasoning\":{\"effort\":\"none\"},\"max_output_tokens\":1024,\"previous_response_id\":\"resp_97f8cd28-c7ab-9d51-9c14-4fc531decdc8\",\"stream\":true}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream;charset=UTF-8"
|
||||
},
|
||||
"body": "id:1\nevent:response.created\n:HTTP_STATUS/200\ndata:{\"sequence_number\":0,\"type\":\"response.created\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"reasoning\":{\"effort\":\"none\"},\"created_at\":1788837162,\"store\":true,\"tools\":[],\"output\":[],\"top_p\":1.0,\"completed_at\":1788837162,\"previous_response_id\":\"resp_97f8cd28-c7ab-9d51-9c14-4fc531decdc8\",\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.8-max\",\"service_tier\":\"default\",\"id\":\"resp_1d81d34c-d3ec-9f98-ab59-5d4641d4217a\",\"max_output_tokens\":1024,\"object\":\"response\",\"status\":\"queued\"}}\n\nid:2\nevent:response.in_progress\n:HTTP_STATUS/200\ndata:{\"sequence_number\":1,\"type\":\"response.in_progress\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"reasoning\":{\"effort\":\"none\"},\"created_at\":1788837162,\"store\":true,\"tools\":[],\"output\":[],\"top_p\":1.0,\"completed_at\":1788837162,\"previous_response_id\":\"resp_97f8cd28-c7ab-9d51-9c14-4fc531decdc8\",\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.8-max\",\"service_tier\":\"default\",\"id\":\"resp_1d81d34c-d3ec-9f98-ab59-5d4641d4217a\",\"max_output_tokens\":1024,\"object\":\"response\",\"status\":\"in_progress\"}}\n\nid:3\nevent:response.output_item.added\n:HTTP_STATUS/200\ndata:{\"sequence_number\":2,\"item\":{\"id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"status\":\"in_progress\"},\"output_index\":0,\"type\":\"response.output_item.added\"}\n\nid:4\nevent:response.content_part.added\n:HTTP_STATUS/200\ndata:{\"sequence_number\":3,\"output_index\":0,\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"part\":{\"type\":\"output_text\",\"annotations\":[],\"text\":\"\"}}\n\nid:5\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":4,\"content_index\":0,\"item_id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"delta\":\"ap\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:6\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":5,\"content_index\":0,\"item_id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"delta\":\"ricot\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:7\nevent:response.output_text.delta\n:HTTP_STATUS/200\ndata:{\"sequence_number\":6,\"content_index\":0,\"item_id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"delta\":\"\",\"output_index\":0,\"type\":\"response.output_text.delta\",\"logprobs\":[]}\n\nid:8\nevent:response.output_text.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":7,\"content_index\":0,\"item_id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"text\":\"apricot\",\"output_index\":0,\"type\":\"response.output_text.done\",\"logprobs\":[]}\n\nid:9\nevent:response.content_part.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":8,\"output_index\":0,\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"part\":{\"type\":\"output_text\",\"annotations\":[],\"text\":\"apricot\"}}\n\nid:10\nevent:response.output_item.done\n:HTTP_STATUS/200\ndata:{\"sequence_number\":9,\"item\":{\"id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"text\":\"apricot\"}],\"status\":\"completed\"},\"output_index\":0,\"type\":\"response.output_item.done\"}\n\nid:11\nevent:response.completed\n:HTTP_STATUS/200\ndata:{\"sequence_number\":10,\"type\":\"response.completed\",\"response\":{\"top_logprobs\":0,\"metadata\":{},\"presence_penalty\":0.0,\"reasoning\":{\"effort\":\"none\"},\"usage\":{\"total_tokens\":92,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":3,\"input_tokens\":89,\"output_tokens_details\":{\"reasoning_tokens\":0},\"x_details\":[{\"total_tokens\":92,\"x_billing_type\":\"response_api\",\"output_tokens\":3,\"input_tokens\":89,\"prompt_tokens_details\":{\"cached_tokens\":0}}]},\"created_at\":1788837162,\"store\":true,\"tools\":[],\"output\":[{\"id\":\"msg_7bc50c33-8fbd-4b10-91fc-4f2533bb2091\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"text\":\"apricot\"}],\"status\":\"completed\"}],\"top_p\":1.0,\"completed_at\":1788837162,\"previous_response_id\":\"resp_97f8cd28-c7ab-9d51-9c14-4fc531decdc8\",\"frequency_penalty\":0.0,\"parallel_tool_calls\":true,\"background\":false,\"temperature\":1.0,\"tool_choice\":\"auto\",\"model\":\"qwen3.8-max\",\"service_tier\":\"default\",\"id\":\"resp_1d81d34c-d3ec-9f98-ab59-5d4641d4217a\",\"max_output_tokens\":1024,\"object\":\"response\",\"status\":\"completed\"}}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-35
File diff suppressed because one or more lines are too long
-73
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
Vendored
-36
File diff suppressed because one or more lines are too long
-35
File diff suppressed because one or more lines are too long
-36
File diff suppressed because one or more lines are too long
@@ -1,39 +0,0 @@
|
||||
import { LLM } from "../../src/index.js"
|
||||
import { Alibaba } from "../../src/providers.js"
|
||||
|
||||
const provider = Alibaba.configure({ region: "ap-southeast-1" })
|
||||
Alibaba.configure({ baseURL: "https://gateway.example/v1" })
|
||||
Alibaba.configure({ region: "eu-central-1", workspaceID: "llm-workspace" })
|
||||
LLM.request({
|
||||
model: provider.chat("qwen3.8-max"),
|
||||
providerOptions: { reasoningEffort: "future", enableThinking: true, preserveThinking: false, toolStream: true },
|
||||
})
|
||||
LLM.request({
|
||||
model: provider.messages("qwen3.8-max"),
|
||||
providerOptions: { effort: "xhigh", thinking: { type: "enabled" } },
|
||||
})
|
||||
LLM.request({
|
||||
model: provider.messages("qwen3.7-plus"),
|
||||
providerOptions: { thinking: { type: "enabled", budgetTokens: 512 } },
|
||||
})
|
||||
LLM.request({
|
||||
model: provider.responses("qwen3.8-max"),
|
||||
providerOptions: { reasoningEffort: "low", enableThinking: true, store: true, previousResponseId: "resp_previous" },
|
||||
})
|
||||
// @ts-expect-error Region or complete base URL is required.
|
||||
Alibaba.configure({ apiKey: "fixture" })
|
||||
LLM.request({
|
||||
model: provider.chat("qwen3.8-max"),
|
||||
// @ts-expect-error Thinking toggle is a boolean.
|
||||
providerOptions: { enableThinking: "true" },
|
||||
})
|
||||
LLM.request({
|
||||
model: provider.messages("qwen3.8-max"),
|
||||
// @ts-expect-error Messages uses effort.
|
||||
providerOptions: { reasoningEffort: "high" },
|
||||
})
|
||||
LLM.request({
|
||||
model: provider.responses("qwen3.8-max"),
|
||||
// @ts-expect-error Responses does not use Chat thinking budgets.
|
||||
providerOptions: { thinkingBudget: 512 },
|
||||
})
|
||||
@@ -51,10 +51,6 @@ describe("provider package entrypoints", () => {
|
||||
import("@opencode/ai/providers/zai-coding-plan/chat"),
|
||||
import("@opencode/ai/providers/zai-coding-plan/messages"),
|
||||
import("@opencode/ai/providers/zai-coding-plan/responses"),
|
||||
import("@opencode/ai/providers/alibaba"),
|
||||
import("@opencode/ai/providers/alibaba/chat"),
|
||||
import("@opencode/ai/providers/alibaba/messages"),
|
||||
import("@opencode/ai/providers/alibaba/responses"),
|
||||
])
|
||||
|
||||
for (const module of modules) expect(module.model).toBeFunction()
|
||||
@@ -65,34 +61,6 @@ describe("provider package entrypoints", () => {
|
||||
expect(modules[19].model).not.toBe(modules[20].model)
|
||||
})
|
||||
|
||||
test("maps Alibaba API entrypoints onto explicit regional routes", async () => {
|
||||
const modules = await Promise.all([
|
||||
import("@opencode/ai/providers/alibaba"),
|
||||
import("@opencode/ai/providers/alibaba/chat"),
|
||||
import("@opencode/ai/providers/alibaba/messages"),
|
||||
import("@opencode/ai/providers/alibaba/responses"),
|
||||
])
|
||||
expect(modules[0].model).toBe(modules[1].model)
|
||||
const settings = {
|
||||
region: "eu-central-1",
|
||||
workspaceID: "llm-fixture",
|
||||
apiKey: "fixture",
|
||||
headers: { "x-test": "fixture" },
|
||||
body: { extension: true },
|
||||
}
|
||||
const routes = ["alibaba-chat", "alibaba-chat", "alibaba-messages", "alibaba-responses"]
|
||||
modules.forEach((module, index) => {
|
||||
const model = module.model("qwen3.8-max", settings)
|
||||
expect(model.provider).toBe("alibaba")
|
||||
expect(model.route.id).toBe(routes[index])
|
||||
expect(model.route.endpoint.baseURL).toBe(
|
||||
`https://llm-fixture.eu-central-1.maas.aliyuncs.com/${index === 2 ? "apps/anthropic/v1" : "compatible-mode/v1"}`,
|
||||
)
|
||||
expect(model.route.defaults.headers).toEqual(settings.headers)
|
||||
expect(model.route.defaults.http?.body).toEqual(settings.body)
|
||||
})
|
||||
})
|
||||
|
||||
test("maps Moonshot API entrypoints onto provider-owned routes", async () => {
|
||||
const modules = await Promise.all([
|
||||
import("@opencode/ai/providers/moonshot"),
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { LLM, LLMEvent, LLMRequest, Message, ToolDefinition } from "../../src/index.js"
|
||||
import { Alibaba } from "../../src/providers.js"
|
||||
import { LLMClient } from "../../src/route.js"
|
||||
import { compileRequest } from "../../src/route/client.js"
|
||||
import { recordedTests } from "../recorded-test.js"
|
||||
|
||||
const alibaba = Alibaba.configure({ region: "ap-southeast-1", apiKey: process.env.ALIBABA_API_KEY ?? "fixture" })
|
||||
const record = (api: "chat" | "messages" | "responses") =>
|
||||
recordedTests({
|
||||
prefix: `alibaba-${api}`,
|
||||
provider: "alibaba",
|
||||
protocol: `alibaba-${api}`,
|
||||
requires: ["ALIBABA_API_KEY"],
|
||||
tags: ["region:ap-southeast-1"],
|
||||
})
|
||||
|
||||
for (const api of ["chat", "messages", "responses"] as const) {
|
||||
const recorded = record(api)
|
||||
describe(`Alibaba ${api} capabilities`, () => {
|
||||
for (const enabled of [false, true]) {
|
||||
recorded.effect.with(
|
||||
`Qwen 3.7 Plus streams thinking ${enabled ? "enabled" : "disabled"}`,
|
||||
{ tags: ["thinking", "usage"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const request = LLM.request({
|
||||
model: alibaba[api]("qwen3.7-plus"),
|
||||
providerOptions:
|
||||
api === "messages"
|
||||
? { thinking: { type: enabled ? "enabled" : "disabled", ...(enabled ? { budgetTokens: 1024 } : {}) } }
|
||||
: api === "chat"
|
||||
? { enableThinking: enabled, ...(enabled ? { thinkingBudget: 1024 } : {}) }
|
||||
: { enableThinking: enabled },
|
||||
prompt: "What is 173 multiplied by 219? Reply with only the final integer.",
|
||||
generation: { maxTokens: 4096 },
|
||||
})
|
||||
const compiled = yield* compileRequest(request)
|
||||
expect(api === "messages" ? compiled.body.thinking.type : compiled.body.enable_thinking).toBe(
|
||||
api === "messages" ? (enabled ? "enabled" : "disabled") : enabled,
|
||||
)
|
||||
const response = yield* LLMClient.generate(request)
|
||||
expect(response.text.replaceAll(",", "")).toContain("37887")
|
||||
expect(response.reasoning.length > 0).toBe(enabled)
|
||||
expect(response.finishReason.normalized).toBe("stop")
|
||||
expect(response.usage?.inputTokens).toBeGreaterThan(0)
|
||||
expect(response.usage?.outputTokens).toBeGreaterThan(0)
|
||||
}),
|
||||
120_000,
|
||||
)
|
||||
}
|
||||
recorded.effect.with(
|
||||
"Qwen 3.8 Flash reads image bytes",
|
||||
{ tags: ["image"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const bytes = yield* Effect.promise(() =>
|
||||
Bun.file(new URL("../fixtures/media/restroom.png", import.meta.url)).bytes(),
|
||||
)
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: alibaba[api]("qwen3.8-flash"),
|
||||
providerOptions: api === "messages" ? { thinking: { type: "disabled" } } : { enableThinking: false },
|
||||
messages: [
|
||||
Message.user([
|
||||
{ type: "text", text: "Read the three words in this image. Reply with only the words in order." },
|
||||
{ type: "media", mediaType: "image/png", data: bytes },
|
||||
]),
|
||||
],
|
||||
generation: { maxTokens: 4096 },
|
||||
}),
|
||||
)
|
||||
expect(response.text.toLowerCase()).toContain("jiggling restroom prison")
|
||||
expect(response.finishReason.normalized).toBe("stop")
|
||||
}),
|
||||
120_000,
|
||||
)
|
||||
recorded.effect.with(
|
||||
"Qwen 3.8 Max obeys named tool choice",
|
||||
{ tags: ["tool", "tool-choice"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: alibaba[api]("qwen3.8-max"),
|
||||
prompt: "Find the current weather in Paris.",
|
||||
providerOptions: api === "messages" ? { thinking: { type: "disabled" } } : { reasoningEffort: "none" },
|
||||
tools: [
|
||||
ToolDefinition.make({
|
||||
name: "get_weather",
|
||||
description: "Get weather in a city",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: { city: { type: "string", enum: ["Paris"] } },
|
||||
required: ["city"],
|
||||
},
|
||||
}),
|
||||
],
|
||||
toolChoice: { type: "tool", name: "get_weather" },
|
||||
generation: { maxTokens: 4096 },
|
||||
}),
|
||||
)
|
||||
expect(response.toolCalls).toMatchObject([{ name: "get_weather", input: { city: "Paris" } }])
|
||||
expect(response.finishReason.normalized).toBe(api === "messages" ? "stop" : "tool-calls")
|
||||
if (api === "messages") expect(response.finishReason.raw).toBe("end_turn")
|
||||
}),
|
||||
120_000,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
record("chat").effect.with(
|
||||
"Qwen 3.8 Max returns a JSON object",
|
||||
{ tags: ["structured-output"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: alibaba.chat("qwen3.8-max"),
|
||||
prompt: 'Return a JSON object with one key "city" set to the capital city of France.',
|
||||
providerOptions: { reasoningEffort: "none", responseFormat: { type: "json_object" } },
|
||||
generation: { maxTokens: 1024 },
|
||||
}),
|
||||
)
|
||||
expect(JSON.parse(response.text)).toEqual({ city: "Paris" })
|
||||
expect(response.finishReason.normalized).toBe("stop")
|
||||
}),
|
||||
120_000,
|
||||
)
|
||||
|
||||
record("messages").effect.with(
|
||||
"Qwen 3.8 Max follows a JSON schema",
|
||||
{ tags: ["structured-output"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: alibaba.messages("qwen3.8-max"),
|
||||
prompt: 'Return a JSON object with one key "city" set to the capital city of France.',
|
||||
providerOptions: {
|
||||
thinking: { type: "disabled" },
|
||||
outputConfig: {
|
||||
format: {
|
||||
type: "json_schema",
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: { city: { type: "string" } },
|
||||
required: ["city"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
generation: { maxTokens: 1024 },
|
||||
}),
|
||||
)
|
||||
expect(JSON.parse(response.text)).toEqual({ city: "Paris" })
|
||||
expect(response.finishReason.normalized).toBe("stop")
|
||||
}),
|
||||
120_000,
|
||||
)
|
||||
|
||||
const responses = record("responses")
|
||||
responses.effect.with(
|
||||
"Qwen 3.8 Max continues a stored response",
|
||||
{ tags: ["continuation", "storage"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const first = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: alibaba.responses("qwen3.8-max"),
|
||||
prompt: "Remember the password word apricot. Reply OK.",
|
||||
providerOptions: { store: true, reasoningEffort: "none" },
|
||||
generation: { maxTokens: 1024 },
|
||||
}),
|
||||
)
|
||||
const id = first.events.find(LLMEvent.is.finish)?.providerMetadata?.alibaba?.responseId
|
||||
expect(id).toBeString()
|
||||
if (typeof id !== "string") throw new Error("Missing Alibaba response ID")
|
||||
const second = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: alibaba.responses("qwen3.8-max"),
|
||||
prompt: "What word did I ask you to remember? Reply with only the word.",
|
||||
providerOptions: { previousResponseId: id, store: true, reasoningEffort: "none" },
|
||||
generation: { maxTokens: 1024 },
|
||||
}),
|
||||
)
|
||||
expect(second.text.toLowerCase()).toContain("apricot")
|
||||
expect(second.finishReason.normalized).toBe("stop")
|
||||
}),
|
||||
120_000,
|
||||
)
|
||||
|
||||
responses.effect.with(
|
||||
"Qwen 3.8 Max uses hosted web search and extraction",
|
||||
{ tags: ["hosted-tool", "web-search", "web-extractor"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const request = LLM.request({
|
||||
model: alibaba.responses("qwen3.8-max"),
|
||||
prompt:
|
||||
"Use web search to find Alibaba Cloud Model Studio's official documentation, then use web_extractor to read the page. Give a brief summary with the source URL.",
|
||||
tools: [Alibaba.webSearch(), Alibaba.webExtractor()],
|
||||
providerOptions: { reasoningEffort: "low", store: false },
|
||||
generation: { maxTokens: 4096 },
|
||||
})
|
||||
const response = yield* LLMClient.generate(request)
|
||||
expect(response.toolCalls).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ name: "web_search", providerExecuted: true }),
|
||||
expect.objectContaining({ name: "web_extractor", providerExecuted: true }),
|
||||
]),
|
||||
)
|
||||
expect(response.text.toLowerCase()).toContain("alibaba")
|
||||
expect(response.events.some(LLMEvent.is.toolResult)).toBe(true)
|
||||
const replay = yield* compileRequest(
|
||||
LLMRequest.update(request, {
|
||||
messages: [...request.messages, response.message, Message.user("Summarize in one sentence.")],
|
||||
}),
|
||||
)
|
||||
expect(replay.body.input).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ type: "web_search_call" }),
|
||||
expect.objectContaining({ type: "web_extractor_call" }),
|
||||
]),
|
||||
)
|
||||
}),
|
||||
180_000,
|
||||
)
|
||||
|
||||
responses.effect.with(
|
||||
"Qwen 3.8 Max uses hosted code interpreter",
|
||||
{ tags: ["hosted-tool", "code-interpreter"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: alibaba.responses("qwen3.8-max"),
|
||||
prompt:
|
||||
"Use the code interpreter to compute the SHA-256 hash of the UTF-8 string hello (no newline). Reply with only the hash.",
|
||||
tools: [Alibaba.codeInterpreter()],
|
||||
providerOptions: { reasoningEffort: "low" },
|
||||
generation: { maxTokens: 4096 },
|
||||
}),
|
||||
)
|
||||
expect(response.toolCalls).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: "code_interpreter", providerExecuted: true })]),
|
||||
)
|
||||
expect(response.text).toContain("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
|
||||
expect(response.events.some(LLMEvent.is.toolResult)).toBe(true)
|
||||
}),
|
||||
180_000,
|
||||
)
|
||||
@@ -1,163 +0,0 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { LLM, LLMEvent, LLMRequest, Message, ToolDefinition, type LLMResponse } from "../../src/index.js"
|
||||
import { Alibaba } from "../../src/providers.js"
|
||||
import { LLMClient } from "../../src/route.js"
|
||||
import { compileRequest } from "../../src/route/client.js"
|
||||
import { recordedTests } from "../recorded-test.js"
|
||||
|
||||
const alibaba = Alibaba.configure({ region: "ap-southeast-1", apiKey: process.env.ALIBABA_API_KEY ?? "fixture" })
|
||||
const weather = ToolDefinition.make({
|
||||
name: "get_weather",
|
||||
description: "Get the current weather in a city",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: { city: { type: "string", enum: ["Paris"] } },
|
||||
required: ["city"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
})
|
||||
|
||||
for (const api of ["chat", "messages", "responses"] as const) {
|
||||
const recorded = recordedTests({
|
||||
prefix: `alibaba-${api}`,
|
||||
provider: "alibaba",
|
||||
protocol: `alibaba-${api}`,
|
||||
requires: ["ALIBABA_API_KEY"],
|
||||
tags: ["region:ap-southeast-1"],
|
||||
})
|
||||
describe(`Alibaba ${api}`, () => {
|
||||
for (const effort of api === "messages"
|
||||
? [undefined, "low", "medium", "high", "xhigh", "max"]
|
||||
: [undefined, "none", "minimal", "low", "medium", "high", "xhigh", "max"]) {
|
||||
recorded.effect.with(
|
||||
`Qwen 3.8 Max streams ${effort ?? "default"} effort`,
|
||||
{ tags: ["text", "reasoning", "usage"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const request = LLM.request({
|
||||
model: alibaba[api]("qwen3.8-max"),
|
||||
prompt: "What is 173 multiplied by 219? Reply with only the final integer.",
|
||||
providerOptions: api === "messages" ? { effort } : { reasoningEffort: effort },
|
||||
generation: { maxTokens: 4096 },
|
||||
})
|
||||
const compiled = yield* compileRequest(request)
|
||||
expect(compiled.body.enable_thinking).toBeUndefined()
|
||||
expect(compiled.body.thinking).toBeUndefined()
|
||||
expect(
|
||||
api === "chat"
|
||||
? compiled.body.reasoning_effort
|
||||
: api === "messages"
|
||||
? compiled.body.output_config?.effort
|
||||
: compiled.body.reasoning?.effort,
|
||||
).toBe(effort)
|
||||
const response = yield* LLMClient.generate(request)
|
||||
expect(response.text.replaceAll(",", "")).toContain("37887")
|
||||
expect(response.reasoning.length > 0).toBe(effort !== "none")
|
||||
expect(response.finishReason.normalized).toBe("stop")
|
||||
expectUsage(response)
|
||||
}),
|
||||
120_000,
|
||||
)
|
||||
}
|
||||
|
||||
recorded.effect.with(
|
||||
"Qwen 3.8 Max replays reasoning through a tool loop and follow-up",
|
||||
{ tags: ["tool", "tool-loop", "reasoning", "continuation"] },
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const request = LLM.request({
|
||||
model: alibaba[api]("qwen3.8-max"),
|
||||
providerOptions:
|
||||
api === "messages"
|
||||
? { effort: "medium" }
|
||||
: api === "chat"
|
||||
? { reasoningEffort: "medium", preserveThinking: true, toolStream: true }
|
||||
: { reasoningEffort: "medium", store: false },
|
||||
prompt:
|
||||
"We have a budget of 38000 dollars for 219 trips costing 173 dollars each. Calculate whether that is affordable. If it is, use get_weather to look up the current weather in Paris. After receiving the result, report the weather in one short sentence.",
|
||||
tools: [weather],
|
||||
generation: { maxTokens: 4096 },
|
||||
})
|
||||
const first = yield* LLMClient.generate(request)
|
||||
expect(first.toolCalls).toMatchObject([{ name: "get_weather", input: { city: "Paris" } }])
|
||||
expect(first.finishReason.normalized).toBe("tool-calls")
|
||||
expect(first.reasoning.length).toBeGreaterThan(0)
|
||||
expect(first.events.some(LLMEvent.is.toolInputDelta)).toBe(true)
|
||||
expectUsage(first)
|
||||
const continuation = LLMRequest.update(request, {
|
||||
messages: [
|
||||
...request.messages,
|
||||
first.message,
|
||||
...first.toolCalls.map((call) =>
|
||||
Message.tool({ id: call.id, name: call.name, result: { condition: "sunny", temperature: "18C" } }),
|
||||
),
|
||||
],
|
||||
})
|
||||
expectReasoning(api, (yield* compileRequest(continuation)).body, first)
|
||||
const second = yield* LLMClient.generate(continuation)
|
||||
expect(second.text.toLowerCase()).toContain("sunny")
|
||||
expect(second.toolCalls).toHaveLength(0)
|
||||
expect(second.finishReason.normalized).toBe("stop")
|
||||
expectUsage(second)
|
||||
const followUp = LLMRequest.update(continuation, {
|
||||
messages: [
|
||||
...continuation.messages,
|
||||
second.message,
|
||||
Message.user("What temperature did the tool report? Reply with only the temperature."),
|
||||
],
|
||||
})
|
||||
expectReasoning(api, (yield* compileRequest(followUp)).body, first)
|
||||
const third = yield* LLMClient.generate(followUp)
|
||||
expect(third.text).toContain("18")
|
||||
expect(third.toolCalls).toHaveLength(0)
|
||||
expect(third.finishReason.normalized).toBe("stop")
|
||||
expectUsage(third)
|
||||
}),
|
||||
180_000,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function expectUsage(response: LLMResponse) {
|
||||
expect(response.usage?.inputTokens).toBeGreaterThan(0)
|
||||
expect(response.usage?.outputTokens).toBeGreaterThan(0)
|
||||
expect(response.events.filter(LLMEvent.is.finish)).toHaveLength(1)
|
||||
}
|
||||
|
||||
function expectReasoning(
|
||||
api: "chat" | "messages" | "responses",
|
||||
body: Readonly<Record<string, unknown>>,
|
||||
response: LLMResponse,
|
||||
) {
|
||||
if (api === "chat") {
|
||||
expect(body.messages).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ role: "assistant", reasoning_content: response.reasoning })]),
|
||||
)
|
||||
return
|
||||
}
|
||||
for (const part of response.message.content.filter((part) => part.type === "reasoning")) {
|
||||
if (api === "messages") {
|
||||
expect(body.messages).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
role: "assistant",
|
||||
content: expect.arrayContaining([
|
||||
{ type: "thinking", thinking: part.text, signature: part.providerMetadata?.alibaba?.signature ?? "" },
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
)
|
||||
continue
|
||||
}
|
||||
expect(body.input).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "reasoning",
|
||||
id: part.providerMetadata?.alibaba?.itemId,
|
||||
summary: expect.arrayContaining([{ type: "summary_text", text: part.text }]),
|
||||
}),
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { ConfigProvider, Effect } from "effect"
|
||||
import { Headers } from "effect/unstable/http"
|
||||
import { Auth, LLM, LLMClient, LLMRequest, Message, ReasoningPart, ToolDefinition } from "../../src/index.js"
|
||||
import { Alibaba } from "../../src/providers.js"
|
||||
import { compileRequest } from "../../src/route/client.js"
|
||||
import { Endpoint } from "../../src/route/endpoint.js"
|
||||
import { it } from "../lib/effect.js"
|
||||
import { dynamicResponse } from "../lib/http.js"
|
||||
import { sseEvents } from "../lib/sse.js"
|
||||
|
||||
const tool = ToolDefinition.make({
|
||||
name: "lookup",
|
||||
description: "Look up a value",
|
||||
inputSchema: { type: "object", properties: { key: { type: "string" } } },
|
||||
})
|
||||
const paths = {
|
||||
chat: "/compatible-mode/v1/chat/completions",
|
||||
messages: "/apps/anthropic/v1/messages",
|
||||
responses: "/compatible-mode/v1/responses",
|
||||
}
|
||||
|
||||
it.effect("Alibaba owns regional shared and workspace-specific endpoints", () =>
|
||||
Effect.gen(function* () {
|
||||
for (const region of [
|
||||
"ap-southeast-1",
|
||||
"cn-beijing",
|
||||
"cn-hongkong",
|
||||
"us-east-1",
|
||||
"eu-central-1",
|
||||
"ap-northeast-1",
|
||||
]) {
|
||||
const provider = Alibaba.configure({ region, workspaceID: "llm-fixture", apiKey: "fixture" })
|
||||
expect(provider.model).toBe(provider.chat)
|
||||
for (const api of ["chat", "messages", "responses"] as const) {
|
||||
const model = provider[api]("qwen-plus-us")
|
||||
const request = LLM.request({ model, prompt: "Hello" })
|
||||
const compiled = yield* compileRequest(request)
|
||||
expect(Endpoint.render(model.route.endpoint, { request, body: compiled.body }).toString()).toBe(
|
||||
`https://llm-fixture.${region}.maas.aliyuncs.com${paths[api]}`,
|
||||
)
|
||||
expect(model.provider).toBe("alibaba")
|
||||
expect(model.route.id).toBe(`alibaba-${api}`)
|
||||
expect(compiled.body.model).toBe("qwen-plus-us")
|
||||
for (const field of [
|
||||
"thinking",
|
||||
"enable_thinking",
|
||||
"thinking_budget",
|
||||
"preserve_thinking",
|
||||
"reasoning_effort",
|
||||
"reasoning",
|
||||
"output_config",
|
||||
"store",
|
||||
"tool_stream",
|
||||
])
|
||||
expect(compiled.body[field]).toBeUndefined()
|
||||
}
|
||||
}
|
||||
for (const [region, host] of [
|
||||
["ap-southeast-1", "dashscope-intl.aliyuncs.com"],
|
||||
["cn-beijing", "dashscope.aliyuncs.com"],
|
||||
["cn-hongkong", "cn-hongkong.dashscope.aliyuncs.com"],
|
||||
["us-east-1", "dashscope-us.aliyuncs.com"],
|
||||
]) {
|
||||
const provider = Alibaba.configure({ region, apiKey: "fixture" })
|
||||
for (const api of ["chat", "messages", "responses"] as const) {
|
||||
const request = LLM.request({ model: provider[api]("qwen3.8-max") })
|
||||
const compiled = yield* compileRequest(request)
|
||||
expect(Endpoint.render(request.model.route.endpoint, { request, body: compiled.body }).toString()).toBe(
|
||||
`https://${host}${paths[api]}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
test("Alibaba requires explicit placement and supports complete base URL overrides", () => {
|
||||
for (const region of ["eu-central-1", "ap-northeast-1", "future-region"])
|
||||
expect(() => Alibaba.configure({ region })).toThrow("requires workspaceID or baseURL")
|
||||
for (const config of [
|
||||
{ baseURL: "https://gateway.example/prefix" },
|
||||
{ region: "future-region", workspaceID: "ignored", baseURL: "https://gateway.example/prefix" },
|
||||
]) {
|
||||
const provider = Alibaba.configure(config)
|
||||
for (const api of ["chat", "messages", "responses"] as const)
|
||||
expect(provider[api]("unchanged-id").route.endpoint.baseURL).toBe(config.baseURL)
|
||||
}
|
||||
expect(
|
||||
Alibaba.configure({ region: "future-region", workspaceID: "llm-fixture" }).chat("new-model").route.endpoint.baseURL,
|
||||
).toBe("https://llm-fixture.future-region.maas.aliyuncs.com/compatible-mode/v1")
|
||||
})
|
||||
|
||||
it.effect("Alibaba resolves explicit auth, API keys, and regional environment credentials", () =>
|
||||
Effect.gen(function* () {
|
||||
for (const item of [
|
||||
{
|
||||
config: {},
|
||||
env: { DASHSCOPE_API_KEY: "primary", ALIBABA_API_KEY: "fallback" },
|
||||
headers: { authorization: "Bearer primary" },
|
||||
},
|
||||
{ config: {}, env: { ALIBABA_API_KEY: "fallback" }, headers: { authorization: "Bearer fallback" } },
|
||||
{
|
||||
config: { apiKey: "explicit" },
|
||||
env: { DASHSCOPE_API_KEY: "primary" },
|
||||
headers: { authorization: "Bearer explicit" },
|
||||
},
|
||||
{
|
||||
config: { auth: Auth.header("x-custom-key", "custom") },
|
||||
env: { DASHSCOPE_API_KEY: "primary" },
|
||||
headers: { "x-custom-key": "custom" },
|
||||
},
|
||||
]) {
|
||||
const provider = Alibaba.configure({ region: "ap-southeast-1", ...item.config })
|
||||
for (const api of ["chat", "messages", "responses"] as const) {
|
||||
const request = LLM.request({ model: provider[api]("qwen3.8-max") })
|
||||
const headers = yield* request.model.route.auth
|
||||
.apply({ request, method: "POST", url: "https://fixture", body: "{}", headers: Headers.empty })
|
||||
.pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: item.env }))))
|
||||
expect(headers).toEqual(expect.objectContaining(item.headers))
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("Alibaba keeps native reasoning controls and future efforts on their selected API", () =>
|
||||
Effect.gen(function* () {
|
||||
const provider = Alibaba.configure({ region: "ap-southeast-1", apiKey: "fixture" })
|
||||
for (const effort of ["none", "minimal", "low", "medium", "high", "xhigh", "max", "future-effort"]) {
|
||||
const chat = yield* compileRequest(
|
||||
LLM.request({ model: provider.chat("qwen3.8-max"), providerOptions: { reasoningEffort: effort } }),
|
||||
)
|
||||
const messages = yield* compileRequest(
|
||||
LLM.request({ model: provider.messages("qwen3.8-max"), providerOptions: { effort } }),
|
||||
)
|
||||
const responses = yield* compileRequest(
|
||||
LLM.request({ model: provider.responses("qwen3.8-max"), providerOptions: { reasoningEffort: effort } }),
|
||||
)
|
||||
expect(chat.body.reasoning_effort).toBe(effort)
|
||||
expect(messages.body.output_config).toEqual({ effort })
|
||||
expect(responses.body.reasoning).toEqual({ effort })
|
||||
for (const result of [chat, messages, responses]) {
|
||||
expect(result.body.thinking).toBeUndefined()
|
||||
expect(result.body.enable_thinking).toBeUndefined()
|
||||
}
|
||||
}
|
||||
for (const enableThinking of [true, false]) {
|
||||
const chat = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: provider.chat("qwen3.7-plus"),
|
||||
tools: [tool],
|
||||
generation: { maxTokens: 1234, topK: 20 },
|
||||
providerOptions: {
|
||||
enableThinking,
|
||||
thinkingBudget: 512,
|
||||
preserveThinking: false,
|
||||
clearThinking: false,
|
||||
toolStream: false,
|
||||
parallelToolCalls: false,
|
||||
repetitionPenalty: 1.1,
|
||||
responseFormat: { type: "json_object" },
|
||||
enableSearch: true,
|
||||
searchOptions: { forced_search: true, search_strategy: "future-strategy", enable_search_extension: false },
|
||||
},
|
||||
}),
|
||||
)
|
||||
expect(chat.body).toMatchObject({
|
||||
enable_thinking: enableThinking,
|
||||
thinking_budget: 512,
|
||||
preserve_thinking: false,
|
||||
clear_thinking: false,
|
||||
tool_stream: false,
|
||||
parallel_tool_calls: false,
|
||||
repetition_penalty: 1.1,
|
||||
max_completion_tokens: 1234,
|
||||
top_k: 20,
|
||||
response_format: { type: "json_object" },
|
||||
enable_search: true,
|
||||
search_options: { forced_search: true, search_strategy: "future-strategy", enable_search_extension: false },
|
||||
})
|
||||
expect(chat.body.max_tokens).toBeUndefined()
|
||||
expect(chat.body.tools).toEqual([
|
||||
expect.objectContaining({ function: expect.not.objectContaining({ strict: expect.anything() }) }),
|
||||
])
|
||||
const responses = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: provider.responses("qwen3.7-plus"),
|
||||
providerOptions: { enableThinking, store: false, previousResponseId: "resp_previous" },
|
||||
}),
|
||||
)
|
||||
expect(responses.body).toMatchObject({
|
||||
enable_thinking: enableThinking,
|
||||
store: false,
|
||||
previous_response_id: "resp_previous",
|
||||
})
|
||||
}
|
||||
for (const thinking of [
|
||||
{ type: "enabled" },
|
||||
{ type: "disabled" },
|
||||
{ type: "enabled", budgetTokens: 512 },
|
||||
{ type: "future", budget_tokens: 4096 },
|
||||
]) {
|
||||
const messages = yield* compileRequest(
|
||||
LLM.request({ model: provider.messages("qwen3.7-plus"), providerOptions: { thinking } }),
|
||||
)
|
||||
expect(messages.body.thinking).toEqual({
|
||||
type: thinking.type,
|
||||
budget_tokens: thinking.budgetTokens ?? thinking.budget_tokens,
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("Alibaba validates malformed options before execution", () =>
|
||||
Effect.gen(function* () {
|
||||
const provider = Alibaba.configure({ region: "ap-southeast-1", apiKey: "fixture" })
|
||||
for (const [api, providerOptions] of [
|
||||
["chat", { preserveThinking: "false" }],
|
||||
["messages", { thinking: { type: "enabled", budgetTokens: "512" } }],
|
||||
["responses", { enableThinking: "false" }],
|
||||
] as const) {
|
||||
const model = provider[api]("qwen3.8-max").route.with({ providerOptions }).model({ id: "qwen3.8-max" })
|
||||
const error = yield* compileRequest(LLM.request({ model })).pipe(Effect.flip)
|
||||
expect(error.reason._tag).toBe("InvalidRequest")
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("Alibaba preserves unsigned and signed Messages thinking without a budget requirement", () =>
|
||||
Effect.gen(function* () {
|
||||
const result = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: Alibaba.configure({ region: "ap-southeast-1", apiKey: "fixture" }).messages("qwen3.8-max"),
|
||||
providerOptions: { thinking: { type: "enabled" }, effort: "low" },
|
||||
messages: [
|
||||
Message.user("Hello"),
|
||||
Message.assistant([
|
||||
ReasoningPart.make({ type: "reasoning", text: "unsigned" }),
|
||||
ReasoningPart.make({
|
||||
type: "reasoning",
|
||||
text: "signed",
|
||||
providerMetadata: { alibaba: { signature: "opaque" } },
|
||||
}),
|
||||
]),
|
||||
],
|
||||
}),
|
||||
)
|
||||
expect(result.body.thinking).toEqual({ type: "enabled" })
|
||||
expect(result.body.messages).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "thinking", thinking: "unsigned", signature: "" },
|
||||
{ type: "thinking", thinking: "signed", signature: "opaque" },
|
||||
],
|
||||
}),
|
||||
]),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("Alibaba serializes Messages configuration, per-request controls, and final HTTP overlays", () =>
|
||||
LLMClient.generate(
|
||||
LLM.request({
|
||||
model: Alibaba.configure({
|
||||
baseURL: "https://gateway.example/v1",
|
||||
apiKey: "fixture",
|
||||
providerOptions: { thinking: { type: "enabled", budgetTokens: 512 }, effort: "high" },
|
||||
}).messages("qwen3.8-max"),
|
||||
prompt: "Hello",
|
||||
providerOptions: { effort: "low" },
|
||||
http: { body: { output_config: { effort: "medium" }, extension: true } },
|
||||
}),
|
||||
).pipe(
|
||||
Effect.provide(
|
||||
dynamicResponse((input) =>
|
||||
Effect.sync(() => {
|
||||
expect(input.request.url).toBe("https://gateway.example/v1/messages")
|
||||
expect(input.request.headers.authorization).toBe("Bearer fixture")
|
||||
expect(input.request.headers["anthropic-version"]).toBe("2023-06-01")
|
||||
expect(JSON.parse(input.text)).toMatchObject({
|
||||
thinking: { type: "enabled", budget_tokens: 512 },
|
||||
output_config: { effort: "medium" },
|
||||
extension: true,
|
||||
})
|
||||
return input.respond(
|
||||
sseEvents(
|
||||
{
|
||||
type: "message_start",
|
||||
message: { id: "msg_fixture", content: [], usage: { input_tokens: 1, output_tokens: 0 } },
|
||||
},
|
||||
{ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 1 } },
|
||||
{ type: "message_stop" },
|
||||
),
|
||||
{ headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("Alibaba Responses lowers hosted tools and named selection using HTTP even with a WebSocket executor", () =>
|
||||
Effect.gen(function* () {
|
||||
const request = LLM.request({
|
||||
model: Alibaba.configure({ region: "ap-southeast-1", apiKey: "fixture" }).responses("qwen3.8-max"),
|
||||
tools: [Alibaba.webSearch(), Alibaba.webExtractor(), Alibaba.codeInterpreter(), tool],
|
||||
prompt: "Hello",
|
||||
})
|
||||
const compiled = yield* compileRequest(request)
|
||||
expect(compiled.body.tools).toMatchObject([
|
||||
{ type: "web_search" },
|
||||
{ type: "web_extractor" },
|
||||
{ type: "code_interpreter" },
|
||||
{ type: "function", name: "lookup" },
|
||||
])
|
||||
const named = yield* compileRequest(
|
||||
LLMRequest.update(request, { tools: [tool], toolChoice: { type: "tool", name: "lookup" } }),
|
||||
)
|
||||
expect(named.body.tool_choice).toEqual({
|
||||
type: "allowed_tools",
|
||||
mode: "required",
|
||||
tools: [{ type: "function", name: "lookup" }],
|
||||
})
|
||||
const response = yield* LLMClient.generate(request, {
|
||||
webSocket: { execute: () => Effect.die("Unexpected WebSocket") },
|
||||
})
|
||||
expect(response.toolCalls).toMatchObject([
|
||||
{
|
||||
name: "web_extractor",
|
||||
providerExecuted: true,
|
||||
input: { urls: ["https://example.com"], goal: "Read the page" },
|
||||
},
|
||||
])
|
||||
const replay = yield* compileRequest(
|
||||
LLMRequest.update(request, { messages: [...request.messages, response.message, Message.user("Continue")] }),
|
||||
)
|
||||
expect(replay.body.input).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "web_extractor_call",
|
||||
id: "extract_1",
|
||||
urls: ["https://example.com"],
|
||||
goal: "Read the page",
|
||||
result: { text: "fixture" },
|
||||
}),
|
||||
]),
|
||||
)
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
dynamicResponse((input) =>
|
||||
Effect.sync(() => {
|
||||
expect(input.request.url).toBe("https://dashscope-intl.aliyuncs.com/compatible-mode/v1/responses")
|
||||
return input.respond(
|
||||
sseEvents(
|
||||
{
|
||||
type: "response.output_item.done",
|
||||
output_index: 0,
|
||||
item: {
|
||||
type: "web_extractor_call",
|
||||
id: "extract_1",
|
||||
status: "completed",
|
||||
urls: ["https://example.com"],
|
||||
goal: "Read the page",
|
||||
result: { text: "fixture" },
|
||||
},
|
||||
},
|
||||
{ type: "response.completed", response: {} },
|
||||
),
|
||||
{ headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -1,67 +0,0 @@
|
||||
import { expect, story } from "../../storybook/playwright/story"
|
||||
|
||||
story("settings menu reconnect retains its prompt handler across server updates", async ({ mount, page }) => {
|
||||
const component = await mount("app-dialog-ssh--settings-reconnect")
|
||||
await component.getByRole("button", { name: "More options" }).click()
|
||||
await page.getByRole("menuitem", { name: "Connect", exact: true }).click()
|
||||
const dialog = page.getByRole("dialog")
|
||||
await expect(dialog.getByRole("textbox")).toBeVisible()
|
||||
await dialog.getByRole("textbox").fill("fixture-password")
|
||||
await dialog.getByRole("button", { name: "Continue" }).click()
|
||||
await expect(dialog.getByRole("textbox", { name: "Verification code:" })).toBeVisible()
|
||||
await dialog.getByRole("textbox").fill("123456")
|
||||
await dialog.getByRole("button", { name: "Continue" }).click()
|
||||
await expect(dialog).toHaveCount(0)
|
||||
await expect(component.getByRole("button", { name: "Authenticate", exact: true })).toHaveCount(0)
|
||||
})
|
||||
|
||||
story("cancelling a version mismatch permits reconnecting again", async ({ mount, page }) => {
|
||||
const component = await mount("app-dialog-ssh--incompatible-session")
|
||||
await component.getByRole("button", { name: "Reconnect", exact: true }).click()
|
||||
const dialog = page.getByRole("dialog")
|
||||
await expect(dialog.getByRole("alert")).toBeVisible()
|
||||
await dialog.getByRole("button", { name: "Cancel", exact: true }).click()
|
||||
await expect(dialog).toHaveCount(0)
|
||||
await component.getByRole("button", { name: "Reconnect", exact: true }).click()
|
||||
await expect(dialog.getByRole("alert")).toBeVisible()
|
||||
await dialog.getByRole("button", { name: "Cancel", exact: true }).click()
|
||||
await expect(dialog).toHaveCount(0)
|
||||
})
|
||||
|
||||
story("adding a server keeps all SSH challenges in the original connection dialog", async ({ mount, page }) => {
|
||||
await mount("app-dialog-ssh--host")
|
||||
const dialog = page.getByRole("dialog")
|
||||
await dialog.getByRole("textbox", { name: "Host or SSH command" }).fill("ssh devbox")
|
||||
await dialog.getByRole("button", { name: "Add server", exact: true }).click()
|
||||
await expect(dialog).toHaveCount(1)
|
||||
await expect(dialog.getByRole("button", { name: "Cancel", exact: true })).toBeFocused()
|
||||
await dialog.getByRole("button", { name: "Trust and connect" }).click()
|
||||
await dialog.getByRole("textbox").fill("fixture-password")
|
||||
await dialog.getByRole("button", { name: "Continue", exact: true }).click()
|
||||
await dialog.getByRole("textbox", { name: "Verification code:" }).fill("123456")
|
||||
await dialog.getByRole("button", { name: "Continue", exact: true }).click()
|
||||
await expect(dialog).toHaveCount(0)
|
||||
})
|
||||
|
||||
story("updating an incompatible connection continues authentication in the same dialog", async ({ mount, page }) => {
|
||||
const component = await mount("app-dialog-ssh--incompatible-session")
|
||||
await component.getByRole("button", { name: "Reconnect", exact: true }).click()
|
||||
const dialog = page.getByRole("dialog")
|
||||
await dialog.getByRole("button", { name: "Update and reconnect", exact: true }).click()
|
||||
await expect(dialog).toHaveCount(1)
|
||||
await dialog.getByRole("textbox").fill("fixture-password")
|
||||
await dialog.getByRole("button", { name: "Continue", exact: true }).click()
|
||||
await dialog.getByRole("textbox", { name: "Verification code:" }).fill("123456")
|
||||
await dialog.getByRole("button", { name: "Continue", exact: true }).click()
|
||||
await expect(dialog).toHaveCount(0)
|
||||
await expect(component.getByText("Session connected")).toBeVisible()
|
||||
})
|
||||
|
||||
story("key-based reconnect completes without opening an authentication dialog", async ({ mount, page }) => {
|
||||
const component = await mount("app-dialog-ssh--key-reconnect")
|
||||
await component.getByRole("button", { name: "Reconnect", exact: true }).click()
|
||||
await expect(component.getByRole("button", { name: "Connecting to SSH server" })).toBeDisabled()
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0)
|
||||
await expect(component.getByText("Session connected")).toBeVisible()
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0)
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
import { readFile } from "node:fs/promises"
|
||||
import { fixture, pageMessages } from "../performance/timeline/session-timeline-stress.fixture"
|
||||
import { installStressSessionTabs, stressSessionHref } from "../performance/timeline/timeline-test-helpers"
|
||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||
|
||||
test("context opens independently, exports its session, and restores the closed panel", async ({ page }, info) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 })
|
||||
await mockOpenCodeServer(page, {
|
||||
directory: fixture.directory,
|
||||
project: fixture.project,
|
||||
sessions: fixture.sessions,
|
||||
provider: fixture.provider,
|
||||
pageMessages,
|
||||
})
|
||||
await installStressSessionTabs(page)
|
||||
await page.goto(stressSessionHref(fixture.sourceID))
|
||||
await expect(page.getByRole("heading", { name: fixture.expected.sourceTitle, exact: true })).toBeVisible()
|
||||
const toggle = page.getByRole("button", { name: "View context usage", exact: true })
|
||||
await toggle.click()
|
||||
const context = page.getByRole("tabpanel", { name: "Context", exact: true })
|
||||
await expect(context.getByText("Total Tokens", { exact: true })).toBeVisible()
|
||||
await expect(context.getByText(fixture.expected.sourceTitle, { exact: true })).toBeVisible()
|
||||
await expect(context.getByText("Raw messages", { exact: true })).toBeVisible()
|
||||
await page.screenshot({ path: info.outputPath("context-extension.png") })
|
||||
const pending = page.waitForEvent("download")
|
||||
await context.getByRole("button", { name: "Export session", exact: true }).click()
|
||||
const download = await pending
|
||||
const exported: { info: { id: string }; messages: { id: string }[] } = JSON.parse(
|
||||
await readFile((await download.path())!, "utf8"),
|
||||
)
|
||||
expect(exported.info.id).toBe(fixture.sourceID)
|
||||
expect(exported.messages.length).toBeGreaterThan(0)
|
||||
expect(new Set(exported.messages.map((message) => message.id)).size).toBe(exported.messages.length)
|
||||
await toggle.click()
|
||||
await expect(page.getByRole("tab", { name: "Context", exact: true })).toHaveCount(0)
|
||||
await expect(page.getByRole("button", { name: "Toggle review", exact: true })).toHaveAttribute(
|
||||
"aria-expanded",
|
||||
"false",
|
||||
)
|
||||
await toggle.click()
|
||||
await expect(context.getByText(fixture.expected.sourceTitle, { exact: true })).toBeVisible()
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
<!doctype html><html><head><meta charset="UTF-8" /><style>html,body,#root{height:100%;margin:0}#root{display:flex;flex-direction:column}</style></head><body><div id="root"></div><script type="module" src="./fixture.tsx"></script></body></html>
|
||||
@@ -0,0 +1,81 @@
|
||||
import { AppBaseProviders, AppInterface } from "../../src/app"
|
||||
import { PlatformProvider } from "../../src/runtime/platform/platform"
|
||||
import { createWebPlatform } from "../../src/runtime/platform/web"
|
||||
import { ServerConnection } from "../../src/runtime/server/registry"
|
||||
import { Plugin } from "@opencode/plugin/desktop"
|
||||
import { Panel } from "@opencode/plugin/desktop/solid"
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { TextInput } from "@opencode/ui/text-input"
|
||||
import { Stack, Text } from "@opencode/ui/layout"
|
||||
import { createMemoryHistory, MemoryRouter } from "@solidjs/router"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { render } from "solid-js/web"
|
||||
import { base64Encode } from "@opencode/util/encode"
|
||||
import { fixture } from "../performance/timeline/session-timeline-stress.fixture"
|
||||
|
||||
const plugin = Plugin.define({
|
||||
id: "test.panels",
|
||||
setup(ctx) {
|
||||
const [state, setState] = createStore({ available: true, closed: 0 })
|
||||
const [draft, saveDraft] = ctx.storage.memory("draft", { initial: { text: "" } })
|
||||
ctx.ui.slot({
|
||||
append: "titlebar.actions",
|
||||
render: () => (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const session = ctx.sessions.current()
|
||||
if (session) ctx.ui.panel.open("notes", session)
|
||||
}}
|
||||
>
|
||||
Open notes
|
||||
</Button>
|
||||
<Button onClick={() => setState("available", !state.available)}>Toggle contribution</Button>
|
||||
</>
|
||||
),
|
||||
})
|
||||
ctx.ui.slot({
|
||||
append: "session.panel",
|
||||
when: () => state.available,
|
||||
render: () => (
|
||||
<>
|
||||
<Panel id="notes" title="Notes" onClose={() => setState("closed", (count) => count + 1)}>
|
||||
<Stack padding="medium">
|
||||
<TextInput
|
||||
aria-label="Notes draft"
|
||||
value={draft.text}
|
||||
onInput={(event) =>
|
||||
saveDraft((draft) => {
|
||||
draft.text = event.currentTarget.value
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
</Panel>
|
||||
<Panel id="results" title="Results">
|
||||
<Stack padding="medium">
|
||||
<Text>Closed notes: {state.closed}</Text>
|
||||
</Stack>
|
||||
</Panel>
|
||||
</>
|
||||
),
|
||||
})
|
||||
},
|
||||
})
|
||||
const web = createWebPlatform("test")
|
||||
const history = createMemoryHistory()
|
||||
history.set({ value: `/server/${base64Encode(web.currentServerUrl!)}/session/${fixture.sourceID}` })
|
||||
render(
|
||||
() => (
|
||||
<PlatformProvider value={{ ...web.platform, extensionPlugins: [plugin] }}>
|
||||
<AppBaseProviders>
|
||||
<AppInterface
|
||||
servers={[{ type: "http", http: { url: web.currentServerUrl! } }]}
|
||||
defaultServer={ServerConnection.Key.make(web.currentServerUrl!)}
|
||||
router={(props) => <MemoryRouter {...props} history={history} />}
|
||||
/>
|
||||
</AppBaseProviders>
|
||||
</PlatformProvider>
|
||||
),
|
||||
document.getElementById("root")!,
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
<!doctype html><html><head><meta charset="UTF-8" /><style>html,body,#root{height:100%;margin:0}#root{display:flex;flex-direction:column}</style></head><body><div id="root"></div><script type="module" src="./manager-fixture.tsx"></script></body></html>
|
||||
@@ -0,0 +1,53 @@
|
||||
import { AppBaseProviders, AppInterface } from "../../src/app"
|
||||
import { PlatformProvider } from "../../src/runtime/platform/platform"
|
||||
import { createWebPlatform } from "../../src/runtime/platform/web"
|
||||
import { ServerConnection } from "../../src/runtime/server/registry"
|
||||
import { createMemoryHistory, MemoryRouter } from "@solidjs/router"
|
||||
import { render } from "solid-js/web"
|
||||
import { Schema } from "effect"
|
||||
import { ExtensionManager } from "@opencode/plugin/desktop/manager"
|
||||
|
||||
const web = createWebPlatform("test")
|
||||
const history = createMemoryHistory()
|
||||
history.set({ value: "/settings" })
|
||||
const inventory = Schema.Array(ExtensionManager.Installed)
|
||||
async function request(name: string, body?: BodyInit) {
|
||||
const response = await fetch(`/__desktop-extensions/${name}`, { method: body ? "POST" : "GET", body })
|
||||
const value: unknown = await response.json()
|
||||
if (!response.ok)
|
||||
throw new ExtensionManager.ManagerError(Schema.decodeUnknownSync(ExtensionManager.Failure)(value).code)
|
||||
return value
|
||||
}
|
||||
const manager: ExtensionManager.Transport = {
|
||||
list: async () => Schema.decodeUnknownSync(inventory)(await request("list")),
|
||||
install: async (data) =>
|
||||
Schema.decodeUnknownSync(inventory)(await request("install", data as Uint8Array<ArrayBuffer>)),
|
||||
installURL: async (url) => Schema.decodeUnknownSync(inventory)(await request("url", JSON.stringify({ url }))),
|
||||
enable: async (id, enabled) =>
|
||||
Schema.decodeUnknownSync(inventory)(await request("enable", JSON.stringify({ id, enabled }))),
|
||||
reload: async (id) => Schema.decodeUnknownSync(inventory)(await request("reload", JSON.stringify({ id }))),
|
||||
source: async (id, revision) =>
|
||||
Schema.decodeUnknownSync(ExtensionManager.Source)(await request("source", JSON.stringify({ id, revision }))),
|
||||
assetURL: (id, revision, path) => `/__desktop-extensions/assets/${id}/${revision}/${path}`,
|
||||
onChange(callback) {
|
||||
const listener = (event: Event) => {
|
||||
if (event instanceof CustomEvent) callback(Schema.decodeUnknownSync(inventory)(event.detail))
|
||||
}
|
||||
window.addEventListener("test-extensions-changed", listener)
|
||||
return () => window.removeEventListener("test-extensions-changed", listener)
|
||||
},
|
||||
}
|
||||
render(
|
||||
() => (
|
||||
<PlatformProvider value={{ ...web.platform, extensionManager: manager }}>
|
||||
<AppBaseProviders>
|
||||
<AppInterface
|
||||
servers={[{ type: "http", http: { url: web.currentServerUrl! } }]}
|
||||
defaultServer={ServerConnection.Key.make(web.currentServerUrl!)}
|
||||
router={(props) => <MemoryRouter {...props} history={history} />}
|
||||
/>
|
||||
</AppBaseProviders>
|
||||
</PlatformProvider>
|
||||
),
|
||||
document.getElementById("root")!,
|
||||
)
|
||||
@@ -0,0 +1,264 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
import { Schema } from "effect"
|
||||
import { createServer } from "node:http"
|
||||
import { once } from "node:events"
|
||||
import { openDatabase } from "../../../desktop/src/main/storage/database"
|
||||
import { createExtensionManager, readExtensionAsset } from "../../../desktop/src/main/extensions/manager"
|
||||
import { ExtensionManager } from "@opencode/plugin/desktop/manager"
|
||||
import { extensionArchive } from "../../../desktop/test/extensions/fixture"
|
||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||
import { fixture } from "../performance/timeline/session-timeline-stress.fixture"
|
||||
import { installStressSessionTabs } from "../performance/timeline/timeline-test-helpers"
|
||||
|
||||
const renderer = (version: string) => `
|
||||
const { usePlugin } = require('@opencode/plugin/desktop');
|
||||
const { createEffect, createComponent } = require('solid-js');
|
||||
const { Panel } = require('@opencode/plugin/desktop/solid');
|
||||
const { Schema } = require('effect');
|
||||
module.exports.default = { id: 'test.lifecycle', setup(ctx) {
|
||||
const [documentState, setDocument, ready] = ctx.storage.persist('document', Schema.Struct({ text: Schema.String }), { text: 'empty' }, { legacyKey: 'extension-fixture-legacy' });
|
||||
const [state, update] = ctx.storage.memory('counts', { initial: { starts: 0, stops: 0 } });
|
||||
update(state => state.starts++);
|
||||
ctx.lifecycle.own(() => update(state => state.stops++));
|
||||
const marker = (id) => {
|
||||
const output = document.createElement('output'); output.hidden = true; output.dataset.testid = id;
|
||||
createEffect(() => output.textContent = ${JSON.stringify(version)} + ':' + state.starts + ':' + state.stops);
|
||||
return output;
|
||||
};
|
||||
ctx.commands.register(() => [
|
||||
{ id: 'show', title: 'Show extension fixture', bind: 'mod+shift+y', run() { const session = ctx.sessions.current(); if (session) ctx.ui.panel.open('state', session) } },
|
||||
{ id: 'save', title: 'Save extension fixture', bind: 'mod+shift+x', run() { setDocument('text', 'saved') } },
|
||||
]);
|
||||
ctx.ui.slot({ append: 'session.panel', render: () => createComponent(Panel, { id: 'state', title: 'File utilities', group: 'state', get children() { return marker('extension-panel-lifecycle') } }) });
|
||||
ctx.ui.slot({ append: 'app', render() {
|
||||
if (usePlugin().lifecycle.signal !== ctx.lifecycle.signal) throw new Error('Shared plugin context identity was lost');
|
||||
const stored = document.createElement('output'); stored.hidden = true; stored.dataset.testid = 'extension-persisted';
|
||||
createEffect(() => stored.textContent = ready() ? documentState.text : 'loading');
|
||||
return [marker('extension-lifecycle'), stored];
|
||||
} });
|
||||
} };`
|
||||
const payload = Schema.Struct({
|
||||
id: Schema.optionalKey(Schema.String),
|
||||
revision: Schema.optionalKey(Schema.String),
|
||||
enabled: Schema.optionalKey(Schema.Boolean),
|
||||
url: Schema.optionalKey(Schema.String),
|
||||
})
|
||||
|
||||
for (const direction of ["ltr", "rtl"] as const)
|
||||
test(`manager installs, hot-replaces, reloads, and disables across windows in ${direction}`, async ({
|
||||
page,
|
||||
context,
|
||||
}, info) => {
|
||||
const database = openDatabase(":memory:")
|
||||
const manager = createExtensionManager({
|
||||
db: database.db,
|
||||
fetch,
|
||||
changed(_id, entries) {
|
||||
context.pages().forEach((page) => {
|
||||
void page.evaluate(
|
||||
(entries) => window.dispatchEvent(new CustomEvent("test-extensions-changed", { detail: entries })),
|
||||
entries,
|
||||
)
|
||||
})
|
||||
},
|
||||
})
|
||||
const archive = async (version: string, code = renderer(version)) =>
|
||||
extensionArchive({
|
||||
version,
|
||||
renderer: code,
|
||||
files: { "assets/style.css": ":root { --installed-extension-marker: 1; }" },
|
||||
manifest: {
|
||||
imports: ["@opencode/plugin/desktop", "@opencode/plugin/desktop/solid", "solid-js", "effect"],
|
||||
style: "assets/style.css",
|
||||
},
|
||||
})
|
||||
const first = await archive("1.0.0")
|
||||
const http = createServer((_request, response) => {
|
||||
response.setHeader("Content-Type", "application/vnd.ocdx")
|
||||
response.end(first)
|
||||
})
|
||||
http.listen(0, "127.0.0.1")
|
||||
await once(http, "listening")
|
||||
const address = http.address()
|
||||
if (!address || typeof address === "string") throw new Error("Fixture address is unavailable")
|
||||
await context.route("**/__desktop-extensions/**", async (route) => {
|
||||
const path = new URL(route.request().url()).pathname.split("/").slice(2)
|
||||
try {
|
||||
if (path[0] === "assets") {
|
||||
const bytes = readExtensionAsset(database.db, path[1], path[2], path.slice(3).join("/"))
|
||||
await route.fulfill({ status: bytes ? 200 : 404, body: bytes, contentType: "text/css" })
|
||||
return
|
||||
}
|
||||
const data = Schema.decodeUnknownSync(payload)(
|
||||
JSON.parse(path[0] === "install" ? "{}" : (route.request().postData() ?? "{}")),
|
||||
)
|
||||
const result =
|
||||
path[0] === "install"
|
||||
? await manager.install(route.request().postDataBuffer()!)
|
||||
: path[0] === "url"
|
||||
? await manager.installURL(data.url ?? "")
|
||||
: path[0] === "enable"
|
||||
? manager.enable(data.id ?? "", data.enabled ?? false)
|
||||
: path[0] === "reload"
|
||||
? manager.reload(data.id ?? "")
|
||||
: path[0] === "source"
|
||||
? manager.source(data.id ?? "", data.revision)
|
||||
: manager.list()
|
||||
await route.fulfill({ json: result })
|
||||
} catch (error) {
|
||||
await route.fulfill({
|
||||
status: 400,
|
||||
json: { code: error instanceof ExtensionManager.ManagerError ? error.code : "storage" },
|
||||
})
|
||||
}
|
||||
})
|
||||
const open = async (target: typeof page) => {
|
||||
await mockOpenCodeServer(target, {
|
||||
sessions: fixture.sessions,
|
||||
provider: fixture.provider,
|
||||
directory: fixture.directory,
|
||||
project: fixture.project,
|
||||
pageMessages: () => ({ items: [] }),
|
||||
})
|
||||
await installStressSessionTabs(target)
|
||||
await target.addInitScript(() =>
|
||||
localStorage.setItem("opencode.global.dat:extension-fixture-legacy", JSON.stringify({ text: "legacy" })),
|
||||
)
|
||||
await target.goto("/e2e/extensions/manager-fixture.html")
|
||||
await target.getByTestId("settings-screen").getByRole("tab", { name: "Extensions", exact: true }).click()
|
||||
await expect(target.getByRole("heading", { name: "Install extensions", exact: true })).toBeVisible()
|
||||
const settings = target.getByTestId("settings-screen")
|
||||
const navigation = settings.locator(".settings-nav")
|
||||
await expect(
|
||||
navigation
|
||||
.locator('[data-slot="settings-nav-group"]')
|
||||
.filter({ has: target.getByRole("tab", { name: "Extensions", exact: true }) })
|
||||
.getByRole("tab"),
|
||||
).toHaveText(["Extensions", "Experimental"])
|
||||
await expect(
|
||||
navigation
|
||||
.locator('[data-slot="settings-nav-group"]')
|
||||
.filter({ has: target.getByRole("tab", { name: "Tools", exact: true }) })
|
||||
.getByRole("tab"),
|
||||
).toHaveText(["Providers", "Models", "Tools"])
|
||||
await expect(settings.getByRole("tab", { name: "Desktop", exact: true })).toHaveCount(0)
|
||||
await expect(settings.getByRole("tab", { name: "MCPs", exact: true })).toHaveCount(0)
|
||||
await settings.getByRole("tab", { name: "Tools", exact: true }).click()
|
||||
await expect(settings.getByRole("heading", { name: "Tools", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("tab", { name: "MCPs", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("tab", { name: "Plugins", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("tab", { name: "Skills", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("heading", { name: "Install extensions", exact: true })).toHaveCount(0)
|
||||
await settings.getByRole("tab", { name: "Extensions", exact: true }).click()
|
||||
await target.evaluate((direction) => {
|
||||
document.documentElement.dir = direction
|
||||
}, direction)
|
||||
}
|
||||
try {
|
||||
await open(page)
|
||||
await page.setViewportSize({ width: 640, height: 900 })
|
||||
await page.getByRole("button", { name: "Extensions", exact: true }).click()
|
||||
await page.getByRole("menuitemradio", { name: "Tools", exact: true }).click()
|
||||
await expect(page.getByRole("heading", { name: "Tools", exact: true })).toBeVisible()
|
||||
await page.getByRole("button", { name: "Tools", exact: true }).click()
|
||||
await page.getByRole("menuitemradio", { name: "Extensions", exact: true }).click()
|
||||
await expect(page.getByRole("heading", { name: "Install extensions", exact: true })).toBeVisible()
|
||||
await page.setViewportSize({ width: 1280, height: 720 })
|
||||
await expect(page.locator('[data-component="desktop-extension-manager"]').getByRole("status")).toHaveCount(0)
|
||||
await page.screenshot({ path: info.outputPath("extensions-manager-empty.png") })
|
||||
await page
|
||||
.getByRole("textbox", { name: "Extension URL", exact: true })
|
||||
.fill(`http://127.0.0.1:${address.port}/extension.ocdx`)
|
||||
await page.getByRole("textbox", { name: "Extension URL", exact: true }).press("Enter")
|
||||
const enabled = page.getByRole("switch", { name: "Enable File utilities", exact: true })
|
||||
await expect(enabled).toBeChecked()
|
||||
await expect(page.getByTestId("extension-lifecycle")).toHaveText("1.0.0:1:0")
|
||||
await expect(page.getByTestId("extension-persisted")).toHaveText("legacy")
|
||||
await page.keyboard.press("Control+Shift+x")
|
||||
await expect(page.getByTestId("extension-persisted")).toHaveText("saved")
|
||||
await page.screenshot({ path: info.outputPath("extensions-manager.png") })
|
||||
await page.getByRole("button", { name: "Reload File utilities", exact: true }).click()
|
||||
await expect(page.getByTestId("extension-lifecycle")).toHaveText("1.0.0:2:1")
|
||||
await expect(page.getByTestId("extension-persisted")).toHaveText("saved")
|
||||
await expect(page.locator('link[href*="/__desktop-extensions/assets/"]')).toHaveCount(1)
|
||||
const second = await context.newPage()
|
||||
await open(second)
|
||||
await expect(second.getByTestId("extension-lifecycle")).toHaveText("1.0.0:1:0")
|
||||
await expect(second.getByTestId("extension-persisted")).toHaveText("saved")
|
||||
await second.locator('header a[href$="/ses_smoke_source"]').click()
|
||||
await expect(second.getByRole("heading", { name: fixture.expected.sourceTitle, exact: true })).toBeVisible()
|
||||
await expect(second.getByRole("button", { name: "Toggle review", exact: true })).toBeEnabled()
|
||||
await second.keyboard.press("Control+Shift+y")
|
||||
await expect(second.getByTestId("extension-panel-lifecycle")).toHaveText("1.0.0:1:0")
|
||||
await page.getByLabel("Extension files", { exact: true }).setInputFiles({
|
||||
name: "update.ocdx",
|
||||
mimeType: "application/vnd.ocdx",
|
||||
buffer: Buffer.from(await archive("2.0.0")),
|
||||
})
|
||||
await expect(page.getByTestId("extension-lifecycle")).toHaveText("2.0.0:3:2")
|
||||
await expect(page.getByTestId("extension-persisted")).toHaveText("saved")
|
||||
await expect(second.getByTestId("extension-lifecycle")).toHaveText("2.0.0:2:1")
|
||||
await expect(second.getByTestId("extension-panel-lifecycle")).toHaveText("2.0.0:2:1")
|
||||
await page.getByLabel("Extension files", { exact: true }).setInputFiles({
|
||||
name: "broken.ocdx",
|
||||
mimeType: "application/vnd.ocdx",
|
||||
buffer: Buffer.from(
|
||||
await archive(
|
||||
"3.0.0",
|
||||
`module.exports.default = { id: 'test.lifecycle', setup(ctx) { ctx.ui.slot({ append: 'app', render() { const node = document.createElement('output'); node.hidden = true; node.dataset.testid = 'partial-load'; return node } }); throw new Error('broken update') } }`,
|
||||
),
|
||||
),
|
||||
})
|
||||
await expect(page.getByRole("alert")).toHaveText("Unable to activate File utilities.")
|
||||
await expect(page.getByTestId("extension-lifecycle")).toHaveText("2.0.0:3:2")
|
||||
await expect(page.getByTestId("partial-load")).toHaveCount(0)
|
||||
await expect(page.locator('link[href*="/__desktop-extensions/assets/"]')).toHaveCount(1)
|
||||
await page.getByLabel("Extension files", { exact: true }).setInputFiles({
|
||||
name: "fixed.ocdx",
|
||||
mimeType: "application/vnd.ocdx",
|
||||
buffer: Buffer.from(await archive("4.0.0")),
|
||||
})
|
||||
await expect(page.getByTestId("extension-lifecycle")).toHaveText("4.0.0:4:3")
|
||||
await second.keyboard.press("Control+,")
|
||||
await second.getByTestId("settings-screen").getByRole("tab", { name: "Extensions", exact: true }).click()
|
||||
await page
|
||||
.locator('[data-component="settings-row"]')
|
||||
.filter({ has: enabled })
|
||||
.locator('[data-slot="switch-control"]')
|
||||
.click()
|
||||
await expect(second.getByRole("switch", { name: "Enable File utilities", exact: true })).not.toBeChecked()
|
||||
await expect(page.getByTestId("extension-lifecycle")).toHaveCount(0)
|
||||
await expect(page.getByTestId("extension-persisted")).toHaveCount(0)
|
||||
await expect(second.getByTestId("extension-lifecycle")).toHaveCount(0)
|
||||
await expect(page.locator('link[href*="/__desktop-extensions/assets/"]')).toHaveCount(0)
|
||||
await enabled.press("Space")
|
||||
await expect(page.getByTestId("extension-lifecycle")).toHaveText("4.0.0:5:4")
|
||||
await expect(page.getByTestId("extension-persisted")).toHaveText("saved")
|
||||
await expect(second.getByTestId("extension-lifecycle")).toHaveText("4.0.0:4:3")
|
||||
const archives = await Promise.all(
|
||||
["Text helpers", "Path helpers"].map(async (name, index) => ({
|
||||
name: `helper-${index}.ocdx`,
|
||||
bytes: Buffer.from(await extensionArchive({ id: `test.helper-${index}`, manifest: { name } })).toString(
|
||||
"base64",
|
||||
),
|
||||
})),
|
||||
)
|
||||
const drop = await page.evaluateHandle((archives) => {
|
||||
const transfer = new DataTransfer()
|
||||
archives.forEach((archive) =>
|
||||
transfer.items.add(
|
||||
new File([Uint8Array.from(atob(archive.bytes), (char) => char.charCodeAt(0))], archive.name),
|
||||
),
|
||||
)
|
||||
return transfer
|
||||
}, archives)
|
||||
await page.locator(".desktop-extension-drop").dispatchEvent("drop", { dataTransfer: drop })
|
||||
await expect(page.getByRole("switch", { name: "Enable Text helpers", exact: true })).toBeChecked()
|
||||
await expect(second.getByRole("switch", { name: "Enable Path helpers", exact: true })).toBeChecked()
|
||||
await drop.dispose()
|
||||
await second.close()
|
||||
} finally {
|
||||
http.close()
|
||||
database.close()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||
import { fixture } from "../performance/timeline/session-timeline-stress.fixture"
|
||||
import { installStressSessionTabs } from "../performance/timeline/timeline-test-helpers"
|
||||
|
||||
test("independent panel instances retain drafts, close explicitly, and obey plugin availability", async ({
|
||||
page,
|
||||
}, info) => {
|
||||
await mockOpenCodeServer(page, {
|
||||
sessions: fixture.sessions,
|
||||
provider: fixture.provider,
|
||||
directory: fixture.directory,
|
||||
project: fixture.project,
|
||||
pageMessages: () => ({ items: [] }),
|
||||
})
|
||||
await installStressSessionTabs(page)
|
||||
await page.goto("/e2e/extensions/fixture.html")
|
||||
await expect(page.getByRole("heading", { name: fixture.expected.sourceTitle, exact: true })).toBeVisible()
|
||||
await page.getByRole("button", { name: "Open notes", exact: true }).click()
|
||||
await page.getByRole("textbox", { name: "Notes draft", exact: true }).fill("Retained draft")
|
||||
await page.getByRole("tab", { name: "Results", exact: true }).click()
|
||||
await expect(page.getByText("Closed notes: 0", { exact: true })).toBeVisible()
|
||||
await page.getByRole("tab", { name: "Notes", exact: true }).click()
|
||||
await expect(page.getByRole("textbox", { name: "Notes draft", exact: true })).toHaveValue("Retained draft")
|
||||
await page
|
||||
.locator('[data-slot="tabs-trigger-wrapper"][data-value="extension:test.panels:notes"]')
|
||||
.getByRole("button", { name: "Close tab", exact: true })
|
||||
.click()
|
||||
await page.getByRole("tab", { name: "Results", exact: true }).click()
|
||||
await expect(page.getByText("Closed notes: 1", { exact: true })).toBeVisible()
|
||||
await page.getByRole("button", { name: "Open notes", exact: true }).click()
|
||||
await expect(page.getByRole("textbox", { name: "Notes draft", exact: true })).toHaveValue("Retained draft")
|
||||
await page.screenshot({ path: info.outputPath("extension-panels.png") })
|
||||
await page
|
||||
.getByRole("tab", { name: "Results", exact: true })
|
||||
.dragTo(page.getByRole("tab", { name: "Notes", exact: true }))
|
||||
await expect(page.getByRole("tab", { name: /^(Notes|Results)$/ })).toHaveText(["Results", "Notes"])
|
||||
await page.getByRole("button", { name: "Home", exact: true }).click()
|
||||
await page.locator('header a[href$="/ses_smoke_source"]').click()
|
||||
await page.getByRole("button", { name: "Open notes", exact: true }).click()
|
||||
await expect(page.getByRole("tab", { name: /^(Notes|Results)$/ })).toHaveText(["Results", "Notes"])
|
||||
await expect(page.getByRole("textbox", { name: "Notes draft", exact: true })).toHaveValue("Retained draft")
|
||||
await page.getByRole("button", { name: "Toggle contribution", exact: true }).click()
|
||||
await expect(page.getByRole("tab", { name: "Notes", exact: true })).toHaveCount(0)
|
||||
await page.getByRole("button", { name: "Toggle contribution", exact: true }).click()
|
||||
await page.getByRole("button", { name: "Open notes", exact: true }).click()
|
||||
await expect(page.getByRole("textbox", { name: "Notes draft", exact: true })).toHaveValue("Retained draft")
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||
import { fixture } from "../performance/timeline/session-timeline-stress.fixture"
|
||||
import { installStressSessionTabs } from "../performance/timeline/timeline-test-helpers"
|
||||
|
||||
test.use({ viewport: { width: 1280, height: 720 }, serviceWorkers: "block" })
|
||||
test("server tools remain available without a native extension manager", async ({ page }, info) => {
|
||||
await mockOpenCodeServer(page, {
|
||||
sessions: fixture.sessions,
|
||||
provider: fixture.provider,
|
||||
directory: fixture.directory,
|
||||
project: fixture.project,
|
||||
pageMessages: () => ({ items: [] }),
|
||||
})
|
||||
await installStressSessionTabs(page)
|
||||
await page.goto("/")
|
||||
await page.getByRole("button", { name: "Settings", exact: true }).click()
|
||||
const settings = page.getByTestId("settings-screen")
|
||||
await settings.getByRole("tab", { name: "Tools", exact: true }).click()
|
||||
await expect(settings.getByRole("heading", { name: "Tools", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("tab", { name: "MCPs", exact: true })).toHaveAttribute("aria-selected", "true")
|
||||
await expect(settings.getByRole("tab", { name: "Plugins", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("tab", { name: "Skills", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("tab", { name: "Extensions", exact: true })).toHaveCount(0)
|
||||
await page.screenshot({ path: info.outputPath("tools-settings.png") })
|
||||
})
|
||||
@@ -28,7 +28,9 @@ resizing reduces the effective row capacity. The report records actual retained
|
||||
rows and the first retained fixture record rather than assuming 10,000 rows. Completion
|
||||
requires the final marker in Ghostty and completion of its write callbacks, not
|
||||
just WebSocket delivery. Teardown requires Home readiness and the final serialized
|
||||
snapshot. Input, focus, resizing, and native process survival are checked.
|
||||
snapshot, capped at the production limit of 2,000 scrollback rows plus the screen.
|
||||
The snapshot must contain the same ordered workload records as that retained tail.
|
||||
Input, focus, resizing, and native process survival are checked.
|
||||
|
||||
`probe.ts` is included only by this benchmark build. It observes actual writes,
|
||||
renderer calls, and serialization. Chrome `TaskDuration` measures renderer task
|
||||
|
||||
@@ -236,6 +236,15 @@ for (const scenario of ["visible-output", "hidden-output", "full-scrollback-tear
|
||||
const close = page.locator(`[data-titlebar-tab-slot]:has(a[href="${href}"]) [data-component="icon-button-v2"]`)
|
||||
await expect(close).toBeVisible()
|
||||
const cpuBefore = await cdp.send("Performance.getMetrics")
|
||||
const retained = await page.evaluate(() => {
|
||||
const term = window.terminalProbe.term!
|
||||
const buffer = term.buffer.normal
|
||||
const start = Math.max(0, buffer.length - term.rows - 2_000)
|
||||
return Array.from(
|
||||
{ length: buffer.length - start },
|
||||
(_, index) => buffer.getLine(start + index)?.translateToString(true) ?? "",
|
||||
).join("\n")
|
||||
})
|
||||
const start = await page.evaluate(() => performance.now())
|
||||
await close.click()
|
||||
await expect(page).toHaveURL("/")
|
||||
@@ -256,9 +265,11 @@ for (const scenario of ["visible-output", "hidden-output", "full-scrollback-tear
|
||||
cpuBefore.metrics.find((x) => x.name === "TaskDuration")!.value) *
|
||||
1000
|
||||
const snapshot = await page.evaluate(() => window.terminalProbe.serialized[0].value)
|
||||
expect(Array.from(snapshot.matchAll(/-(\d{5})\.test\.ts/g), (match) => Number(match[1]))).toEqual(
|
||||
Array.from({ length: 12_000 - produced.firstRecord }, (_, index) => produced.firstRecord + index),
|
||||
)
|
||||
const records = (text: string) => Array.from(text.matchAll(/-(\d{5})\.test\.ts/g), (match) => Number(match[1]))
|
||||
const expected = records(retained)
|
||||
const actual = records(snapshot)
|
||||
expect(actual.length).toBe(expected.length)
|
||||
expect(actual.every((record, index) => record === expected[index])).toBe(true)
|
||||
expect(snapshot).toContain("TERMINAL_WORKLOAD_DONE")
|
||||
await writeFile(
|
||||
path.join(
|
||||
@@ -301,6 +312,7 @@ for (const scenario of ["visible-output", "hidden-output", "full-scrollback-tear
|
||||
await expect
|
||||
.poll(async () => sizes.at(-1)?.cols === (await page.evaluate(() => window.terminalProbe.term!.cols)))
|
||||
.toBe(true)
|
||||
await expect(page.locator('#terminal-panel [data-slot="tabs-list"]')).toHaveCSS("padding-inline-start", "12px")
|
||||
expect(closed).toBe(0)
|
||||
}
|
||||
if (process.env.TERMINAL_SCREENSHOTS && scenario !== "full-scrollback-teardown") {
|
||||
|
||||
@@ -1,25 +1,6 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import type { Page, Route } from "@playwright/test"
|
||||
import { createMockServerHandler, mockOpenCodeServer } from "../../utils/mock-server"
|
||||
|
||||
test("serves an empty config document list for composer defaults", async () => {
|
||||
const server = createMockServerHandler({
|
||||
provider: {},
|
||||
directory: "C:/OpenCode",
|
||||
project: {},
|
||||
sessions: [],
|
||||
pageMessages: () => ({ items: [] }),
|
||||
})
|
||||
try {
|
||||
const response = await server.handler(
|
||||
new Request("http://localhost/api/config?location%5Bdirectory%5D=C%3A%2FOpenCode"),
|
||||
)
|
||||
expect(response.status).toBe(200)
|
||||
expect(await response.json()).toEqual([])
|
||||
} finally {
|
||||
await server.dispose()
|
||||
}
|
||||
})
|
||||
import { mockOpenCodeServer } from "../../utils/mock-server"
|
||||
|
||||
test("applies message latency after a list response gate is released", async () => {
|
||||
const events: string[] = []
|
||||
|
||||
@@ -59,6 +59,9 @@ test("keeps the file-browser sidebar mounted when switching file tabs", async ({
|
||||
expect(await readProbe(page)).toBe(PROBE)
|
||||
await expect.poll(() => viewport.evaluate((element) => element.scrollTop)).toBe(scrolled)
|
||||
})
|
||||
test.afterEach(async ({ page }, info) => {
|
||||
if (info.status === "passed") await page.screenshot({ path: info.outputPath("review-files-extension.png") })
|
||||
})
|
||||
|
||||
type Probed = HTMLElement & { __e2eProbe?: string }
|
||||
|
||||
|
||||
@@ -342,7 +342,6 @@ async function mockServers(
|
||||
if (route.request().method() === "GET" && sessionPermission)
|
||||
return json(route, { data: options.sessionPending?.[sessionPermission[1]!] ?? [] })
|
||||
if (requestDirectory && requestDirectory !== directory) return json(route, { name: "InvalidDirectory" }, 500)
|
||||
if (url.pathname === "/api/config") return json(route, [])
|
||||
if (url.pathname === "/api/provider")
|
||||
return json(route, {
|
||||
location: { directory },
|
||||
|
||||
@@ -39,51 +39,6 @@ test("restores review mode and selected file per session", async ({ page }) => {
|
||||
await expectSelectedFile(page, "gamma.ts")
|
||||
})
|
||||
|
||||
for (const tab of ["Context", "Open file", "README.md"]) {
|
||||
test(`restores the selected ${tab} pane tab after switching sessions and reloading`, async ({ page }) => {
|
||||
await setup(page)
|
||||
await page.goto(sessionHref(sessionA))
|
||||
await expectSessionTitle(page, titleA)
|
||||
await page.getByRole("button", { name: "Toggle review" }).click()
|
||||
|
||||
const panel = page.locator("#review-panel")
|
||||
if (tab === "Context") await page.getByRole("button", { name: "View context usage" }).click()
|
||||
if (tab !== "Context") await panel.getByRole("button", { name: "Open file" }).click()
|
||||
if (tab === "README.md") await panel.getByRole("button", { name: "README.md" }).click()
|
||||
await expect(panel.getByRole("tab", { name: tab, selected: true })).toBeVisible()
|
||||
|
||||
await switchSession(page, titleB)
|
||||
await page.getByRole("button", { name: "Toggle review" }).click()
|
||||
await expect(panel.locator("#session-side-panel-review-tab")).toHaveAttribute("aria-selected", "true")
|
||||
|
||||
await switchSession(page, titleA)
|
||||
await expect(panel.getByRole("tab", { name: tab, selected: true })).toBeVisible()
|
||||
|
||||
await page.reload()
|
||||
await expectSessionTitle(page, titleA)
|
||||
await expect(panel.getByRole("tab", { name: tab, selected: true })).toBeVisible()
|
||||
|
||||
const selected = panel.getByRole("tab", { name: tab })
|
||||
const review = panel.locator("#session-side-panel-review-tab")
|
||||
await selected.press("Home")
|
||||
await expect(review).toHaveAttribute("aria-selected", "true")
|
||||
await review.press("End")
|
||||
await expect(selected).toHaveAttribute("aria-selected", "true")
|
||||
await review.click()
|
||||
await expect(review).toHaveAttribute("aria-selected", "true")
|
||||
await selected.click()
|
||||
await expect(selected).toHaveAttribute("aria-selected", "true")
|
||||
|
||||
await switchSession(page, titleB)
|
||||
await expect(review).toHaveAttribute("aria-selected", "true")
|
||||
await switchSession(page, titleA)
|
||||
await expect(selected).toHaveAttribute("aria-selected", "true")
|
||||
await selected.press("Control+w")
|
||||
await expect(selected).toHaveCount(0)
|
||||
await expect(review).toHaveAttribute("aria-selected", "true")
|
||||
})
|
||||
}
|
||||
|
||||
async function selectFile(page: Page, file: string) {
|
||||
await page.getByRole("button", { name: file }).click()
|
||||
await expectSelectedFile(page, file)
|
||||
@@ -121,10 +76,6 @@ async function setup(page: Page) {
|
||||
default: { providerID: "opencode", modelID: "test" },
|
||||
},
|
||||
sessions: [session(sessionA, titleA, 1700000000000), session(sessionB, titleB, 1700000001000)],
|
||||
fileList: () => [
|
||||
{ name: "README.md", path: "README.md", absolute: `${directory}/README.md`, type: "file", ignored: false },
|
||||
],
|
||||
fileContent: (path) => ({ type: "text", content: `contents:${path}` }),
|
||||
pageMessages: () => ({ items: [] }),
|
||||
})
|
||||
await page.route(/\/api\/vcs(?:\?.*)?$/, (route) =>
|
||||
|
||||
@@ -139,7 +139,7 @@ test("uses side placement by default and supports the terminal across the bottom
|
||||
await expectSessionReady(page, { server, sessionID, title })
|
||||
await expect(page.locator("#review-panel")).toBeVisible()
|
||||
await expectTree(page, 2_773, "action.yml")
|
||||
await expect(page.locator("#session-side-panel-review-tab")).toHaveText("Files Changed 2740")
|
||||
await expect(page.locator("#review-panel").getByRole("tab", { name: "Files Changed 2740", exact: true })).toBeVisible()
|
||||
await page.keyboard.press("Control+Backquote")
|
||||
await expect(page.locator("#terminal-panel")).toBeVisible()
|
||||
await expectTree(page, 2_773, "action.yml")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { base64Encode } from "@opencode/util/encode"
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test"
|
||||
import { expect, test, type Locator } from "@playwright/test"
|
||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||
import { expectSessionTitle } from "../utils/waits"
|
||||
|
||||
@@ -85,38 +85,6 @@ for (const width of [1000, 1440]) {
|
||||
await expect.poll(() => toggle.boundingBox()).toEqual(closed)
|
||||
})
|
||||
|
||||
test(`keeps moving header content out of the toggle area (${width}px, ${direction})`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 900 })
|
||||
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
|
||||
await expectSessionTitle(page, "Review toggle position")
|
||||
await page.locator("html").evaluate((element, dir) => element.setAttribute("dir", dir), direction)
|
||||
|
||||
const toggle = page.getByRole("button", { name: "Toggle review", exact: true })
|
||||
await expect(toggle).toHaveAttribute("aria-expanded", "false")
|
||||
for (const opened of [true, false]) {
|
||||
// Pause in the same task as the click so even the first painted state can be inspected.
|
||||
await toggle.evaluate((element) => {
|
||||
;(element as HTMLButtonElement).click()
|
||||
document
|
||||
.getAnimations()
|
||||
.filter((animation) => animation.timeline instanceof DocumentTimeline)
|
||||
.forEach((animation) => animation.pause())
|
||||
})
|
||||
await expect(toggle).toHaveAttribute("aria-expanded", String(opened))
|
||||
await expect(page.locator("#review-panel")).toHaveAttribute("aria-hidden", String(!opened))
|
||||
for (const progress of [0.08, 0.16, 0.25, 0.5, 0.8, 0.96]) {
|
||||
await expectHeaderClearOfToggle(page, toggle, progress)
|
||||
}
|
||||
await page.evaluate(() => {
|
||||
document
|
||||
.getAnimations()
|
||||
.filter((animation) => animation.timeline instanceof DocumentTimeline)
|
||||
.forEach((animation) => animation.finish())
|
||||
})
|
||||
}
|
||||
await expect(page.locator("#review-panel")).toBeHidden()
|
||||
})
|
||||
|
||||
test(`keeps terminal controls clear of the review toggle (${width}px, ${direction})`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 900 })
|
||||
const ptys: { id: string; title: string }[] = []
|
||||
@@ -198,84 +166,10 @@ for (const width of [1000, 1440]) {
|
||||
await expect(toggle).toBeFocused()
|
||||
await expect.poll(() => toggle.boundingBox()).toEqual(position)
|
||||
await expectTerminalControlsAligned(terminal, toggle)
|
||||
|
||||
// Closing the terminal clears the region's animation flag while retaining the review contents.
|
||||
await page.keyboard.press("Control+Backquote")
|
||||
await expect(terminal).toBeHidden()
|
||||
await expect
|
||||
.poll(() =>
|
||||
page
|
||||
.locator('[data-slot="session-chat-panel"]')
|
||||
.evaluate((element) => element.getAnimations().every((animation) => animation.playState === "finished")),
|
||||
)
|
||||
.toBe(true)
|
||||
await expect(page.locator('[data-slot="session-review-content"]')).toHaveCSS("opacity", "0")
|
||||
await toggle.evaluate((element) => {
|
||||
;(element as HTMLButtonElement).click()
|
||||
document
|
||||
.getAnimations()
|
||||
.filter((animation) => animation.timeline instanceof DocumentTimeline)
|
||||
.forEach((animation) => animation.pause())
|
||||
})
|
||||
await expect(toggle).toHaveAttribute("aria-expanded", "true")
|
||||
await expectHeaderClearOfToggle(page, toggle, 0.25)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function expectHeaderClearOfToggle(page: Page, toggle: Locator, progress: number) {
|
||||
const geometry = await page.locator('[data-slot="session-chat-panel"]').evaluate((chat, progress) => {
|
||||
const row = chat.parentElement!
|
||||
const animations = row
|
||||
.getAnimations({ subtree: true })
|
||||
.filter((animation) => animation.timeline instanceof DocumentTimeline)
|
||||
const width = animations.find(
|
||||
(animation) => animation instanceof CSSTransition && animation.transitionProperty === "width",
|
||||
)!
|
||||
animations.forEach((animation) => {
|
||||
animation.pause()
|
||||
animation.currentTime = Number(width.effect!.getTiming().duration) * progress
|
||||
})
|
||||
const chatBounds = chat.getBoundingClientRect()
|
||||
const panelBounds = document.querySelector("#review-panel")!.getBoundingClientRect()
|
||||
const summaryBounds = document
|
||||
.querySelector('[data-session-title] button[aria-label="Session details"]')!
|
||||
.getBoundingClientRect()
|
||||
return {
|
||||
row: row.getBoundingClientRect().width,
|
||||
panelWidth: panelBounds.width,
|
||||
timelineControlInset:
|
||||
getComputedStyle(row).direction === "rtl"
|
||||
? summaryBounds.left - chatBounds.left
|
||||
: chatBounds.right - summaryBounds.right,
|
||||
gap:
|
||||
getComputedStyle(row).direction === "rtl"
|
||||
? chatBounds.left - panelBounds.right
|
||||
: panelBounds.left - chatBounds.right,
|
||||
contentOpacity: Number(getComputedStyle(document.querySelector('[data-slot="session-review-content"]')!).opacity),
|
||||
panels: chatBounds.width + panelBounds.width + parseFloat(getComputedStyle(row).columnGap),
|
||||
}
|
||||
}, progress)
|
||||
expect(geometry.gap).toBeCloseTo(8, 1)
|
||||
// Reserve the fixed toggle's 28px width, the 8px control gap, and the 12px header inset.
|
||||
expect(geometry.timelineControlInset).toBeCloseTo(48, 1)
|
||||
if (geometry.panelWidth > 0) expect(Math.abs(geometry.row - geometry.panels)).toBeLessThanOrEqual(1)
|
||||
if (progress === 0.25) {
|
||||
expect(geometry.contentOpacity).toBeGreaterThan(0)
|
||||
expect(geometry.contentOpacity).toBeLessThan(1)
|
||||
}
|
||||
|
||||
const clip = await toggle.boundingBox()
|
||||
if (!clip) throw new Error("Review toggle bounds are unavailable")
|
||||
// Header contents must make no difference to the pixels behind the fixed toggle.
|
||||
expect(await page.screenshot({ clip })).toEqual(
|
||||
await page.screenshot({
|
||||
clip,
|
||||
style: ".session-review-v2-tabs-bar { visibility: hidden !important; }",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async function expectTerminalControlsAligned(terminal: Locator, toggle: Locator) {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
import { setupTimeline } from "../performance/timeline-stability/fixture"
|
||||
|
||||
for (const reducedMotion of [false, true]) {
|
||||
test(`suppresses the scrollbar from toggle press until timeline interaction (reduced motion: ${reducedMotion})`, async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupTimeline(page, { seedHistory: true, reducedMotion })
|
||||
const chat = page.locator('[data-slot="session-chat-panel"]')
|
||||
const scroll = page.locator('[data-slot="session-timeline-scroll"]')
|
||||
const viewport = scroll.locator(".scroll-view__viewport")
|
||||
const thumb = scroll.locator('.scroll-view__thumb[data-orientation="vertical"]')
|
||||
const toggle = page.getByRole("button", { name: "Toggle review", exact: true })
|
||||
await expect(thumb).toHaveCount(1)
|
||||
await scroll.hover()
|
||||
await expect(thumb).toHaveAttribute("data-visible", "true")
|
||||
await expect(thumb).toHaveCSS("visibility", "visible")
|
||||
|
||||
for (const opened of [true, false]) {
|
||||
await toggle.hover()
|
||||
await page.mouse.down()
|
||||
await expect(thumb).toHaveCSS("visibility", "hidden")
|
||||
await page.mouse.up()
|
||||
await expect(toggle).toHaveAttribute("aria-expanded", String(opened))
|
||||
await chat.evaluate(async (element) => {
|
||||
await Promise.all(element.getAnimations().map((animation) => animation.finished))
|
||||
})
|
||||
await expect(chat).toHaveAttribute("data-width-animating", "false")
|
||||
await expect(thumb).toHaveCSS("visibility", "hidden")
|
||||
// Late scroll anchoring must not bring the thumb back after the panel has settled.
|
||||
await viewport.evaluate(
|
||||
(element) =>
|
||||
new Promise<void>((resolve) => {
|
||||
element.addEventListener("scroll", () => resolve(), { once: true })
|
||||
element.scrollTop += element.scrollTop > 0 ? -1 : 1
|
||||
}),
|
||||
)
|
||||
await expect(thumb).toHaveCSS("visibility", "hidden")
|
||||
await scroll.hover()
|
||||
await expect(thumb).toHaveAttribute("data-visible", "true")
|
||||
await expect(thumb).toHaveCSS("visibility", "visible")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -139,15 +139,9 @@ async function openSession(page: Page, mock: ReturnType<typeof createQueueMock>,
|
||||
sessionStatus: () => ({ [sessionID]: { type: "running" } }),
|
||||
inbox: () => mock.rows.map((row) => ({ ...row, payload: { ...row.payload } })),
|
||||
onPrompt: mock.onPrompt,
|
||||
commands: [{ name: "review", description: "Review current changes" }],
|
||||
onInboxChange: mock.onInboxChange,
|
||||
events: mock.events,
|
||||
})
|
||||
await page.route(`**/api/session/${sessionID}/command`, async (route) => {
|
||||
const body = route.request().postDataJSON() as Record<string, unknown>
|
||||
mock.onPrompt({ sessionID, body: { ...body, text: `Review ${body.text}` } })
|
||||
await route.fulfill({ status: 204 })
|
||||
})
|
||||
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
|
||||
const composer = page.locator('[data-component="composer"]')
|
||||
await expectAppVisible(composer)
|
||||
@@ -173,31 +167,6 @@ test("follow-up preference controls Enter while Mod+Enter uses the alternate del
|
||||
await expect(view.input).toHaveText("")
|
||||
})
|
||||
|
||||
for (const behavior of ["queue", "steer"] as const) {
|
||||
test(`slash commands respect ${behavior} preference and alternate submit`, async ({ page }, testInfo) => {
|
||||
const mock = createQueueMock([])
|
||||
const view = await openSession(page, mock, behavior)
|
||||
await expect(view.input).toBeEditable()
|
||||
await view.input.fill("/review current changes")
|
||||
await expect(view.composer.locator('[data-action="composer-alternate-delivery"]')).toContainText(
|
||||
behavior === "queue" ? "Steer" : "Queue",
|
||||
)
|
||||
await view.input.press("Enter")
|
||||
await expect(page.getByText("Review current changes", { exact: true })).toBeVisible()
|
||||
await expect(view.input).toHaveText("")
|
||||
await page.screenshot({ path: testInfo.outputPath("slash-command-delivery.png") })
|
||||
expect(mock.prompts.map((prompt) => prompt.delivery)).toEqual([behavior])
|
||||
await expect(view.rows).toHaveCount(behavior === "queue" ? 1 : 0)
|
||||
|
||||
await view.input.fill("/review the retry path")
|
||||
await view.input.press("ControlOrMeta+Enter")
|
||||
await expect
|
||||
.poll(() => mock.prompts.map((prompt) => prompt.delivery))
|
||||
.toEqual([behavior, behavior === "queue" ? "steer" : "queue"])
|
||||
await expect(view.rows).toHaveCount(1)
|
||||
})
|
||||
}
|
||||
|
||||
test("dragging reorders queued prompts", async ({ page }) => {
|
||||
const mock = createQueueMock(["first queued prompt", "second queued prompt", "third queued prompt"])
|
||||
const view = await openSession(page, mock)
|
||||
@@ -339,8 +308,8 @@ for (const delivery of ["steer", "queue"] as const) {
|
||||
})
|
||||
const tools = page.locator('[data-timeline-part-ids="tool_queue_read,tool_queue_grep"]')
|
||||
await expect(tools).toBeVisible()
|
||||
await expect(tools).toHaveText(/^Used\s*2\s*Read, Grep$/)
|
||||
await expect(tools.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("Read, Grep")
|
||||
await expect(tools).toHaveText(/^Used\s*1 Read, 1 Grep$/)
|
||||
await expect(tools.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("1 Read, 1 Grep")
|
||||
await expect(thinking).toHaveCount(0)
|
||||
await expect(pending).toBeVisible()
|
||||
expect(mock.rows.map((row) => ({ id: row.id, delivery: row.delivery }))).toEqual([
|
||||
@@ -349,7 +318,7 @@ for (const delivery of ["steer", "queue"] as const) {
|
||||
await transcript.screenshot({ path: testInfo.outputPath("pending-steer.png") })
|
||||
|
||||
// Soft assertions let delivery run too, even when the pending ordering regresses.
|
||||
await expect.soft(tools.or(pending)).toHaveText([/^Used\s*2\s*Read, Grep$/, /U2: Also check the retry path\./])
|
||||
await expect.soft(tools.or(pending)).toHaveText([/^Used\s*1 Read, 1 Grep$/, /U2: Also check the retry path\./])
|
||||
await expect
|
||||
.soft(transcript.locator('[data-timeline-row="AssistantPart"]').filter({ has: tools }))
|
||||
.toHaveAttribute("data-message-id", userID)
|
||||
@@ -381,7 +350,7 @@ for (const delivery of ["steer", "queue"] as const) {
|
||||
await expect(response).toHaveAttribute("data-message-id", inboxID)
|
||||
await expect(thinking).toHaveCount(0)
|
||||
await expect(tools.or(pending).or(response)).toHaveText([
|
||||
/^Used\s*2\s*Read, Grep$/,
|
||||
/^Used\s*1 Read, 1 Grep$/,
|
||||
/U2: Also check the retry path\./,
|
||||
/A3: Now checking the retry path for U2\./,
|
||||
])
|
||||
|
||||
@@ -111,7 +111,7 @@ test.describe("regression: session timeline local row state", () => {
|
||||
const id = `prt_patch_${count}`
|
||||
events.push(...toolEvents({ ...part, id, callID: id }))
|
||||
await expect(summary).toHaveAccessibleName(`Used ${count} Patch`)
|
||||
await expect(summary.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("Patch")
|
||||
await expect(summary.locator('[data-slot="basic-tool-tool-title"]')).toHaveText(`${count} Patch`)
|
||||
await expect(group).toHaveAttribute("data-timeline-part-ids", new RegExp(`${id}$`))
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", String(count === 2))
|
||||
await expect(summary).toHaveAttribute("aria-expanded", "true")
|
||||
|
||||
@@ -55,7 +55,7 @@ test.describe("regression: session timeline context group resize", () => {
|
||||
await devtools.send("Emulation.setCPUThrottlingRate", { rate: 4 })
|
||||
const context = page.locator(`[data-timeline-part-ids="${contextIDs.join(",")}"]`).first()
|
||||
await expectAppVisible(context)
|
||||
await expect(context.getByRole("button")).toHaveAccessibleName("Used 4 Read, Glob, Grep, List")
|
||||
await expect(context.getByRole("button")).toHaveAccessibleName("Used 1 Read, 1 Glob, 1 Grep, 1 List")
|
||||
|
||||
const contextSelector = `[data-timeline-part-ids="${contextIDs.join(",")}"]`
|
||||
const regions = defineVisualRegions({
|
||||
@@ -88,7 +88,7 @@ test.describe("regression: session timeline context group resize", () => {
|
||||
await page.waitForTimeout(delay)
|
||||
}
|
||||
|
||||
await expect(context.getByRole("button")).toHaveAccessibleName("Used 4 Read, Glob, Grep, List")
|
||||
await expect(context.getByRole("button")).toHaveAccessibleName("Used 1 Read, 1 Glob, 1 Grep, 1 List")
|
||||
await page.waitForTimeout(700)
|
||||
const trace = await stopVisualProbe<keyof typeof regions>(page)
|
||||
const labels = trace.samples
|
||||
@@ -107,7 +107,7 @@ test.describe("regression: session timeline context group resize", () => {
|
||||
]),
|
||||
)
|
||||
|
||||
expect(labels).toEqual(["Used 4 Read, Glob, Grep, List"])
|
||||
expect(labels).toEqual(["Used 1 Read, 1 Glob, 1 Grep, 1 List"])
|
||||
expect(issues, JSON.stringify(trace.samples, null, 2)).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,8 +16,8 @@ for (const locale of ["de", "ar"] as const) {
|
||||
})
|
||||
|
||||
const group = page.locator(`[data-timeline-part-ids="${ids.join(",")}"]`)
|
||||
const names = locale === "de" ? "Lesen, Glob" : "\u0642\u0631\u0627\u0621\u0629, Glob"
|
||||
await expect(group.getByRole("button")).toHaveAccessibleName(`Used 2 ${names}`)
|
||||
const names = locale === "de" ? "1 Lesen, 1 Glob" : "1 \u0642\u0631\u0627\u0621\u0629, 1 Glob"
|
||||
await expect(group.getByRole("button")).toHaveAccessibleName(`Used ${names}`)
|
||||
await expect(group.locator('[data-slot="basic-tool-tool-title"]')).toHaveText(names)
|
||||
await expect(page.locator("html")).toHaveAttribute("lang", locale)
|
||||
})
|
||||
|
||||
@@ -386,7 +386,7 @@ test("separates blocking and already-backgrounded work into two rows", async ({
|
||||
const used = page
|
||||
.locator('[data-timeline-part-ids="call_backgrounded,call_shell_backgrounded,call_blocking"]')
|
||||
.locator(':scope > [data-component="collapsible"] > [data-slot="collapsible-trigger"]')
|
||||
await expect(used).toHaveText(/^Used\s*3\s*Agent, Shell$/)
|
||||
await expect(used).toHaveText(/^Used\s*2 Agent, 1 Shell$/)
|
||||
await expect(used).toHaveAttribute("aria-expanded", "false")
|
||||
await used.click()
|
||||
await expect(used).toHaveAttribute("aria-expanded", "true")
|
||||
|
||||
@@ -45,10 +45,10 @@ test("expands a mixed collapsed tool stack without expanding its individual call
|
||||
const group = page.locator(
|
||||
'[data-timeline-part-ids="prt_stack_shell_1,prt_stack_explore,prt_stack_patch,prt_stack_shell_2"]',
|
||||
)
|
||||
const summary = group.getByRole("button", { name: "Used 4 Shell, Agent, Patch", exact: true })
|
||||
const summary = group.getByRole("button", { name: "Used 2 Shell, 1 Agent, 1 Patch", exact: true })
|
||||
await expect(summary).toHaveAttribute("aria-expanded", "false")
|
||||
await expect(summary).toHaveCSS("height", "28px")
|
||||
await expect(summary.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("Shell, Agent, Patch")
|
||||
await expect(summary.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("2 Shell, 1 Agent, 1 Patch")
|
||||
await expect(summary.locator('[data-component="tag"]')).toHaveCount(0)
|
||||
await summary.click()
|
||||
await expect(summary).toHaveAttribute("aria-expanded", "true")
|
||||
@@ -75,8 +75,8 @@ test("leaves tools expanded by settings outside the collapsed stack", async ({ p
|
||||
|
||||
await expect(page.locator('[data-timeline-part-id="prt_expanded_shell"]')).toBeVisible()
|
||||
const group = page.locator('[data-timeline-part-ids="prt_collapsed_patch,prt_collapsed_read"]')
|
||||
await expect(group.getByRole("button", { name: "Used 2 Patch, Read", exact: true })).toBeVisible()
|
||||
await expect(group.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("Patch, Read")
|
||||
await expect(group.getByRole("button", { name: "Used 1 Patch, 1 Read", exact: true })).toBeVisible()
|
||||
await expect(group.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("1 Patch, 1 Read")
|
||||
await expect(page.locator('[data-timeline-spacing="tool"]')).toHaveCSS("padding-top", "8px")
|
||||
})
|
||||
|
||||
@@ -114,7 +114,7 @@ test("combines follow-up patches into one three-file stack inside Used", async (
|
||||
],
|
||||
})
|
||||
const group = page.locator('[data-component="collapsed-tool-group"]')
|
||||
await group.getByRole("button", { name: "Used 2 Shell, Patch", exact: true }).click()
|
||||
await group.getByRole("button", { name: "Used 1 Shell, 1 Patch", exact: true }).click()
|
||||
await expect(group.getByText("2 files", { exact: true })).toBeVisible()
|
||||
await timeline.send(
|
||||
partUpdated(
|
||||
@@ -129,7 +129,7 @@ test("combines follow-up patches into one three-file stack inside Used", async (
|
||||
),
|
||||
),
|
||||
)
|
||||
await expect(group.getByRole("button", { name: "Used 3 Shell, Patch", exact: true })).toHaveAttribute(
|
||||
await expect(group.getByRole("button", { name: "Used 1 Shell, 2 Patch", exact: true })).toHaveAttribute(
|
||||
"aria-expanded",
|
||||
"true",
|
||||
)
|
||||
@@ -162,7 +162,7 @@ test("keeps failed search calls and their error cards inside the collapsed stack
|
||||
await setupTimeline(page, { messages: [userMessage(), assistantMessage(parts)] })
|
||||
|
||||
const group = page.locator('[data-component="collapsed-tool-group"]')
|
||||
const summary = group.getByRole("button", { name: "Used 2 Glob, Grep", exact: true })
|
||||
const summary = group.getByRole("button", { name: "Used 1 Glob, 1 Grep", exact: true })
|
||||
await expect(summary).toHaveAttribute("aria-expanded", "false")
|
||||
await summary.click()
|
||||
await expect(group.locator('[data-kind="tool-error-card"]')).toHaveCount(2)
|
||||
|
||||
@@ -169,7 +169,7 @@ test("worktree deletion sends the project location separately from the target",
|
||||
await expect(settings.getByText("11 worktrees", { exact: true })).toBeVisible()
|
||||
})
|
||||
|
||||
test("extensions opens without waiting for MCPs", async ({ page }) => {
|
||||
test("tools opens without waiting for MCPs", async ({ page }) => {
|
||||
const mcps = Promise.withResolvers<void>()
|
||||
await page.route("**/api/mcp", async (route) => {
|
||||
await mcps.promise
|
||||
@@ -179,9 +179,9 @@ test("extensions opens without waiting for MCPs", async ({ page }) => {
|
||||
})
|
||||
const settings = page.getByTestId("settings-screen")
|
||||
const requested = page.waitForRequest((request) => new URL(request.url()).pathname === "/api/mcp")
|
||||
await settings.getByRole("tab", { name: "Extensions", exact: true }).click()
|
||||
await settings.getByRole("tab", { name: "Tools", exact: true }).click()
|
||||
await requested
|
||||
await expect(settings.getByRole("heading", { name: "Extensions", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("heading", { name: "Tools", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("button", { name: "Back to app" })).toBeVisible()
|
||||
mcps.resolve()
|
||||
await settings.getByRole("tab", { name: "MCPs", exact: true }).click()
|
||||
|
||||
@@ -55,7 +55,7 @@ for (const viewport of [
|
||||
"Worktrees",
|
||||
"Providers",
|
||||
"Models",
|
||||
"Extensions",
|
||||
"Tools",
|
||||
"Experimental",
|
||||
"About",
|
||||
]) {
|
||||
|
||||
@@ -602,7 +602,6 @@ async function mockServer(page: Page) {
|
||||
if (url.origin !== server) return route.fallback()
|
||||
if (url.pathname === `/api/session/${unresolvedSessionID}`) return new Promise(() => {})
|
||||
if (url.pathname === "/api/event") return sse(route)
|
||||
if (url.pathname === "/api/config") return json(route, [])
|
||||
if (url.pathname === "/api/session")
|
||||
return json(route, { data: sessions.map((session) => currentSession(session)), cursor: {} })
|
||||
if (url.pathname === "/api/session/active") return json(route, { data: {} })
|
||||
|
||||
@@ -206,9 +206,9 @@ test("animates review and terminal panels while caching hidden terminal content"
|
||||
|
||||
await page.keyboard.press("Control+Backquote")
|
||||
await expect(panel).toBeVisible()
|
||||
await expectAnimation(page, "terminal-panel-size-in")
|
||||
await expectAnimation(page, "auxiliary-panel-size-in")
|
||||
await page.keyboard.press("Control+Backquote")
|
||||
await expectAnimation(page, "terminal-panel-size-out")
|
||||
await expectAnimation(page, "auxiliary-panel-size-out")
|
||||
await expect(panel).toBeHidden()
|
||||
await expect(page.locator('[data-component="terminal"]')).toBeAttached()
|
||||
})
|
||||
|
||||
@@ -99,16 +99,13 @@ test("creates a session in a new project and selects its model", async ({ page }
|
||||
const modelControl = page.locator('[data-action="composer-model"]')
|
||||
await expect(modelControl).toContainText("Go Model 1")
|
||||
await modelControl.click()
|
||||
const modelSearch = page.getByPlaceholder("Search models", { exact: true })
|
||||
await expect(modelSearch).toBeFocused()
|
||||
await modelSearch.press("ArrowDown")
|
||||
await modelSearch.press("Enter")
|
||||
await page.locator('[data-option-key="opencode:free-model"]').click()
|
||||
await expect(modelControl).toContainText("Free Model")
|
||||
|
||||
await modelControl.click()
|
||||
await expect(modelSearch).toBeFocused()
|
||||
await modelSearch.press("ArrowUp")
|
||||
await modelSearch.press("Enter")
|
||||
const goModel = page.locator('[data-option-key="opencode-go:go-model-1"]')
|
||||
await expect(goModel).toBeVisible()
|
||||
await goModel.click()
|
||||
|
||||
await expect(modelControl).toContainText("Go Model 1")
|
||||
})
|
||||
|
||||
@@ -39,7 +39,6 @@ const Group = HttpApiGroup.make("mock")
|
||||
}),
|
||||
)
|
||||
.add(HttpApiEndpoint.get("reference", "/api/reference", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("config", "/api/config", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("agent", "/api/agent", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("provider", "/api/provider", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("model", "/api/model", { success: Json }))
|
||||
|
||||
@@ -38,7 +38,6 @@ export interface MockServerConfig {
|
||||
sessionStatus?: Record<string, unknown> | (() => Record<string, unknown>)
|
||||
inbox?: unknown[] | (() => unknown[])
|
||||
onPrompt?: (input: { sessionID: string; body: Record<string, unknown> }) => void
|
||||
commands?: { name: string; description?: string }[]
|
||||
onInboxChange?: (input: { sessionID: string; inboxID: string; action: "cancel" | "steer" }) => void
|
||||
}
|
||||
|
||||
@@ -208,7 +207,6 @@ function mockHandlers(config: MockServerConfig, state: { cursors: Map<string, st
|
||||
)
|
||||
.handleAll({
|
||||
health: () => Effect.succeed({ healthy: true, version: "2.0.0", pid: 1 }),
|
||||
config: () => Effect.succeed([]),
|
||||
reference: () =>
|
||||
Effect.succeed({
|
||||
location: {
|
||||
@@ -255,7 +253,7 @@ function mockHandlers(config: MockServerConfig, state: { cursors: Map<string, st
|
||||
Effect.andThen(noContent),
|
||||
),
|
||||
credentialRemove: () => noContent,
|
||||
command: () => Effect.succeed({ location: location(config), data: config.commands ?? [] }),
|
||||
command: () => Effect.succeed({ location: location(config), data: [] }),
|
||||
skill: () => Effect.succeed({ location: location(config), data: [] }),
|
||||
plugin: () => Effect.succeed({ location: location(config), data: [] }),
|
||||
mcp: () => Effect.succeed({ location: location(config), data: [] }),
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"./i18n/desktop-native": "./src/runtime/i18n/desktop-native.ts",
|
||||
"./updater": "./src/shell/updates/types.ts",
|
||||
"./wsl/types": "./src/servers/wsl/types.ts",
|
||||
"./ssh": "./src/servers/ssh/types.ts",
|
||||
"./vite": "./vite.js",
|
||||
"./index.css": "./src/index.css"
|
||||
},
|
||||
@@ -65,8 +64,10 @@
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/plugin-review-desktop": "workspace:*",
|
||||
"@opencode/plugin-context-desktop": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
"@opencode/plugin-browser": "workspace:*",
|
||||
"@opencode/session-ui": "workspace:*",
|
||||
"@opencode/ui": "workspace:*",
|
||||
"@opencode/util": "workspace:*",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import path from "node:path"
|
||||
|
||||
const packages = path.resolve(import.meta.dirname, "../..")
|
||||
const imports = new Set([
|
||||
"solid-js",
|
||||
"solid-js/store",
|
||||
"solid-js/web",
|
||||
"@tanstack/solid-query",
|
||||
"effect",
|
||||
"@opencode/plugin/desktop",
|
||||
"@opencode/plugin/desktop/solid",
|
||||
"@opencode/plugin/desktop/manager",
|
||||
"@opencode/plugin/desktop/persistence",
|
||||
"@opencode/schema/rpc",
|
||||
"@opencode/client",
|
||||
"@opencode/client/solid",
|
||||
"@opencode/util/encode",
|
||||
"@opencode/util/path",
|
||||
])
|
||||
for (const name of ["ui", "session-ui"]) {
|
||||
const directory = path.join(packages, name)
|
||||
const manifest: { name: string; exports: Record<string, string> } = await Bun.file(
|
||||
path.join(directory, "package.json"),
|
||||
).json()
|
||||
for (const [key, target] of Object.entries(manifest.exports)) {
|
||||
if (!/\.tsx?$/.test(target) || /storybook/.test(key)) continue
|
||||
if (!key.includes("*")) {
|
||||
imports.add(key === "." ? manifest.name : manifest.name + key.slice(1))
|
||||
continue
|
||||
}
|
||||
const [prefix, suffix] = target.split("*")
|
||||
for await (const file of new Bun.Glob(target.slice(2)).scan(directory)) {
|
||||
if (/(\.test\.|\.stories\.|\.story\.)/.test(file)) continue
|
||||
imports.add(
|
||||
manifest.name +
|
||||
key.slice(1).replace("*", ("./" + file.replaceAll("\\", "/")).slice(prefix.length, -suffix.length)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
await Bun.write(
|
||||
path.join(packages, "app/src/extensions/modules.gen.ts"),
|
||||
`// Generated by script/generate-extension-modules.ts. Shared renderer modules only.\nexport const modules: Record<string, () => Promise<unknown>> = {\n${Array.from(
|
||||
imports,
|
||||
)
|
||||
.sort()
|
||||
.map((name) => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(name)}),`)
|
||||
.join("\n")}\n}\n`,
|
||||
)
|
||||
@@ -17,8 +17,6 @@ import { ServerConnection, ServersProvider } from "@/runtime/server/registry"
|
||||
import { SettingsProvider } from "@/settings/model"
|
||||
import { TabsProvider } from "@/shell/tabs/tabs"
|
||||
import { WslServersProvider } from "@/servers/wsl/context"
|
||||
import { SshProvider } from "@/servers/ssh/context"
|
||||
import { SshRestore } from "@/servers/ssh/restore"
|
||||
import { ErrorPage } from "@/shell/errors/error"
|
||||
import { AppRoutes, File, preloadRoute } from "@/shell/routes/routes"
|
||||
|
||||
@@ -83,9 +81,7 @@ export function AppBaseProviders(
|
||||
<QueryProvider>
|
||||
<WslServersProvider>
|
||||
<DialogProvider>
|
||||
<SshProvider>
|
||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||
</SshProvider>
|
||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||
</DialogProvider>
|
||||
</WslServersProvider>
|
||||
</QueryProvider>
|
||||
@@ -113,7 +109,6 @@ export function AppInterface(props: {
|
||||
<BodyTypography />
|
||||
<CommandProvider>
|
||||
<DesktopCommands />
|
||||
<SshRestore />
|
||||
<HighlightsProvider>
|
||||
{props.children}
|
||||
{rootProps.children}
|
||||
|
||||
@@ -841,8 +841,8 @@ export function ComposerEditorSubmitButton(props: {
|
||||
disabled={!props.stopping && props.disabled}
|
||||
tabIndex={props.mode === "normal" ? undefined : -1}
|
||||
icon={<Icon name={props.stopping ? "stop" : props.mode === "shell" ? "arrow-undo-down" : "arrow-up"} />}
|
||||
variant="submit"
|
||||
class="size-7 rounded-md p-[6px]"
|
||||
variant="contrast"
|
||||
class="size-7 rounded-md p-[6px] disabled:opacity-50"
|
||||
aria-label={props.stopping ? props.stopLabel : props.sendLabel}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
|
||||
@@ -37,7 +37,6 @@ export type ComposerEditorView = {
|
||||
agent?: ComposerSelectControl
|
||||
variant?: ComposerSelectControl
|
||||
submit: {
|
||||
available?: Accessor<boolean>
|
||||
stopping: Accessor<boolean>
|
||||
working?: Accessor<boolean>
|
||||
queue?: ComposerQueue
|
||||
@@ -334,7 +333,6 @@ export function createComposerEditor(input: {
|
||||
draft.removeAttachment(id)
|
||||
},
|
||||
canSubmit() {
|
||||
if (input.view.submit.available?.() === false) return false
|
||||
if (input.view.draftOnly) return false
|
||||
const persisted = draft.state
|
||||
if (state.mode === "shell") {
|
||||
@@ -367,7 +365,6 @@ export function createComposerEditor(input: {
|
||||
dispatch({ type: "mode.shell" })
|
||||
},
|
||||
submit(options?: { alternate?: boolean }) {
|
||||
if (input.view.submit.available?.() === false) return
|
||||
if (input.view.draftOnly) return
|
||||
input.view.submit.onSubmit(options)
|
||||
dispatch({ type: "popover.close" })
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user