Compare commits
92
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce6c114d99 | ||
|
|
1a329e4e67 | ||
|
|
bfb2d8dc76 | ||
|
|
8f7a6c4a00 | ||
|
|
1857c73565 | ||
|
|
5f42351159 | ||
|
|
968aaa3cfe | ||
|
|
b8266e5819 | ||
|
|
ba746e36d8 | ||
|
|
d5068ba28e | ||
|
|
7265c46af6 | ||
|
|
536ee857c6 | ||
|
|
dda69d77e8 | ||
|
|
0e14404e5f | ||
|
|
05f51bfe45 | ||
|
|
aee552c043 | ||
|
|
4f6eaf859b | ||
|
|
d0cb58782f | ||
|
|
a3430db73a | ||
|
|
3e1972fd92 | ||
|
|
b368e5adbe | ||
|
|
69e4f52272 | ||
|
|
8a55920531 | ||
|
|
9db90a0b76 | ||
|
|
700d012025 | ||
|
|
59e486a917 | ||
|
|
8596967415 | ||
|
|
00038027c8 | ||
|
|
5cf597d583 | ||
|
|
d92b8d8009 | ||
|
|
854c535536 | ||
|
|
3cf955e9ad | ||
|
|
63f3e84792 | ||
|
|
0beb4de3e8 | ||
|
|
51da3483a9 | ||
|
|
060fbc9ce7 | ||
|
|
7a769dab3a | ||
|
|
2663ecd39d | ||
|
|
4ce247eaba | ||
|
|
76d9c2cd76 | ||
|
|
f6101aef8a | ||
|
|
f3874ec2f9 | ||
|
|
6466fcfdea | ||
|
|
1f0390cfbb | ||
|
|
9f06accfb4 | ||
|
|
7a9724496b | ||
|
|
3e931152d1 | ||
|
|
ad1d14775d | ||
|
|
fcf4dff2ce | ||
|
|
56714327f4 | ||
|
|
21f338652d | ||
|
|
8115f0c661 | ||
|
|
87d4cb07b0 | ||
|
|
ee008923f3 | ||
|
|
bbbef0da1c | ||
|
|
1268f8657e | ||
|
|
39e7ff932d | ||
|
|
e92c4fb460 | ||
|
|
4b496066bf | ||
|
|
e63dcd30f3 | ||
|
|
2935d1819e | ||
|
|
86907e2e4a | ||
|
|
31d2d38d7a | ||
|
|
d21477d55a | ||
|
|
80fa6e6c49 | ||
|
|
b99787e95b | ||
|
|
562d299a41 | ||
|
|
9ecb04e35b | ||
|
|
231689c767 | ||
|
|
d70942079a | ||
|
|
0cc55c11ac | ||
|
|
40da77e77c | ||
|
|
0d2de7db65 | ||
|
|
a568f616c9 | ||
|
|
e4957a78ea | ||
|
|
7e35cff4f5 | ||
|
|
82b796ce31 | ||
|
|
6c24062d2f | ||
|
|
3ca09ee3ef | ||
|
|
003f1ce0f4 | ||
|
|
61390dbb49 | ||
|
|
a58c3c53a9 | ||
|
|
75ce8feff5 | ||
|
|
6bee6ee755 | ||
|
|
9739d75892 | ||
|
|
4a976482b1 | ||
|
|
2caac055ef | ||
|
|
2697cb8001 | ||
|
|
dbd709376b | ||
|
|
fc08292136 | ||
|
|
ee594ba55b | ||
|
|
516af86d53 |
@@ -21,15 +21,6 @@ jobs:
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
# Workaround for Pulumi version conflict:
|
||||
# GitHub runners have Pulumi 3.212.0+ pre-installed, which removed the -root flag
|
||||
# from pulumi-language-nodejs (see https://github.com/pulumi/pulumi/pull/21065).
|
||||
# SST 3.17.x uses Pulumi SDK 3.210.0 which still passes -root, causing a conflict.
|
||||
# Removing the system language plugin forces SST to use its bundled compatible version.
|
||||
# TODO: Remove when sst supports Pulumi >3.210.0
|
||||
- name: Fix Pulumi version conflict
|
||||
run: sudo rm -f /usr/local/bin/pulumi-language-nodejs
|
||||
|
||||
- run: bun sst deploy --stage=${{ github.ref_name }}
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
"oxlint-tsgolint": "0.21.0",
|
||||
"prettier": "3.6.2",
|
||||
"semver": "^7.6.0",
|
||||
"sst": "3.18.10",
|
||||
"sst": "4.13.1",
|
||||
"turbo": "2.8.13",
|
||||
},
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -44,6 +44,7 @@
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/media": "2.3.3",
|
||||
"@solid-primitives/resize-observer": "2.1.5",
|
||||
"@solid-primitives/scheduled": "1.5.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solid-primitives/timer": "1.4.4",
|
||||
@@ -84,7 +85,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -119,7 +120,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -146,7 +147,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.64",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -168,7 +169,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -192,7 +193,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -230,7 +231,7 @@
|
||||
"ai-gateway-provider": "3.1.2",
|
||||
"cross-spawn": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"gitlab-ai-provider": "6.6.0",
|
||||
"gitlab-ai-provider": "6.7.0",
|
||||
"glob": "13.0.5",
|
||||
"google-auth-library": "10.5.0",
|
||||
"immer": "11.1.4",
|
||||
@@ -253,7 +254,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"drizzle-orm": "catalog:",
|
||||
@@ -308,7 +309,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "1.15.5",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -322,7 +323,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -352,7 +353,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -368,7 +369,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -381,7 +382,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
"@smithy/util-utf8": "4.2.2",
|
||||
@@ -399,7 +400,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -469,7 +470,7 @@
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "3.1.0",
|
||||
"gitlab-ai-provider": "6.6.0",
|
||||
"gitlab-ai-provider": "6.7.0",
|
||||
"glob": "13.0.5",
|
||||
"google-auth-library": "10.5.0",
|
||||
"gray-matter": "4.0.3",
|
||||
@@ -537,7 +538,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -575,7 +576,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -590,7 +591,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -625,7 +626,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -674,7 +675,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -907,6 +908,8 @@
|
||||
|
||||
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7Ne3Yk/bgQPVebAkv7W+RfhiwTRSbfER9BtbhOa2w/+dIr902LrJf6vrZlxiqaJbGj2ALx8M+ZK1YIHVxSwu9A=="],
|
||||
|
||||
"@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-ryEYNVdilyWkKsOs/7Xy/l7+qjtSz4sll8NpcWD6AtONxjG/5OMaAhxxDkQb4iBoNMKnISxsARzQAp/Wa8pXIg=="],
|
||||
|
||||
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.933.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.933.0", "@aws-sdk/middleware-bucket-endpoint": "3.930.0", "@aws-sdk/middleware-expect-continue": "3.930.0", "@aws-sdk/middleware-flexible-checksums": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-location-constraint": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-sdk-s3": "3.932.0", "@aws-sdk/middleware-ssec": "3.930.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/signature-v4-multi-region": "3.932.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-KxwZvdxdCeWK6o8mpnb+kk7Kgb8V+8AjTwSXUWH1UAD85B0tjdo1cSfE5zoR5fWGol4Ml5RLez12a6LPhsoTqA=="],
|
||||
|
||||
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zwGLSiK48z3PzKpQiDMKP85+fpIrPMF1qQOQW9OW7BGj5AuBZIisT2O4VzIgYJeh+t47MLU7VgBQL7muc+MJDg=="],
|
||||
@@ -977,6 +980,8 @@
|
||||
|
||||
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="],
|
||||
|
||||
"@aws/durable-execution-sdk-js": ["@aws/durable-execution-sdk-js@1.0.2", "", { "dependencies": { "@aws-sdk/client-lambda": "^3.943.0" } }, "sha512-KIYBVqV9gArkWdPEDYOjJqLlO+NecmCXOXadNBlOspJTmqztwuiyb6aZc468bYWSGuL4Me/gyTIK97ubkwFNCw=="],
|
||||
|
||||
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
|
||||
|
||||
"@azure-rest/core-client": ["@azure-rest/core-client@2.6.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-iuFKDm8XPzNxPfRjhyU5/xKZmcRDzSuEghXDHHk4MjBV/wFL34GmYVBZnn9wmuoLBeS1qAw9ceMdaeJBPcB1QQ=="],
|
||||
@@ -1463,6 +1468,8 @@
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
|
||||
|
||||
"@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
@@ -2171,7 +2178,7 @@
|
||||
|
||||
"@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="],
|
||||
|
||||
"@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA=="],
|
||||
"@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.3", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-oNwLE6E6lxJAWrc8QXuwM0k2oU1BnANnkChwMw82aK1j3+mWGJkG1IFe5gCwbV+afYmjI76t9JJV3md/8tLw+g=="],
|
||||
|
||||
"@solid-primitives/scroll": ["@solid-primitives/scroll@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Ejq/Z7zKo/6eIEFr1bFLzXFxiGBCMLuqCM8QB8urr3YdPzjSETFLzYRWUyRiDWaBQN0F7k0SY6S7ig5nWOP7vg=="],
|
||||
|
||||
@@ -2613,8 +2620,6 @@
|
||||
|
||||
"avvio": ["avvio@9.2.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ=="],
|
||||
|
||||
"aws-sdk": ["aws-sdk@2.1692.0", "", { "dependencies": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", "util": "^0.12.4", "uuid": "8.0.0", "xml2js": "0.6.2" } }, "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw=="],
|
||||
|
||||
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
||||
|
||||
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||
@@ -2699,7 +2704,7 @@
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
|
||||
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="],
|
||||
|
||||
@@ -3295,7 +3300,7 @@
|
||||
|
||||
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
||||
|
||||
"gitlab-ai-provider": ["gitlab-ai-provider@6.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-jUxYnKA4XQaPc3wxACDZ8bPDXO0Mzx7cZaBDxbT2uGgLqtGZmSi+9tVNIg7louSS+s/ioVra3SoUz3iOFVhKPA=="],
|
||||
"gitlab-ai-provider": ["gitlab-ai-provider@6.7.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-J7apROAmDwDJcLPcbQ9YQiPhyFN1Vvt/f3ugsePaUnzh3sedfHCHGETgG0Mm0dJCB2rC7DYwmV/sgxLQcQ/sjw=="],
|
||||
|
||||
"glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
|
||||
|
||||
@@ -3437,7 +3442,7 @@
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"ieee754": ["ieee754@1.1.13", "", {}, "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="],
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
@@ -3569,7 +3574,7 @@
|
||||
|
||||
"is64bit": ["is64bit@2.0.0", "", { "dependencies": { "system-architecture": "^0.1.0" } }, "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw=="],
|
||||
|
||||
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
||||
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="],
|
||||
|
||||
@@ -3587,8 +3592,6 @@
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"jmespath": ["jmespath@0.16.0", "", {}, "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="],
|
||||
|
||||
"jose": ["jose@6.0.11", "", {}, "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg=="],
|
||||
|
||||
"js-base64": ["js-base64@3.7.7", "", {}, "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="],
|
||||
@@ -4075,8 +4078,6 @@
|
||||
|
||||
"opencode-poe-auth": ["opencode-poe-auth@0.0.1", "", { "dependencies": { "open": "^10.0.0", "poe-oauth": "*" }, "peerDependencies": { "@opencode-ai/plugin": "*" } }, "sha512-cXqTlS6AXHzo1oBdosnxbT47ZJEZ9WXn050X8Re6wZ1vaNnTpB/l2fMQt90evT7RBK0fB8UjXQUDMKyd7bbiqg=="],
|
||||
|
||||
"opencontrol": ["opencontrol@0.0.6", "", { "dependencies": { "@modelcontextprotocol/sdk": "1.6.1", "@tsconfig/bun": "1.0.7", "hono": "4.7.4", "zod": "3.24.2", "zod-to-json-schema": "3.24.3" }, "bin": { "opencontrol": "bin/index.mjs" } }, "sha512-QeCrpOK5D15QV8kjnGVeD/BHFLwcVr+sn4T6KKmP0WAMs2pww56e4h+eOGHb5iPOufUQXbdbBKi6WV2kk7tefQ=="],
|
||||
|
||||
"openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="],
|
||||
|
||||
"opentui-spinner": ["opentui-spinner@0.0.6", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-xupLOeVQEAXEvVJCvHkfX6fChDWmJIPHe5jyUrVb8+n4XVTX8mBNhitFfB9v2ZbkC1H2UwPab/ElePHoW37NcA=="],
|
||||
@@ -4273,7 +4274,7 @@
|
||||
|
||||
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
|
||||
|
||||
"punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="],
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"pupa": ["pupa@3.3.0", "", { "dependencies": { "escape-goat": "^4.0.0" } }, "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA=="],
|
||||
|
||||
@@ -4283,8 +4284,6 @@
|
||||
|
||||
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
||||
|
||||
"querystring": ["querystring@0.2.0", "", {}, "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||
@@ -4469,7 +4468,7 @@
|
||||
|
||||
"sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="],
|
||||
|
||||
"sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="],
|
||||
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
@@ -4597,23 +4596,23 @@
|
||||
|
||||
"ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="],
|
||||
|
||||
"sst": ["sst@3.18.10", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="],
|
||||
"sst": ["sst@4.13.1", "", { "dependencies": { "@aws/durable-execution-sdk-js": "1.0.2", "aws4fetch": "1.0.18", "jose": "5.2.3", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "4.13.1", "sst-darwin-x64": "4.13.1", "sst-linux-arm64": "4.13.1", "sst-linux-x64": "4.13.1", "sst-linux-x86": "4.13.1", "sst-win32-arm64": "4.13.1", "sst-win32-x64": "4.13.1", "sst-win32-x86": "4.13.1" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-FKzhWuqV5NPhFWH7b+Ktf5+Mvox6DCMc8iOrjaS3mkjnnmyEcOZq7HNx2/ZlIcBwgOZE9sCPR3ot1zXtXkXzZg=="],
|
||||
|
||||
"sst-darwin-arm64": ["sst-darwin-arm64@3.18.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3MwIpMZhhdZKDqLp9ZQNlwkWix5+q+N0PWstuTomYwgZOxCCe6u9IIsoIszSk+GAJJN/jvGZyLiXKeV4iiQvw=="],
|
||||
"sst-darwin-arm64": ["sst-darwin-arm64@4.13.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gGwfgZGOsulU6iGeZJtxP5BpSZFsRUy1dG/yTqJa++n1P1APWXZC64u2qRHm6rdwLAjEeWaCQ9k7ouKgn0zh5Q=="],
|
||||
|
||||
"sst-darwin-x64": ["sst-darwin-x64@3.18.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-nQ0jMKkPOa+kj6Ygz8+kYhBua/vgNTLkd+4r8NSmk7v+Zs78lKnx3T//kEzS0yik6Q6QwGfokwrTcA1Jii2xSw=="],
|
||||
"sst-darwin-x64": ["sst-darwin-x64@4.13.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-RcKLI2oAY+Wk5fWUF0gJFKvwMSpid3qw9L3OcfgFL2ymZH652ybLpQPBxtoJ95FdyknWIMnYf6ZK9C2jnH5eaw=="],
|
||||
|
||||
"sst-linux-arm64": ["sst-linux-arm64@3.18.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-mj9VNj3SvLS+HaXx2PhCX0aTA7CwJNoM6JhRc0s/zCilqchcvqDjbhpYBJO4brEPv6aOaaa7T3WvIQqtYauK4Q=="],
|
||||
"sst-linux-arm64": ["sst-linux-arm64@4.13.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Lp/8BWhH8KZnb50xPd8A6bkZ7QqCH3Dar9l16TPwuAmPxxRUlkmg42h+ACZ9n02vSJl8y/WLnV4JB6SgJpRFVg=="],
|
||||
|
||||
"sst-linux-x64": ["sst-linux-x64@3.18.10", "", { "os": "linux", "cpu": "x64" }, "sha512-7iy1Eq2eqnT9Ag/8OVgC04vRjV7AAQyf/BvzLc+6Sz+GvRiKA8VEuPnbXNYQF+NIvEqsawfcd7MknSTtImpsvQ=="],
|
||||
"sst-linux-x64": ["sst-linux-x64@4.13.1", "", { "os": "linux", "cpu": "x64" }, "sha512-f4eHOsApoP+CpUzLUGzfoTBYsqqr6nl/M0gjYsVNi1T+6v92lc84AhQ4DoDZx033QggyxkqRezj8jOkjkKxa7w=="],
|
||||
|
||||
"sst-linux-x86": ["sst-linux-x86@3.18.10", "", { "os": "linux", "cpu": "none" }, "sha512-77qZSuPZeQ5bdRCiq1pQEdY8EcGNHboKrx4P2yFid2FBDKJsXxOXtIxJdloyx+ljBn0+nxl/g040QBmXxdc9tA=="],
|
||||
"sst-linux-x86": ["sst-linux-x86@4.13.1", "", { "os": "linux", "cpu": "none" }, "sha512-5OHdtPa0GmozDpBPdsD9Tv+SGnpjJSoTXptgK59ivQg3UYNWBBotidvEyUJs1MU9B/3+I+Fmw32zsF2O+ZtPgg=="],
|
||||
|
||||
"sst-win32-arm64": ["sst-win32-arm64@3.18.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-aY+FhMxvYs8crlrKALpLn/kKmud8YQj6LkMHsrOAAIJhfNyxhCja2vrYQaY+bcqdsS5W2LMVcS2hyaMqKXZKcg=="],
|
||||
"sst-win32-arm64": ["sst-win32-arm64@4.13.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-qAUuCQj6wDdZ84a9W4FhnRTHYlEnYRHGYFqO3cmMImbnAQEoKSQR1XuWqt0Ev4RT3kU4Lz/fgvJjGkR7WUstLg=="],
|
||||
|
||||
"sst-win32-x64": ["sst-win32-x64@3.18.10", "", { "os": "win32", "cpu": "x64" }, "sha512-rY+yJXOpG+P5xXnaQRpCvBK2zwwLhjzpYidGkp6F+cGgiVdh2Wre/CIQNRaVHr20ncj8lLe/RsHWa9QCNM48jg=="],
|
||||
"sst-win32-x64": ["sst-win32-x64@4.13.1", "", { "os": "win32", "cpu": "x64" }, "sha512-r+83NMwpe4MLAvkVoOxi1KNs6nIqilFucsTr+VN1PgknsiVBY1emITjSWS2jDJJMuH8xJXL5xJzF/l5rcKWErg=="],
|
||||
|
||||
"sst-win32-x86": ["sst-win32-x86@3.18.10", "", { "os": "win32", "cpu": "none" }, "sha512-pq8SmV0pIjBFMY6DraUZ4akyTxHnfjIKCRbBLdMxFUZK8TzA1NK2YdjRt1AwrgXRYGRyctrz/mt4WyO0SMOVQQ=="],
|
||||
"sst-win32-x86": ["sst-win32-x86@4.13.1", "", { "os": "win32", "cpu": "none" }, "sha512-YPxBVdac/MsrzwlC6pF0NrrvMcmfdBLYjv7MbzHc5jNh1FQ1WPh6bdWQqgv0KD9EQTNLLEkej0beydgUvcCWJg=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
@@ -4893,8 +4892,6 @@
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
|
||||
|
||||
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
||||
|
||||
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
||||
@@ -4903,8 +4900,6 @@
|
||||
|
||||
"utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="],
|
||||
|
||||
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
||||
@@ -5031,6 +5026,8 @@
|
||||
|
||||
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
||||
|
||||
"xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
|
||||
|
||||
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
|
||||
|
||||
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
||||
@@ -5193,6 +5190,20 @@
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/core": ["@aws-sdk/core@3.974.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.24", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.42", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.37", "@aws-sdk/credential-provider-http": "^3.972.39", "@aws-sdk/credential-provider-ini": "^3.972.41", "@aws-sdk/credential-provider-process": "^3.972.37", "@aws-sdk/credential-provider-sso": "^3.972.41", "@aws-sdk/credential-provider-web-identity": "^3.972.41", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@smithy/core": ["@smithy/core@3.24.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="],
|
||||
|
||||
"@aws-sdk/client-sso/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/core": ["@aws-sdk/core@3.775.0", "", { "dependencies": { "@aws-sdk/types": "3.775.0", "@smithy/core": "^3.2.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.0.2", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA=="],
|
||||
@@ -5657,28 +5668,18 @@
|
||||
|
||||
"astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"aws-sdk/events": ["events@1.1.1", "", {}, "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="],
|
||||
|
||||
"aws-sdk/uuid": ["uuid@8.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="],
|
||||
|
||||
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||
|
||||
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
|
||||
"bl/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"body-parser/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="],
|
||||
|
||||
"buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"builder-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"builder-util-runtime/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||
|
||||
"c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
||||
@@ -5735,8 +5736,6 @@
|
||||
|
||||
"engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||
|
||||
"es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
@@ -5839,22 +5838,14 @@
|
||||
|
||||
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
|
||||
"opencode/@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA=="],
|
||||
|
||||
"opencode/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
|
||||
"opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"opencode-poe-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
||||
|
||||
"opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
|
||||
|
||||
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
|
||||
|
||||
"opencontrol/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||
|
||||
"opencontrol/zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="],
|
||||
|
||||
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||
|
||||
"openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
@@ -5901,8 +5892,6 @@
|
||||
|
||||
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
|
||||
|
||||
"restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
@@ -5913,10 +5902,6 @@
|
||||
|
||||
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||
|
||||
"safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
||||
@@ -5929,8 +5914,6 @@
|
||||
|
||||
"shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
|
||||
"sitemap/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||
@@ -5973,8 +5956,6 @@
|
||||
|
||||
"unused-filename/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
|
||||
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
|
||||
"vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
|
||||
@@ -5993,8 +5974,6 @@
|
||||
|
||||
"vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
|
||||
|
||||
"which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
@@ -6003,8 +5982,6 @@
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="],
|
||||
|
||||
"yaml-language-server/yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="],
|
||||
@@ -6069,6 +6046,36 @@
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@smithy/types": "^4.14.0", "@smithy/url-parser": "^4.2.13", "@smithy/util-endpoints": "^3.3.4", "tslib": "^2.6.2" } }, "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.24", "", { "dependencies": { "@nodable/entities": "2.1.0", "@smithy/types": "^4.14.1", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/core/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.37", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.39", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-env": "^3.972.37", "@aws-sdk/credential-provider-http": "^3.972.39", "@aws-sdk/credential-provider-login": "^3.972.41", "@aws-sdk/credential-provider-process": "^3.972.37", "@aws-sdk/credential-provider-sso": "^3.972.41", "@aws-sdk/credential-provider-web-identity": "^3.972.41", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.37", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/types/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@smithy/core/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@smithy/fetch-http-handler/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@smithy/node-http-handler/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww=="],
|
||||
@@ -6239,8 +6246,6 @@
|
||||
|
||||
"@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="],
|
||||
|
||||
"@azure/core-http/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"@azure/core-xml/fast-xml-parser/strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="],
|
||||
|
||||
"@azure/identity/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
@@ -6553,14 +6558,10 @@
|
||||
|
||||
"babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
|
||||
"crc/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
|
||||
@@ -6603,6 +6604,8 @@
|
||||
|
||||
"lazystream/readable-stream/core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||
|
||||
"lazystream/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
||||
|
||||
"lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
|
||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
@@ -6617,18 +6620,6 @@
|
||||
|
||||
"opencode-poe-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||
|
||||
"ora/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"ora/bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
@@ -6641,8 +6632,6 @@
|
||||
|
||||
"pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
||||
|
||||
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
|
||||
|
||||
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
@@ -6761,6 +6750,18 @@
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.9", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.9", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.9", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-5GlJBejo8wqMpSSEKb45WE82YxI2k73YuebjLH/eWDNQeE6VI5Bh9lA1YQ7xNkLLH8hIsb0pSfKVuwh0VEzVrg=="],
|
||||
@@ -6957,30 +6958,6 @@
|
||||
|
||||
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"ora/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
|
||||
@@ -7003,6 +6980,16 @@
|
||||
|
||||
"@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||
@@ -7077,8 +7064,6 @@
|
||||
|
||||
"js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
@@ -7087,6 +7072,12 @@
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="],
|
||||
|
||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="],
|
||||
|
||||
+11
-11
@@ -4,7 +4,7 @@ import { domain } from "./stage"
|
||||
const description = "Managed by SST (Don't edit in Honeycomb UI)"
|
||||
const alertsDisabled = $app.stage !== "production"
|
||||
|
||||
const webhookRecipient = new honeycomb.WebhookRecipient("DiscordAlerts", {
|
||||
const webhookRecipient = new honeycombio.WebhookRecipient("DiscordAlerts", {
|
||||
name: $app.stage === "production" ? "Discord Alerts" : `Discord Alerts (${$app.stage})`,
|
||||
url: `https://${domain}/honeycomb/webhook`,
|
||||
secret: SECRET.HoneycombWebhookSecret.result,
|
||||
@@ -67,7 +67,7 @@ IF(
|
||||
)`,
|
||||
})
|
||||
|
||||
return honeycomb.getQuerySpecificationOutput({
|
||||
return honeycombio.getQuerySpecificationOutput({
|
||||
breakdowns: ["model"],
|
||||
calculatedFields: [failedHttpStatus],
|
||||
calculations: [
|
||||
@@ -99,7 +99,7 @@ const providerHttpErrorsQuery = () => {
|
||||
expression: `IF(GT($llm.error.code, "400"), 1, 0)`,
|
||||
})
|
||||
|
||||
return honeycomb.getQuerySpecificationOutput({
|
||||
return honeycombio.getQuerySpecificationOutput({
|
||||
breakdowns: ["provider"],
|
||||
calculatedFields: [successHttpStatus, failedProviderHttpStatus],
|
||||
calculations: [
|
||||
@@ -140,7 +140,7 @@ const modelLowTpsQuery = (product: "go" | "zen") => {
|
||||
{ column: "tps.output", op: "exists" },
|
||||
]
|
||||
|
||||
return honeycomb.getQuerySpecificationOutput({
|
||||
return honeycombio.getQuerySpecificationOutput({
|
||||
breakdowns: ["model"],
|
||||
calculations: [
|
||||
{ op: "COUNT", name: "TOTAL", filterCombination: "AND", filters },
|
||||
@@ -157,7 +157,7 @@ const modelLowTpsQuery = (product: "go" | "zen") => {
|
||||
}).json
|
||||
}
|
||||
|
||||
new honeycomb.Trigger("IncreasedModelHttpErrorsGo", {
|
||||
new honeycombio.Trigger("IncreasedModelHttpErrorsGo", {
|
||||
name: "Increased Model HTTP Errors [Go]",
|
||||
description,
|
||||
disabled: alertsDisabled,
|
||||
@@ -177,7 +177,7 @@ new honeycomb.Trigger("IncreasedModelHttpErrorsGo", {
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("IncreasedModelHttpErrorsZen", {
|
||||
new honeycombio.Trigger("IncreasedModelHttpErrorsZen", {
|
||||
name: "Increased Model HTTP Errors [Zen]",
|
||||
description,
|
||||
disabled: alertsDisabled,
|
||||
@@ -197,7 +197,7 @@ new honeycomb.Trigger("IncreasedModelHttpErrorsZen", {
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("LowModelTpsGo", {
|
||||
new honeycombio.Trigger("LowModelTpsGo", {
|
||||
name: "Low Model TPS [Go]",
|
||||
description,
|
||||
disabled: alertsDisabled,
|
||||
@@ -217,7 +217,7 @@ new honeycomb.Trigger("LowModelTpsGo", {
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("LowModelTpsZen", {
|
||||
new honeycombio.Trigger("LowModelTpsZen", {
|
||||
name: "Low Model TPS [Zen]",
|
||||
description,
|
||||
disabled: alertsDisabled,
|
||||
@@ -237,7 +237,7 @@ new honeycomb.Trigger("LowModelTpsZen", {
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("IncreasedProviderHttpErrors", {
|
||||
new honeycombio.Trigger("IncreasedProviderHttpErrors", {
|
||||
name: "Increased Provider HTTP Errors",
|
||||
description,
|
||||
disabled: alertsDisabled,
|
||||
@@ -257,11 +257,11 @@ new honeycomb.Trigger("IncreasedProviderHttpErrors", {
|
||||
],
|
||||
})
|
||||
|
||||
new honeycomb.Trigger("IncreasedFreeTierRequests", {
|
||||
new honeycombio.Trigger("IncreasedFreeTierRequests", {
|
||||
name: "Increased Free Tier Requests",
|
||||
description,
|
||||
disabled: alertsDisabled,
|
||||
queryJson: honeycomb.getQuerySpecificationOutput({
|
||||
queryJson: honeycombio.getQuerySpecificationOutput({
|
||||
calculations: [{ op: "COUNT" }],
|
||||
filters: [
|
||||
{ column: "event_type", op: "=", value: "completions" },
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-hIarzU3QNIvkwpfnearfsGaBMCSdovkOWAuvX+EBQI8=",
|
||||
"aarch64-linux": "sha256-qu+86RNsBZhqzE3CEyg4XHD/3Czc/BqHhY3O1vyogtY=",
|
||||
"aarch64-darwin": "sha256-7sXZdPh/R4fQBBMq36YdpQE6G2ELMZd08BqMhHbhwv4=",
|
||||
"x86_64-darwin": "sha256-g5QWpX33R143sNR1G9m7UsSA3jygFyJjT7Kjyjo6sCc="
|
||||
"x86_64-linux": "sha256-1RiaZQHzIhdtcOJUMsLagpP+nBBL/Qu6zQgrAXMHDCI=",
|
||||
"aarch64-linux": "sha256-5QZhtkWuNpY/qUxlKRHcGbILOAVnuyzu+h8VDIuMQcU=",
|
||||
"aarch64-darwin": "sha256-9w3QA22XNc1itGLyhikYU90xGH/iLUUM+SSGXla7lhw=",
|
||||
"x86_64-darwin": "sha256-cSYiyhhSqIYiTeK1uWHDkHbYstQYD5jEB7JaYjWjgi4="
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@
|
||||
"oxlint-tsgolint": "0.21.0",
|
||||
"prettier": "3.6.2",
|
||||
"semver": "^7.6.0",
|
||||
"sst": "3.18.10",
|
||||
"sst": "4.13.1",
|
||||
"turbo": "2.8.13"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -42,10 +42,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/active-element": "2.1.3",
|
||||
"@solid-primitives/audio": "1.4.2",
|
||||
@@ -54,6 +54,7 @@
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/media": "2.3.3",
|
||||
"@solid-primitives/resize-observer": "2.1.5",
|
||||
"@solid-primitives/scheduled": "1.5.3",
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solid-primitives/timer": "1.4.4",
|
||||
|
||||
@@ -48,22 +48,17 @@ import { ErrorPage } from "./pages/error"
|
||||
import { useCheckServerHealth } from "./utils/server-health"
|
||||
|
||||
const HomeRoute = lazy(() => import("@/pages/home"))
|
||||
const loadSession = () => import("@/pages/session")
|
||||
const Session = lazy(loadSession)
|
||||
const Loading = () => <div class="size-full" />
|
||||
const Session = lazy(() => import("@/pages/session"))
|
||||
|
||||
if (typeof location === "object" && /\/session(?:\/|$)/.test(location.pathname)) {
|
||||
void loadSession()
|
||||
}
|
||||
|
||||
const SessionRoute = () => (
|
||||
<SessionProviders>
|
||||
<Session />
|
||||
</SessionProviders>
|
||||
const SessionRoute = Object.assign(
|
||||
() => (
|
||||
<SessionProviders>
|
||||
<Session />
|
||||
</SessionProviders>
|
||||
),
|
||||
{ preload: Session.preload },
|
||||
)
|
||||
|
||||
const SessionIndexRoute = () => <Navigate href="session" />
|
||||
|
||||
function UiI18nBridge(props: ParentProps) {
|
||||
const language = useLanguage()
|
||||
return <I18nProvider value={{ locale: language.intl, t: language.t }}>{props.children}</I18nProvider>
|
||||
@@ -317,7 +312,7 @@ export function AppInterface(props: {
|
||||
>
|
||||
<Route path="/" component={HomeRoute} />
|
||||
<Route path="/:dir" component={DirectoryLayout}>
|
||||
<Route path="/" component={SessionIndexRoute} />
|
||||
<Route path="/" component={() => <Navigate href="session" />} />
|
||||
<Route path="/session/:id?" component={SessionRoute} />
|
||||
</Route>
|
||||
</Dynamic>
|
||||
|
||||
@@ -41,9 +41,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
})
|
||||
|
||||
const provider = createMemo(
|
||||
() =>
|
||||
providers.all().find((x) => x.id === props.provider) ??
|
||||
globalSync.data.provider.all.find((x) => x.id === props.provider)!,
|
||||
() => providers.all().get(props.provider) ?? globalSync.data.provider.all.get(props.provider)!,
|
||||
)
|
||||
const fallback = createMemo<ProviderAuthMethod[]>(() => [
|
||||
{
|
||||
@@ -526,7 +524,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
const code = createMemo(() => {
|
||||
const instructions = store.authorization?.instructions
|
||||
if (instructions?.includes(":")) {
|
||||
return instructions.split(":")[1]?.trim()
|
||||
return instructions.split(":").pop()?.trim()
|
||||
}
|
||||
return instructions
|
||||
})
|
||||
|
||||
@@ -106,7 +106,7 @@ export function DialogCustomProvider(props: Props) {
|
||||
form,
|
||||
t: language.t,
|
||||
disabledProviders: globalSync.data.config.disabled_providers ?? [],
|
||||
existingProviderIDs: new Set(globalSync.data.provider.all.map((p) => p.id)),
|
||||
existingProviderIDs: new Set(globalSync.data.provider.all.keys()),
|
||||
})
|
||||
batch(() => {
|
||||
setForm("err", output.err)
|
||||
|
||||
@@ -91,7 +91,7 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
|
||||
<div class="w-full">
|
||||
<List
|
||||
class="w-full px-0"
|
||||
key={(x) => x?.id}
|
||||
key={(p) => p.id}
|
||||
items={providers.popular}
|
||||
activeIcon="plus-small"
|
||||
sortBy={(a, b) => {
|
||||
|
||||
@@ -35,7 +35,7 @@ export const DialogSelectProvider: Component = () => {
|
||||
key={(x) => x?.id}
|
||||
items={() => {
|
||||
language.locale()
|
||||
return [{ id: CUSTOM_ID, name: customLabel() }, ...providers.all()]
|
||||
return [{ id: CUSTOM_ID, name: customLabel() }, ...providers.all().values()]
|
||||
}}
|
||||
filterKeys={["id", "name"]}
|
||||
groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())}
|
||||
|
||||
@@ -52,7 +52,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||
}),
|
||||
)
|
||||
|
||||
const metrics = createMemo(() => getSessionContextMetrics(messages(), providers.all()))
|
||||
const metrics = createMemo(() => getSessionContextMetrics(messages(), [...providers.all().values()]))
|
||||
const context = createMemo(() => metrics().context)
|
||||
const cost = createMemo(() => {
|
||||
return usd().format(metrics().totalCost)
|
||||
|
||||
@@ -132,7 +132,7 @@ export function SessionContextTab() {
|
||||
}),
|
||||
)
|
||||
|
||||
const metrics = createMemo(() => getSessionContextMetrics(messages(), providers.all()))
|
||||
const metrics = createMemo(() => getSessionContextMetrics(messages(), [...providers.all().values()]))
|
||||
const ctx = createMemo(() => metrics().context)
|
||||
const formatter = createMemo(() => createSessionContextFormatter(language.intl()))
|
||||
|
||||
|
||||
@@ -193,6 +193,12 @@ export const SettingsGeneral: Component = () => {
|
||||
{ initialValue: null as DisplayBackend | null },
|
||||
)
|
||||
|
||||
const [pinchZoom, { mutate: setPinchZoom }] = createResource(
|
||||
() => (desktop() && platform.getPinchZoomEnabled ? true : false),
|
||||
() => Promise.resolve(platform.getPinchZoomEnabled?.() ?? false).catch(() => false),
|
||||
{ initialValue: false },
|
||||
)
|
||||
|
||||
onMount(() => {
|
||||
void theme.loadThemes()
|
||||
})
|
||||
@@ -239,6 +245,13 @@ export const SettingsGeneral: Component = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const onPinchZoomChange = (checked: boolean) => {
|
||||
setPinchZoom(checked)
|
||||
const update = platform.setPinchZoomEnabled?.(checked)
|
||||
if (!update) return
|
||||
void update.catch(() => setPinchZoom(!checked))
|
||||
}
|
||||
|
||||
const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [
|
||||
{ value: "system", label: language.t("theme.scheme.system") },
|
||||
{ value: "light", label: language.t("theme.scheme.light") },
|
||||
@@ -729,6 +742,45 @@ export const SettingsGeneral: Component = () => {
|
||||
</div>
|
||||
)
|
||||
|
||||
const DisplaySection = () => (
|
||||
<Show when={desktop()}>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.display")}</h3>
|
||||
|
||||
<SettingsList>
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.pinchZoom.title")}
|
||||
description={language.t("settings.general.row.pinchZoom.description")}
|
||||
>
|
||||
<div data-action="settings-pinch-zoom">
|
||||
<Switch checked={pinchZoom.latest} onChange={onPinchZoomChange} />
|
||||
</div>
|
||||
</SettingsRow>
|
||||
|
||||
<Show when={linux()}>
|
||||
<SettingsRow
|
||||
title={
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{language.t("settings.general.row.wayland.title")}</span>
|
||||
<Tooltip value={language.t("settings.general.row.wayland.tooltip")} placement="top">
|
||||
<span class="text-text-weak">
|
||||
<Icon name="help" size="small" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
description={language.t("settings.general.row.wayland.description")}
|
||||
>
|
||||
<div data-action="settings-wayland">
|
||||
<Switch checked={displayBackend.latest === "wayland"} onChange={onDisplayBackendChange} />
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</Show>
|
||||
</SettingsList>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
|
||||
console.log(import.meta.env)
|
||||
return (
|
||||
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
|
||||
@@ -749,31 +801,7 @@ export const SettingsGeneral: Component = () => {
|
||||
|
||||
<UpdatesSection />
|
||||
|
||||
<Show when={linux()}>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.display")}</h3>
|
||||
|
||||
<SettingsList>
|
||||
<SettingsRow
|
||||
title={
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{language.t("settings.general.row.wayland.title")}</span>
|
||||
<Tooltip value={language.t("settings.general.row.wayland.tooltip")} placement="top">
|
||||
<span class="text-text-weak">
|
||||
<Icon name="help" size="small" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
description={language.t("settings.general.row.wayland.description")}
|
||||
>
|
||||
<div data-action="settings-wayland">
|
||||
<Switch checked={displayBackend.latest === "wayland"} onChange={onDisplayBackendChange} />
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</SettingsList>
|
||||
</div>
|
||||
</Show>
|
||||
<DisplaySection />
|
||||
|
||||
<Show when={desktop() && import.meta.env.VITE_OPENCODE_CHANNEL === "beta"}>
|
||||
<AdvancedSection />
|
||||
|
||||
@@ -22,6 +22,9 @@ import { iife } from "@opencode-ai/core/util/iife"
|
||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||
import { Avatar as AvatarV2 } from "@opencode-ai/ui/v2/components/avatar-v2.jsx"
|
||||
import { displayName, getProjectAvatarSource, projectForSession } from "@/pages/layout/helpers"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { StatusPopover } from "./status-popover"
|
||||
import { SDKProvider } from "@/context/sdk"
|
||||
|
||||
type TauriDesktopWindow = {
|
||||
startDragging?: () => Promise<void>
|
||||
@@ -222,19 +225,13 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
const navigate = useNavigate()
|
||||
const homeMatch = useMatch(() => "/")
|
||||
|
||||
const openNewSession = () => {
|
||||
if (params.dir) {
|
||||
navigate(`/${params.dir}/session`)
|
||||
return
|
||||
}
|
||||
const newSessionHref = () => {
|
||||
if (params.dir) return `/${params.dir}/session`
|
||||
|
||||
const project = layout.projects.list()[0]
|
||||
if (!project) {
|
||||
navigate("/")
|
||||
return
|
||||
}
|
||||
if (!project) return "/"
|
||||
|
||||
navigate(`/${base64Encode(project.worktree)}/session`)
|
||||
return `/${base64Encode(project.worktree)}/session`
|
||||
}
|
||||
|
||||
type Tab = { dir: string; sessionId: string; href: string }
|
||||
@@ -298,6 +295,98 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
() => new Map(projects().flatMap((project) => (project.id ? [[project.id, project] as const] : []))),
|
||||
)
|
||||
|
||||
const currentSessionTab = () => {
|
||||
if (!params.dir || !params.id) return
|
||||
const href = makeSessionHref(params.dir, params.id)
|
||||
return tabsStore.find((tab) => tab.href === href)
|
||||
}
|
||||
|
||||
const closeCurrentSessionTab = () => {
|
||||
const tab = currentSessionTab()
|
||||
if (!tab) return false
|
||||
tabsStoreActions.removeTab(tab.href)
|
||||
return true
|
||||
}
|
||||
|
||||
const closeNewSessionTab = () => {
|
||||
if (!(params.dir && !params.id)) return false
|
||||
const last = tabsStore[tabsStore.length - 1]
|
||||
if (last) navigate(last.href)
|
||||
else navigate("/")
|
||||
return true
|
||||
}
|
||||
|
||||
makeEventListener(
|
||||
document,
|
||||
"keydown",
|
||||
(event) => {
|
||||
if (!event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return
|
||||
if (event.key.toLowerCase() !== "w") return
|
||||
if (!(closeCurrentSessionTab() || closeNewSessionTab())) return
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
},
|
||||
{ capture: true },
|
||||
)
|
||||
|
||||
command.register(() => {
|
||||
const commands = [
|
||||
{
|
||||
id: `tab.prev`,
|
||||
category: "tab",
|
||||
title: "",
|
||||
keybind: `mod+option+ArrowLeft`,
|
||||
hidden: true,
|
||||
onSelect: () => {
|
||||
let index = tabsStore.findIndex((tab) => tab.href === currentSessionTab()?.href)
|
||||
if (index === -1) return
|
||||
|
||||
index -= 1
|
||||
if (index === -1) index = tabsStore.length - 1
|
||||
|
||||
const next = tabsStore[index]
|
||||
if (next) navigate(next.href)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `tab.next`,
|
||||
category: "tab",
|
||||
title: "",
|
||||
keybind: `mod+option+ArrowRight`,
|
||||
hidden: true,
|
||||
onSelect: () => {
|
||||
let index = tabsStore.findIndex((tab) => tab.href === currentSessionTab()?.href)
|
||||
if (index === -1) return
|
||||
|
||||
index += 1
|
||||
if (index === tabsStore.length) index = 0
|
||||
|
||||
const next = tabsStore[index]
|
||||
if (next) navigate(next.href)
|
||||
},
|
||||
},
|
||||
...Array.from({ length: 9 }, (_, i) => {
|
||||
const index = i
|
||||
const number = index + 1
|
||||
return {
|
||||
id: `tab.${number}`,
|
||||
category: "tab",
|
||||
title: "",
|
||||
keybind: `mod+${number}`,
|
||||
disabled: layout.projects.list().length <= index,
|
||||
hidden: true,
|
||||
onSelect: () => {
|
||||
const tab = tabsStore[index]
|
||||
if (tab) navigate(tab.href)
|
||||
},
|
||||
}
|
||||
}),
|
||||
]
|
||||
|
||||
return commands
|
||||
})
|
||||
|
||||
const tabsEnriched = iife(() => {
|
||||
const base = mapArray(
|
||||
() => tabsStore,
|
||||
@@ -362,7 +451,8 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
size="large"
|
||||
class="shrink-0"
|
||||
icon={<IconV2 name="plus" />}
|
||||
onClick={openNewSession}
|
||||
as="a"
|
||||
href={newSessionHref()}
|
||||
aria-label={language.t("command.session.new")}
|
||||
/>
|
||||
}
|
||||
@@ -375,6 +465,15 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
</Show>
|
||||
<div class="min-w-0 flex-1" />
|
||||
</div>
|
||||
<Show when={currentSessionTab()?.dir} keyed>
|
||||
{(dir) => (
|
||||
<SDKProvider directory={dir}>
|
||||
<Tooltip placement="bottom" value={language.t("status.popover.trigger")}>
|
||||
<StatusPopover />
|
||||
</Tooltip>
|
||||
</SDKProvider>
|
||||
)}
|
||||
</Show>
|
||||
<TitlebarUpdatePill update={props.update} />
|
||||
<Show when={windows() && !electronWindows()}>
|
||||
<div data-tauri-decorum-tb class="flex flex-row" />
|
||||
@@ -578,15 +677,15 @@ function TabNavItem(props: {
|
||||
const isActive = () => !!match()
|
||||
return (
|
||||
<div
|
||||
class="group relative flex h-7 min-w-24 max-w-60 flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] bg-[var(--tab-bg)] pl-1.5 pr-8 [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
||||
class="group relative flex h-7 min-w-24 max-w-60 flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] bg-[var(--tab-bg)] pl-1.5 [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
||||
data-active={isActive()}
|
||||
>
|
||||
<a
|
||||
href={props.href}
|
||||
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 overflow-hidden text-[13px] font-medium leading-none tracking-[-0.04px] text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base"
|
||||
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 overflow-hidden text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base"
|
||||
>
|
||||
<ProjectTabAvatar project={props.project} directory={props.directory} />
|
||||
<span class="truncate">{props.title}</span>
|
||||
<span class="text-clip">{props.title}</span>
|
||||
</a>
|
||||
|
||||
<div class="absolute right-0 inset-y-0 flex flex-row items-center pr-1 py-1 w-8 pl-2">
|
||||
@@ -624,7 +723,7 @@ function ProjectTabAvatar(props: { project?: LocalProject; directory: string })
|
||||
|
||||
function NewSessionTabItem(props: { href: string; title: string; onClose: () => void }) {
|
||||
return (
|
||||
<div class="group relative flex h-7 w-[135px] min-w-24 max-w-60 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--v2-overlay-simple-overlay-pressed)] pl-1.5 pr-8 whitespace-nowrap focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-[var(--v2-border-border-focus)]">
|
||||
<div class="group relative flex h-7 max-w-60 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--v2-overlay-simple-overlay-pressed)] pl-1.5 pr-8 whitespace-nowrap focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-[var(--v2-border-border-focus)]">
|
||||
<a
|
||||
href={props.href}
|
||||
aria-current="page"
|
||||
|
||||
@@ -232,6 +232,9 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
||||
throwOnError: true,
|
||||
})
|
||||
|
||||
const dirSyncContexts = new Map<string, ReturnType<typeof createDirSdkContext>>()
|
||||
const dirSdkContextRefCounts = new Map<string, number>()
|
||||
|
||||
return {
|
||||
url: currentServer.http.url,
|
||||
client: sdk,
|
||||
@@ -249,6 +252,58 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
||||
...opts,
|
||||
})
|
||||
},
|
||||
createDirSyncContext: (directory: string) => {
|
||||
onCleanup(() => {
|
||||
dirSdkContextRefCounts.set(directory, (dirSdkContextRefCounts.get(directory) ?? 0) - 1)
|
||||
if (dirSdkContextRefCounts.get(directory) === 0) {
|
||||
dirSyncContexts.delete(directory)
|
||||
dirSdkContextRefCounts.delete(directory)
|
||||
}
|
||||
})
|
||||
|
||||
const cached = dirSyncContexts.get(directory)
|
||||
if (cached) {
|
||||
dirSdkContextRefCounts.set(directory, (dirSdkContextRefCounts.get(directory) ?? 0) + 1)
|
||||
return cached
|
||||
}
|
||||
const ctx = createDirSdkContext(directory)
|
||||
dirSyncContexts.set(directory, ctx)
|
||||
dirSdkContextRefCounts.set(directory, 1)
|
||||
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
type SDKEventMap = {
|
||||
[key in Event["type"]]: Extract<Event, { type: key }>
|
||||
}
|
||||
|
||||
function createDirSdkContext(directory: string) {
|
||||
const globalSDK = useGlobalSDK()
|
||||
|
||||
const client = globalSDK.createClient({
|
||||
directory,
|
||||
throwOnError: true,
|
||||
})
|
||||
|
||||
const emitter = createGlobalEmitter<SDKEventMap>()
|
||||
|
||||
const unsub = globalSDK.event.on(directory, (event) => {
|
||||
emitter.emit(event.type, event)
|
||||
})
|
||||
onCleanup(unsub)
|
||||
|
||||
return {
|
||||
directory,
|
||||
client,
|
||||
event: emitter,
|
||||
get url() {
|
||||
return globalSDK.url
|
||||
},
|
||||
createClient(opts: Parameters<typeof globalSDK.createClient>[0]) {
|
||||
return globalSDK.createClient(opts)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import type {
|
||||
Config,
|
||||
OpencodeClient,
|
||||
Path,
|
||||
Project,
|
||||
ProviderAuthResponse,
|
||||
ProviderListResponse,
|
||||
Todo,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import type { Config, OpencodeClient, Path, Project, ProviderAuthResponse, Todo } from "@opencode-ai/sdk/v2/client"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js"
|
||||
@@ -37,6 +29,7 @@ import { createRefreshQueue } from "./global-sync/queue"
|
||||
import { directoryKey } from "./global-sync/utils"
|
||||
import { PathKey } from "@/utils/path-key"
|
||||
import { createDirSyncContext } from "./directory-sync"
|
||||
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||
|
||||
type GlobalStore = {
|
||||
ready: boolean
|
||||
@@ -46,7 +39,7 @@ type GlobalStore = {
|
||||
session_todo: {
|
||||
[sessionID: string]: Todo[]
|
||||
}
|
||||
provider: ProviderListResponse
|
||||
provider: NormalizedProviderListResponse
|
||||
provider_auth: ProviderAuthResponse
|
||||
config: Config
|
||||
reload: undefined | "pending" | "complete"
|
||||
@@ -121,7 +114,7 @@ function createGlobalSync() {
|
||||
return pathQuery.data ?? EMPTY
|
||||
},
|
||||
get provider() {
|
||||
const EMPTY = { all: [], connected: [], default: {} }
|
||||
const EMPTY = { all: new Map(), connected: [], default: {} }
|
||||
if (providerQuery.isLoading) return EMPTY
|
||||
return providerQuery.data ?? EMPTY
|
||||
},
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
PermissionRequest,
|
||||
Project,
|
||||
ProviderAuthResponse,
|
||||
ProviderListResponse,
|
||||
QuestionRequest,
|
||||
Session,
|
||||
Todo,
|
||||
@@ -20,6 +19,7 @@ import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
import { QueryClient, queryOptions } from "@tanstack/solid-query"
|
||||
import { loadMcpQuery } from "../global-sync"
|
||||
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||
|
||||
type GlobalStore = {
|
||||
ready: boolean
|
||||
@@ -28,7 +28,7 @@ type GlobalStore = {
|
||||
session_todo: {
|
||||
[sessionID: string]: Todo[]
|
||||
}
|
||||
provider: ProviderListResponse
|
||||
provider: NormalizedProviderListResponse
|
||||
provider_auth: ProviderAuthResponse
|
||||
config: Config
|
||||
reload: undefined | "pending" | "complete"
|
||||
@@ -208,7 +208,7 @@ export async function bootstrapDirectory(input: {
|
||||
config: Config
|
||||
path: Path
|
||||
project: Project[]
|
||||
provider: ProviderListResponse
|
||||
provider: NormalizedProviderListResponse
|
||||
}
|
||||
queryClient: QueryClient
|
||||
}) {
|
||||
@@ -220,7 +220,6 @@ export async function bootstrapDirectory(input: {
|
||||
if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
|
||||
input.setStore("config", reconcile(input.global.config, { merge: false }))
|
||||
}
|
||||
if (loading) input.setStore("status", "partial")
|
||||
|
||||
const rev = (providerRev.get(input.directory) ?? 0) + 1
|
||||
providerRev.set(input.directory, rev)
|
||||
@@ -327,7 +326,5 @@ export async function bootstrapDirectory(input: {
|
||||
description: formatServerError(slowErrs[0], input.translate),
|
||||
})
|
||||
}
|
||||
|
||||
if (loading && slowErrs.length === 0) input.setStore("status", "complete")
|
||||
})()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
|
||||
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import type { ProviderListResponse, VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
import type { VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
import {
|
||||
DIR_IDLE_TTL_MS,
|
||||
MAX_DIR_STORES,
|
||||
@@ -17,6 +17,7 @@ import { canDisposeDirectory, pickDirectoriesToEvict } from "./eviction"
|
||||
import { useQueries } from "@tanstack/solid-query"
|
||||
import { QueryOptionsApi } from "../global-sync"
|
||||
import { directoryKey, type DirectoryKey } from "./utils"
|
||||
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||
|
||||
export function createChildStoreManager(input: {
|
||||
owner: Owner
|
||||
@@ -27,7 +28,7 @@ export function createChildStoreManager(input: {
|
||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||
queryOptions: QueryOptionsApi
|
||||
global: {
|
||||
provider: ProviderListResponse
|
||||
provider: NormalizedProviderListResponse
|
||||
}
|
||||
}) {
|
||||
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
||||
@@ -190,10 +191,9 @@ export function createChildStoreManager(input: {
|
||||
return !providerQuery.isLoading
|
||||
},
|
||||
get provider() {
|
||||
const EMPTY = { all: [], connected: [], default: {} }
|
||||
const EMPTY = { all: new Map(), connected: [], default: {} }
|
||||
if (providerQuery.isLoading) return EMPTY
|
||||
if (providerQuery.data?.all.length === 0 && input.global.provider.all.length > 0)
|
||||
return input.global.provider
|
||||
if (providerQuery.data?.all.size === 0 && input.global.provider.all.size > 0) return input.global.provider
|
||||
return providerQuery.data ?? EMPTY
|
||||
},
|
||||
config: {},
|
||||
@@ -202,7 +202,7 @@ export function createChildStoreManager(input: {
|
||||
return { state: "", config: "", worktree: "", directory: "", home: "" }
|
||||
return pathQuery.data
|
||||
},
|
||||
status: "loading" as const,
|
||||
status: "complete" as const,
|
||||
agent: [],
|
||||
command: [],
|
||||
session: [],
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
Part,
|
||||
Path,
|
||||
PermissionRequest,
|
||||
ProviderListResponse,
|
||||
QuestionRequest,
|
||||
Session,
|
||||
SessionStatus,
|
||||
@@ -16,6 +15,7 @@ import type {
|
||||
Todo,
|
||||
VcsInfo,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||
import type { Accessor } from "solid-js"
|
||||
import type { SetStoreFunction, Store } from "solid-js/store"
|
||||
|
||||
@@ -38,7 +38,7 @@ export type State = {
|
||||
projectMeta: ProjectMeta | undefined
|
||||
icon: string | undefined
|
||||
provider_ready: boolean
|
||||
provider: ProviderListResponse
|
||||
provider: NormalizedProviderListResponse
|
||||
config: Config
|
||||
path: Path
|
||||
session: Session[]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Agent, Project, ProviderListResponse } from "@opencode-ai/sdk/v2/client"
|
||||
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||
export { pathKey as directoryKey, type PathKey as DirectoryKey } from "@/utils/path-key"
|
||||
|
||||
export const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
||||
@@ -17,13 +18,23 @@ export function normalizeAgentList(input: unknown): Agent[] {
|
||||
return Object.values(input).filter(isAgent)
|
||||
}
|
||||
|
||||
export function normalizeProviderList(input: ProviderListResponse): ProviderListResponse {
|
||||
export function normalizeProviderList(input: ProviderListResponse): NormalizedProviderListResponse {
|
||||
return {
|
||||
...input,
|
||||
all: input.all.map((provider) => ({
|
||||
...provider,
|
||||
models: Object.fromEntries(Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated")),
|
||||
})),
|
||||
all: new Map(
|
||||
input.all.map(
|
||||
(provider) =>
|
||||
[
|
||||
provider.id,
|
||||
{
|
||||
...provider,
|
||||
models: Object.fromEntries(
|
||||
Object.entries(provider.models).filter(([, info]) => info.status !== "deprecated"),
|
||||
),
|
||||
},
|
||||
] as const,
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
})
|
||||
|
||||
const validModel = (model: ModelKey) => {
|
||||
const provider = providers.all().find((item) => item.id === model.providerID)
|
||||
const provider = providers.all().get(model.providerID)
|
||||
return !!provider?.models[model.modelID] && connected().has(model.providerID)
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,12 @@ export type Platform = {
|
||||
/** Webview zoom level (desktop only) */
|
||||
webviewZoom?: Accessor<number>
|
||||
|
||||
/** Get whether native pinch/Ctrl-scroll zoom gestures are enabled (desktop only) */
|
||||
getPinchZoomEnabled?(): Promise<boolean> | boolean
|
||||
|
||||
/** Allow native pinch/Ctrl-scroll zoom gestures (desktop only) */
|
||||
setPinchZoomEnabled?(enabled: boolean): Promise<void> | void
|
||||
|
||||
/** Run a desktop-only menu action from the app chrome */
|
||||
runDesktopMenuAction?(action: DesktopMenuAction): Promise<void> | void
|
||||
|
||||
|
||||
@@ -1,49 +1,11 @@
|
||||
import type { Event } from "@opencode-ai/sdk/v2/client"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||
import { type Accessor, createEffect, createMemo, onCleanup } from "solid-js"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
|
||||
type SDKEventMap = {
|
||||
[key in Event["type"]]: Extract<Event, { type: key }>
|
||||
}
|
||||
|
||||
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||
name: "SDK",
|
||||
init: (props: { directory: Accessor<string> }) => {
|
||||
init: (props: { directory: string }) => {
|
||||
const globalSDK = useGlobalSDK()
|
||||
|
||||
const directory = createMemo(props.directory)
|
||||
const client = createMemo(() =>
|
||||
globalSDK.createClient({
|
||||
directory: directory(),
|
||||
throwOnError: true,
|
||||
}),
|
||||
)
|
||||
|
||||
const emitter = createGlobalEmitter<SDKEventMap>()
|
||||
|
||||
createEffect(() => {
|
||||
const unsub = globalSDK.event.on(directory(), (event) => {
|
||||
emitter.emit(event.type, event)
|
||||
})
|
||||
onCleanup(unsub)
|
||||
})
|
||||
|
||||
return {
|
||||
get directory() {
|
||||
return directory()
|
||||
},
|
||||
get client() {
|
||||
return client()
|
||||
},
|
||||
event: emitter,
|
||||
get url() {
|
||||
return globalSDK.url
|
||||
},
|
||||
createClient(opts: Parameters<typeof globalSDK.createClient>[0]) {
|
||||
return globalSDK.createClient(opts)
|
||||
},
|
||||
}
|
||||
return globalSDK.createDirSyncContext(props.directory)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Binary } from "@opencode-ai/core/util/binary"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
import { useSDK } from "./sdk"
|
||||
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
||||
@@ -10,26 +9,8 @@ function sortParts(parts: Part[]) {
|
||||
return parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id))
|
||||
}
|
||||
|
||||
function runInflight(map: Map<string, Promise<void>>, key: string, task: () => Promise<void>) {
|
||||
const pending = map.get(key)
|
||||
if (pending) return pending
|
||||
const promise = task().finally(() => {
|
||||
map.delete(key)
|
||||
})
|
||||
map.set(key, promise)
|
||||
return promise
|
||||
}
|
||||
|
||||
const keyFor = (directory: string, id: string) => `${directory}\n${id}`
|
||||
|
||||
const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
||||
|
||||
function merge<T extends { id: string }>(a: readonly T[], b: readonly T[]) {
|
||||
const map = new Map(a.map((item) => [item.id, item] as const))
|
||||
for (const item of b) map.set(item.id, item)
|
||||
return [...map.values()].sort((x, y) => cmp(x.id, y.id))
|
||||
}
|
||||
|
||||
type OptimisticStore = {
|
||||
message: Record<string, Message[] | undefined>
|
||||
part: Record<string, Part[] | undefined>
|
||||
@@ -127,40 +108,9 @@ export function applyOptimisticRemove(draft: OptimisticStore, input: OptimisticR
|
||||
delete draft.part[input.messageID]
|
||||
}
|
||||
|
||||
function setOptimisticAdd(setStore: (...args: unknown[]) => void, input: OptimisticAddInput) {
|
||||
setStore("message", input.sessionID, (messages: Message[] | undefined) => {
|
||||
if (!messages) return [input.message]
|
||||
const result = Binary.search(messages, input.message.id, (m) => m.id)
|
||||
const next = [...messages]
|
||||
next.splice(result.index, 0, input.message)
|
||||
return next
|
||||
})
|
||||
setStore("part", input.message.id, sortParts(input.parts))
|
||||
export const useSync = () => {
|
||||
const globalSync = useGlobalSync()
|
||||
const sdk = useSDK()
|
||||
|
||||
return globalSync.createDirSyncContext(sdk.directory)
|
||||
}
|
||||
|
||||
function setOptimisticRemove(setStore: (...args: unknown[]) => void, input: OptimisticRemoveInput) {
|
||||
setStore("message", input.sessionID, (messages: Message[] | undefined) => {
|
||||
if (!messages) return messages
|
||||
const result = Binary.search(messages, input.messageID, (m) => m.id)
|
||||
if (!result.found) return messages
|
||||
const next = [...messages]
|
||||
next.splice(result.index, 1)
|
||||
return next
|
||||
})
|
||||
setStore("part", (part: Record<string, Part[] | undefined>) => {
|
||||
if (!(input.messageID in part)) return part
|
||||
const next = { ...part }
|
||||
delete next[input.messageID]
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||
name: "Sync",
|
||||
init: () => {
|
||||
const globalSync = useGlobalSync()
|
||||
const sdk = useSDK()
|
||||
|
||||
return globalSync.createDirSyncContext(sdk.directory)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { Iterable, pipe } from "effect"
|
||||
import { createMemo } from "solid-js"
|
||||
|
||||
export const popularProviders = [
|
||||
@@ -29,16 +30,32 @@ export function useProviders() {
|
||||
return {
|
||||
all: () => providers().all,
|
||||
default: () => providers().default,
|
||||
popular: () => providers().all.filter((p) => popularProviderSet.has(p.id)),
|
||||
popular: () =>
|
||||
pipe(
|
||||
providers().all,
|
||||
Iterable.map(([, p]) => p),
|
||||
Iterable.filter((p) => popularProviderSet.has(p.id)),
|
||||
(v) => Array.from(v),
|
||||
),
|
||||
connected: () => {
|
||||
const connected = new Set(providers().connected)
|
||||
return providers().all.filter((p) => connected.has(p.id))
|
||||
return pipe(
|
||||
providers().all,
|
||||
Iterable.map(([, p]) => p),
|
||||
Iterable.filter((p) => connected.has(p.id)),
|
||||
(v) => Array.from(v),
|
||||
)
|
||||
},
|
||||
paid: () => {
|
||||
const connected = new Set(providers().connected)
|
||||
return providers().all.filter(
|
||||
(p) => connected.has(p.id) && (p.id !== "opencode" || Object.values(p.models).some((m) => m.cost?.input)),
|
||||
)
|
||||
return [
|
||||
...Iterable.filter(
|
||||
providers().all,
|
||||
([id]) =>
|
||||
connected.has(id) &&
|
||||
(id !== "opencode" || Object.values(providers().all.get(id)?.models ?? {}).some((m) => m.cost?.input)),
|
||||
),
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,6 +784,8 @@ export const dict = {
|
||||
"settings.general.row.showSessionProgressBar.title": "Show session progress bar",
|
||||
"settings.general.row.showSessionProgressBar.description":
|
||||
"Display the animated progress bar at the top of the session when the agent is working",
|
||||
"settings.general.row.pinchZoom.title": "Pinch to zoom",
|
||||
"settings.general.row.pinchZoom.description": "Allow trackpad pinch and Ctrl-scroll gestures to zoom",
|
||||
|
||||
"settings.general.row.wayland.title": "Use native Wayland",
|
||||
"settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createEffect, createMemo, createResource, type ParentProps, Show } from
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { LocalProvider } from "@/context/local"
|
||||
import { SDKProvider } from "@/context/sdk"
|
||||
import { SyncProvider, useSync } from "@/context/sync"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { Schema } from "effect"
|
||||
|
||||
@@ -81,10 +81,8 @@ export default function Layout(props: ParentProps) {
|
||||
return (
|
||||
<Show when={resolved()} keyed>
|
||||
{(resolved) => (
|
||||
<SDKProvider directory={() => resolved}>
|
||||
<SyncProvider>
|
||||
<DirectoryDataProvider directory={resolved}>{props.children}</DirectoryDataProvider>
|
||||
</SyncProvider>
|
||||
<SDKProvider directory={resolved}>
|
||||
<DirectoryDataProvider directory={resolved}>{props.children}</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -89,7 +89,7 @@ import {
|
||||
import { ProjectDragOverlay, SortableProject, type ProjectSidebarContext } from "./layout/sidebar-project"
|
||||
import { SidebarContent } from "./layout/sidebar-shell"
|
||||
|
||||
const USE_HOME_DESIGN = import.meta.env.VITE_OPENCODE_CHANNEL !== "prod"
|
||||
const USE_NEW_DESIGN = import.meta.env.VITE_OPENCODE_CHANNEL !== "prod"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const [store, setStore, , ready] = persisted(
|
||||
@@ -154,7 +154,7 @@ export default function Layout(props: ParentProps) {
|
||||
const currentDir = createMemo(() => route().dir)
|
||||
|
||||
const [state, setState] = createStore({
|
||||
autoselect: !initialDirectory && !USE_HOME_DESIGN,
|
||||
autoselect: !initialDirectory && !USE_NEW_DESIGN,
|
||||
busyWorkspaces: {} as Record<string, boolean>,
|
||||
hoverProject: undefined as string | undefined,
|
||||
scrollSessionKey: undefined as string | undefined,
|
||||
@@ -1028,19 +1028,6 @@ export default function Layout(props: ParentProps) {
|
||||
keybind: "mod+alt+arrowdown",
|
||||
onSelect: () => navigateProjectByOffset(1),
|
||||
},
|
||||
...Array.from({ length: 9 }, (_, i) => {
|
||||
const index = i
|
||||
const number = index + 1
|
||||
return {
|
||||
id: `project.${number}`,
|
||||
category: language.t("command.category.project"),
|
||||
title: `Open Project {number}`,
|
||||
keybind: `mod+${number}`,
|
||||
disabled: layout.projects.list().length <= index,
|
||||
hidden: true,
|
||||
onSelect: () => navigateToProjectIndex(index),
|
||||
}
|
||||
}),
|
||||
{
|
||||
id: "provider.connect",
|
||||
title: language.t("command.provider.connect"),
|
||||
@@ -1155,6 +1142,21 @@ export default function Layout(props: ParentProps) {
|
||||
},
|
||||
]
|
||||
|
||||
if (!USE_NEW_DESIGN)
|
||||
Array.from({ length: 9 }, (_, i) => {
|
||||
const index = i
|
||||
const number = index + 1
|
||||
commands.push({
|
||||
id: `project.${number}`,
|
||||
category: language.t("command.category.project"),
|
||||
title: `Open Project {number}`,
|
||||
keybind: `mod+${number}`,
|
||||
disabled: layout.projects.list().length <= index,
|
||||
hidden: true,
|
||||
onSelect: () => navigateToProjectIndex(index),
|
||||
})
|
||||
})
|
||||
|
||||
for (const [id] of availableThemeEntries()) {
|
||||
commands.push({
|
||||
id: `theme.set.${id}`,
|
||||
@@ -1819,7 +1821,7 @@ export default function Layout(props: ParentProps) {
|
||||
createEffect(() => {
|
||||
document.documentElement.style.setProperty(
|
||||
"--dialog-left-margin",
|
||||
USE_HOME_DESIGN ? "0px" : `${layout.sidebar.opened() ? layout.sidebar.width() : 48}px`,
|
||||
USE_NEW_DESIGN ? "0px" : `${layout.sidebar.opened() ? layout.sidebar.width() : 48}px`,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -2303,7 +2305,7 @@ export default function Layout(props: ParentProps) {
|
||||
<div
|
||||
class="shrink-0 px-3 py-3"
|
||||
classList={{
|
||||
hidden: store.gettingStartedDismissed || !(providers.all().length > 0 && providers.paid().length === 0),
|
||||
hidden: store.gettingStartedDismissed || !(providers.all().size > 0 && providers.paid().length === 0),
|
||||
}}
|
||||
>
|
||||
<div class="rounded-xl bg-background-base shadow-xs-border-base" data-component="getting-started">
|
||||
@@ -2361,7 +2363,7 @@ export default function Layout(props: ParentProps) {
|
||||
/>
|
||||
)
|
||||
|
||||
if (USE_HOME_DESIGN) {
|
||||
if (USE_NEW_DESIGN) {
|
||||
return (
|
||||
<div class="relative bg-v2-background-bg-deep flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
|
||||
{autoselecting() ?? ""}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||
import { debounce } from "@solid-primitives/scheduled"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||
import { createStore } from "solid-js/store"
|
||||
@@ -262,8 +263,9 @@ export default function Page() {
|
||||
|
||||
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||
const size = createSizing()
|
||||
const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
|
||||
const desktopFileTreeOpen = createMemo(() => isDesktop() && layout.fileTree.opened())
|
||||
const isV2NewSessionPage = () => import.meta.env.VITE_OPENCODE_CHANNEL === "prod" || !params.id
|
||||
const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened() && !isV2NewSessionPage())
|
||||
const desktopFileTreeOpen = createMemo(() => isDesktop() && layout.fileTree.opened() && !isV2NewSessionPage())
|
||||
const desktopSidePanelOpen = createMemo(() => desktopReviewOpen() || desktopFileTreeOpen())
|
||||
const sessionPanelWidth = createMemo(() => {
|
||||
if (!desktopSidePanelOpen()) return "100%"
|
||||
@@ -477,7 +479,7 @@ export default function Page() {
|
||||
: skipToken,
|
||||
}
|
||||
})
|
||||
const refreshVcs = () => void queryClient.invalidateQueries({ queryKey: vcsKey() })
|
||||
const refreshVcs = debounce(() => void queryClient.invalidateQueries({ queryKey: vcsKey() }), 100)
|
||||
const reviewDiffs = () => {
|
||||
if (store.changes === "git" || store.changes === "branch")
|
||||
// avoids suspense
|
||||
@@ -1733,12 +1735,12 @@ export default function Page() {
|
||||
</Tabs>
|
||||
</Show>
|
||||
|
||||
{/* Session panel */}
|
||||
<div
|
||||
classList={{
|
||||
"@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger flex-1 md:flex-none": true,
|
||||
"transition-[width] duration-[240ms] ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width] motion-reduce:transition-none":
|
||||
"duration-[240ms] ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width] motion-reduce:transition-none":
|
||||
!size.active() && !ui.reviewSnap,
|
||||
"transition-[width]": !isV2NewSessionPage(),
|
||||
}}
|
||||
style={{
|
||||
width: sessionPanelWidth(),
|
||||
|
||||
@@ -55,7 +55,7 @@ export function SessionSidePanel(props: {
|
||||
const language = useLanguage()
|
||||
const command = useCommand()
|
||||
const dialog = useDialog()
|
||||
const { sessionKey, tabs, view } = useSessionLayout()
|
||||
const { sessionKey, tabs, view, params } = useSessionLayout()
|
||||
|
||||
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||
const shown = createMemo(
|
||||
@@ -207,7 +207,7 @@ export function SessionSidePanel(props: {
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={isDesktop()}>
|
||||
<Show when={isDesktop() && !(import.meta.env.VITE_OPENCODE_CHANNEL !== "prod" && !params.id)}>
|
||||
<aside
|
||||
id="review-panel"
|
||||
aria-label={language.t("session.panel.reviewAndFiles")}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -348,8 +348,8 @@ export const dict = {
|
||||
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).",
|
||||
|
||||
"zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.",
|
||||
"zen.api.error.modelNotSupported": "Model {{model}} not supported",
|
||||
"zen.api.error.modelFormatNotSupported": "Model {{model}} not supported for format {{format}}",
|
||||
"zen.api.error.modelNotSupported": "Model {{model}} is not supported",
|
||||
"zen.api.error.modelFormatNotSupported": "Model {{model}} is not supported for format {{format}}",
|
||||
"zen.api.error.noProviderAvailable": "No provider available",
|
||||
"zen.api.error.providerNotSupported": "Provider {{provider}} not supported",
|
||||
"zen.api.error.missingApiKey": "Missing API key.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
Vendored
+1
-298
@@ -4,304 +4,7 @@
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_CHANNEL_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HoneycombWebhookSecret": {
|
||||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_INSTANCE_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_PUBLISHABLE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"Teams": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
"WebApp": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
"ZEN_BLACK_PRICE": {
|
||||
"plan100": string
|
||||
"plan20": string
|
||||
"plan200": string
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_LIMITS": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth100Coupon": string
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS10": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS11": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS12": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS13": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS14": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS15": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS16": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS17": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS18": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS19": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS20": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS21": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS22": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS23": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS24": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS25": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS26": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS27": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS28": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS29": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS30": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS5": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS6": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS7": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS8": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS9": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_SESSION_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"LogProcessor": cloudflare.Service
|
||||
"Stat": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
"ZenDataNew": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
/// <reference path="../../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
+1
-298
@@ -4,304 +4,7 @@
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_CHANNEL_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HoneycombWebhookSecret": {
|
||||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_INSTANCE_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_PUBLISHABLE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"Teams": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
"WebApp": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
"ZEN_BLACK_PRICE": {
|
||||
"plan100": string
|
||||
"plan20": string
|
||||
"plan200": string
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_LIMITS": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth100Coupon": string
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS10": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS11": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS12": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS13": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS14": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS15": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS16": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS17": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS18": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS19": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS20": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS21": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS22": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS23": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS24": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS25": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS26": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS27": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS28": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS29": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS30": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS5": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS6": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS7": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS8": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS9": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_SESSION_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"LogProcessor": cloudflare.Service
|
||||
"Stat": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
"ZenDataNew": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
/// <reference path="../../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -5,6 +5,11 @@ export const Resource = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_target, prop: string) {
|
||||
if (`SST_RESOURCE_${prop}` in env) {
|
||||
// @ts-expect-error
|
||||
const value = env[`SST_RESOURCE_${prop}`]
|
||||
return typeof value === "string" ? JSON.parse(value) : value
|
||||
}
|
||||
if (prop in env) {
|
||||
// @ts-expect-error
|
||||
const value = env[prop]
|
||||
|
||||
+1
-298
@@ -4,304 +4,7 @@
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_CHANNEL_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HoneycombWebhookSecret": {
|
||||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_INSTANCE_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_PUBLISHABLE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"Teams": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
"WebApp": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
"ZEN_BLACK_PRICE": {
|
||||
"plan100": string
|
||||
"plan20": string
|
||||
"plan200": string
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_LIMITS": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth100Coupon": string
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS10": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS11": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS12": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS13": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS14": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS15": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS16": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS17": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS18": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS19": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS20": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS21": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS22": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS23": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS24": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS25": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS26": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS27": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS28": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS29": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS30": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS5": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS6": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS7": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS8": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS9": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_SESSION_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"LogProcessor": cloudflare.Service
|
||||
"Stat": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
"ZenDataNew": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
/// <reference path="../../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -59,7 +59,7 @@
|
||||
"ai-gateway-provider": "3.1.2",
|
||||
"cross-spawn": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"gitlab-ai-provider": "6.6.0",
|
||||
"gitlab-ai-provider": "6.7.0",
|
||||
"glob": "13.0.5",
|
||||
"google-auth-library": "10.5.0",
|
||||
"immer": "11.1.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export * as PluginBoot from "./boot"
|
||||
|
||||
import { Context, Deferred, Effect, Layer, Scope } from "effect"
|
||||
import { Context, Deferred, Effect, Layer } from "effect"
|
||||
import { AccountV2 } from "../account"
|
||||
import { AgentV2 } from "../agent"
|
||||
import { Catalog } from "../catalog"
|
||||
|
||||
@@ -25,7 +25,8 @@ function resolveLocation(options: Record<string, any>) {
|
||||
}
|
||||
|
||||
function vertexEndpoint(location: string) {
|
||||
return location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`
|
||||
if (location === "global") return "aiplatform.googleapis.com"
|
||||
return `${location}-aiplatform.googleapis.com`
|
||||
}
|
||||
|
||||
function replaceVertexVars(value: string, project: string | undefined, location: string) {
|
||||
@@ -131,16 +132,25 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({
|
||||
"aisdk.sdk": Effect.fn(function* (evt) {
|
||||
if (evt.package !== "@ai-sdk/google-vertex/anthropic") return
|
||||
const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic"))
|
||||
const project =
|
||||
typeof evt.options.project === "string"
|
||||
? evt.options.project
|
||||
: (process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCP_PROJECT ?? process.env.GCLOUD_PROJECT)
|
||||
const location =
|
||||
typeof evt.options.location === "string"
|
||||
? evt.options.location
|
||||
: (process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "global")
|
||||
evt.sdk = mod.createVertexAnthropic({
|
||||
...evt.options,
|
||||
project:
|
||||
typeof evt.options.project === "string"
|
||||
? evt.options.project
|
||||
: (process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCP_PROJECT ?? process.env.GCLOUD_PROJECT),
|
||||
location:
|
||||
typeof evt.options.location === "string"
|
||||
? evt.options.location
|
||||
: (process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "global"),
|
||||
project,
|
||||
location,
|
||||
// Continental multi-regions (eu, us) require Regional Endpoint Platform
|
||||
// domains; the default {region}-aiplatform.googleapis.com does not resolve.
|
||||
...((location === "eu" || location === "us") && project && !evt.options.baseURL
|
||||
? {
|
||||
baseURL: `https://aiplatform.${location}.rep.googleapis.com/v1/projects/${project}/locations/${location}/publishers/anthropic/models`,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
}),
|
||||
"aisdk.language": Effect.fn(function* (evt) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Effect } from "effect"
|
||||
import { PluginV2 } from "../../plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const VercelPlugin = PluginV2.define({
|
||||
id: PluginV2.ID.make("vercel"),
|
||||
|
||||
@@ -65,5 +65,6 @@ export abstract class NamedError extends Error {
|
||||
|
||||
public static readonly Unknown = NamedError.create("UnknownError", {
|
||||
message: Schema.String,
|
||||
ref: Schema.optional(Schema.String),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||
import { CerebrasPlugin } from "@opencode-ai/core/plugin/provider/cerebras"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { it, model, provider } from "./provider-helper"
|
||||
import { it, model } from "./provider-helper"
|
||||
|
||||
const cerebrasOptions: Record<string, unknown>[] = []
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||
import { GoogleVertexAnthropicPlugin } from "@opencode-ai/core/plugin/provider/google-vertex"
|
||||
import { GoogleVertexAnthropicPlugin, GoogleVertexPlugin } from "@opencode-ai/core/plugin/provider/google-vertex"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { fakeSelectorSdk, it, model, withEnv } from "./provider-helper"
|
||||
|
||||
@@ -109,6 +109,73 @@ describe("GoogleVertexAnthropicPlugin", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("creates SDKs for google-vertex Anthropic models with multi-region endpoints", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugin = yield* PluginV2.Service
|
||||
yield* plugin.add(GoogleVertexAnthropicPlugin)
|
||||
const result = yield* plugin.trigger(
|
||||
"aisdk.sdk",
|
||||
{
|
||||
model: model("google-vertex", "claude-sonnet-4-5"),
|
||||
package: "@ai-sdk/google-vertex/anthropic",
|
||||
options: { name: "google-vertex", project: "project", location: "eu" },
|
||||
},
|
||||
{},
|
||||
)
|
||||
expect(result.sdk.languageModel("claude-sonnet-4-5").config.baseURL).toBe(
|
||||
"https://aiplatform.eu.rep.googleapis.com/v1/projects/project/locations/eu/publishers/anthropic/models",
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("keeps configured baseURL for google-vertex Anthropic models", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugin = yield* PluginV2.Service
|
||||
yield* plugin.add(GoogleVertexAnthropicPlugin)
|
||||
const result = yield* plugin.trigger(
|
||||
"aisdk.sdk",
|
||||
{
|
||||
model: model("google-vertex", "claude-sonnet-4-5"),
|
||||
package: "@ai-sdk/google-vertex/anthropic",
|
||||
options: { name: "google-vertex", project: "project", location: "eu", baseURL: "https://proxy.example/v1" },
|
||||
},
|
||||
{},
|
||||
)
|
||||
expect(result.sdk.languageModel("claude-sonnet-4-5").config.baseURL).toBe("https://proxy.example/v1")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("selects google-vertex Anthropic language models through V2 plugins", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugin = yield* PluginV2.Service
|
||||
yield* plugin.add(GoogleVertexPlugin)
|
||||
yield* plugin.add(GoogleVertexAnthropicPlugin)
|
||||
const sdkResult = yield* plugin.trigger(
|
||||
"aisdk.sdk",
|
||||
{
|
||||
model: model("google-vertex", " claude-sonnet-4-5 "),
|
||||
package: "@ai-sdk/google-vertex/anthropic",
|
||||
options: { name: "google-vertex", project: "project", location: "us" },
|
||||
},
|
||||
{},
|
||||
)
|
||||
const languageResult = yield* plugin.trigger(
|
||||
"aisdk.language",
|
||||
{
|
||||
model: model("google-vertex", " claude-sonnet-4-5 "),
|
||||
sdk: sdkResult.sdk,
|
||||
options: {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
const language = languageResult.language as unknown as { config: { baseURL: string }; modelId: string }
|
||||
expect(language.config.baseURL).toBe(
|
||||
"https://aiplatform.us.rep.googleapis.com/v1/projects/project/locations/us/publishers/anthropic/models",
|
||||
)
|
||||
expect(language.modelId).toBe("claude-sonnet-4-5")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("trims model IDs before selecting language models", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugin = yield* PluginV2.Service
|
||||
|
||||
@@ -160,6 +160,32 @@ describe("GoogleVertexPlugin", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("keeps OpenAI-compatible Vertex endpoint templates regional for eu", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugin = yield* PluginV2.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
yield* plugin.add(GoogleVertexPlugin)
|
||||
const load = yield* catalog.loader()
|
||||
yield* load((catalog) =>
|
||||
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
|
||||
provider.endpoint = {
|
||||
type: "aisdk",
|
||||
package: "@ai-sdk/openai-compatible",
|
||||
url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
|
||||
}
|
||||
provider.options.aisdk.provider.project = "config-project"
|
||||
provider.options.aisdk.provider.location = "eu"
|
||||
}),
|
||||
)
|
||||
const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
|
||||
expect(provider.endpoint).toEqual({
|
||||
type: "aisdk",
|
||||
package: "@ai-sdk/openai-compatible",
|
||||
url: "https://eu-aiplatform.googleapis.com/v1/projects/config-project/locations/eu",
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("defaults location to us-central1 when only project is configured", () =>
|
||||
withEnv(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -7,4 +7,5 @@ export const CHANNEL: Channel = raw === "dev" || raw === "beta" || raw === "prod
|
||||
export const SETTINGS_STORE = "opencode.settings"
|
||||
export const DEFAULT_SERVER_URL_KEY = "defaultServerUrl"
|
||||
export const WSL_ENABLED_KEY = "wslEnabled"
|
||||
export const PINCH_ZOOM_ENABLED_KEY = "pinchZoomEnabled"
|
||||
export const UPDATER_ENABLED = app.isPackaged && CHANNEL !== "dev"
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
} from "../preload/types"
|
||||
import { runDesktopMenuAction } from "./desktop-menu-actions"
|
||||
import { getStore } from "./store"
|
||||
import { setTitlebar, updateTitlebar } from "./windows"
|
||||
import { getPinchZoomEnabled, setPinchZoomEnabled, setTitlebar, updateTitlebar } from "./windows"
|
||||
|
||||
const pickerFilters = (ext?: string[]) => {
|
||||
if (!ext || ext.length === 0) return undefined
|
||||
@@ -202,6 +202,10 @@ export function registerIpcHandlers(deps: Deps) {
|
||||
if (!win) return
|
||||
updateTitlebar(win)
|
||||
})
|
||||
ipcMain.handle("get-pinch-zoom-enabled", () => getPinchZoomEnabled())
|
||||
ipcMain.handle("set-pinch-zoom-enabled", (_event: IpcMainInvokeEvent, enabled: boolean) => {
|
||||
setPinchZoomEnabled(enabled)
|
||||
})
|
||||
ipcMain.handle("set-titlebar", (event: IpcMainInvokeEvent, theme: TitlebarTheme) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
if (!win) return
|
||||
|
||||
@@ -3,7 +3,9 @@ import { app, BrowserWindow, dialog, net, nativeImage, nativeTheme, protocol } f
|
||||
import { dirname, isAbsolute, join, relative, resolve } from "node:path"
|
||||
import { fileURLToPath, pathToFileURL } from "node:url"
|
||||
import type { TitlebarTheme } from "../preload/types"
|
||||
import { PINCH_ZOOM_ENABLED_KEY } from "./constants"
|
||||
import { exportDebugLogs, write as writeLog } from "./logging"
|
||||
import { getStore } from "./store"
|
||||
import { createUnresponsiveSampler } from "./unresponsive"
|
||||
|
||||
const root = dirname(fileURLToPath(import.meta.url))
|
||||
@@ -33,7 +35,10 @@ let relaunchHandler = () => {
|
||||
app.exit(0)
|
||||
}
|
||||
const titlebarThemes = new WeakMap<BrowserWindow, Partial<TitlebarTheme>>()
|
||||
const pinchZoomEnabled = new WeakMap<BrowserWindow, boolean>()
|
||||
const titlebarHeight = 40
|
||||
const maxZoomLevel = 10
|
||||
const minZoomLevel = 0.2
|
||||
|
||||
export function setRelaunchHandler(handler: () => void) {
|
||||
relaunchHandler = handler
|
||||
@@ -79,6 +84,20 @@ export function updateTitlebar(win: BrowserWindow) {
|
||||
win.setTitleBarOverlay(overlay(titlebarThemes.get(win), win.webContents.getZoomFactor()))
|
||||
}
|
||||
|
||||
export function setPinchZoomEnabled(enabled: boolean) {
|
||||
getStore().set(PINCH_ZOOM_ENABLED_KEY, enabled)
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
pinchZoomEnabled.set(win, enabled)
|
||||
win.webContents.send("pinch-zoom-enabled-changed", enabled)
|
||||
if (!enabled && win.webContents.getZoomFactor() !== 1) win.webContents.setZoomFactor(1)
|
||||
updateZoom(win)
|
||||
}
|
||||
}
|
||||
|
||||
export function getPinchZoomEnabled() {
|
||||
return getStore().get(PINCH_ZOOM_ENABLED_KEY) === true
|
||||
}
|
||||
|
||||
export function setDockIcon() {
|
||||
if (process.platform !== "darwin") return
|
||||
const icon = nativeImage.createFromPath(join(iconsDir(), "dock.png"))
|
||||
@@ -392,13 +411,29 @@ function isRendererUrl(value?: string, html = false) {
|
||||
}
|
||||
|
||||
function wireZoom(win: BrowserWindow) {
|
||||
pinchZoomEnabled.set(win, getPinchZoomEnabled())
|
||||
win.webContents.setZoomFactor(1)
|
||||
win.webContents.on("zoom-changed", () => {
|
||||
win.webContents.setZoomFactor(1)
|
||||
updateTitlebar(win)
|
||||
win.webContents.on("zoom-changed", (event, zoomDirection) => {
|
||||
event.preventDefault()
|
||||
if (pinchZoomEnabled.get(win)) {
|
||||
win.webContents.setZoomFactor(clampZoom(win.webContents.getZoomFactor() + (zoomDirection === "in" ? 0.2 : -0.2)))
|
||||
updateZoom(win)
|
||||
return
|
||||
}
|
||||
if (win.webContents.getZoomFactor() !== 1) win.webContents.setZoomFactor(1)
|
||||
updateZoom(win)
|
||||
})
|
||||
}
|
||||
|
||||
function clampZoom(value: number) {
|
||||
return Math.min(Math.max(value, minZoomLevel), maxZoomLevel)
|
||||
}
|
||||
|
||||
function updateZoom(win: BrowserWindow) {
|
||||
updateTitlebar(win)
|
||||
win.webContents.send("zoom-factor-changed", win.webContents.getZoomFactor())
|
||||
}
|
||||
|
||||
function upsertKeyValue(obj: Record<string, any>, keyToChange: string, value: any) {
|
||||
const keyToChangeLower = keyToChange.toLowerCase()
|
||||
for (const key of Object.keys(obj)) {
|
||||
|
||||
@@ -60,6 +60,18 @@ const api: ElectronAPI = {
|
||||
relaunch: () => ipcRenderer.send("relaunch"),
|
||||
getZoomFactor: () => ipcRenderer.invoke("get-zoom-factor"),
|
||||
setZoomFactor: (factor) => ipcRenderer.invoke("set-zoom-factor", factor),
|
||||
getPinchZoomEnabled: () => ipcRenderer.invoke("get-pinch-zoom-enabled"),
|
||||
setPinchZoomEnabled: (enabled) => ipcRenderer.invoke("set-pinch-zoom-enabled", enabled),
|
||||
onPinchZoomEnabledChanged: (cb) => {
|
||||
const handler = (_: unknown, enabled: boolean) => cb(enabled)
|
||||
ipcRenderer.on("pinch-zoom-enabled-changed", handler)
|
||||
return () => ipcRenderer.removeListener("pinch-zoom-enabled-changed", handler)
|
||||
},
|
||||
onZoomFactorChanged: (cb) => {
|
||||
const handler = (_: unknown, factor: number) => cb(factor)
|
||||
ipcRenderer.on("zoom-factor-changed", handler)
|
||||
return () => ipcRenderer.removeListener("zoom-factor-changed", handler)
|
||||
},
|
||||
setTitlebar: (theme) => ipcRenderer.invoke("set-titlebar", theme),
|
||||
runDesktopMenuAction: (action) => ipcRenderer.invoke("run-desktop-menu-action", action),
|
||||
loadingWindowComplete: () => ipcRenderer.send("loading-window-complete"),
|
||||
|
||||
@@ -79,6 +79,10 @@ export type ElectronAPI = {
|
||||
relaunch: () => void
|
||||
getZoomFactor: () => Promise<number>
|
||||
setZoomFactor: (factor: number) => Promise<void>
|
||||
getPinchZoomEnabled: () => Promise<boolean>
|
||||
setPinchZoomEnabled: (enabled: boolean) => Promise<void>
|
||||
onPinchZoomEnabledChanged: (cb: (enabled: boolean) => void) => () => void
|
||||
onZoomFactorChanged: (cb: (factor: number) => void) => () => void
|
||||
setTitlebar: (theme: TitlebarTheme) => Promise<void>
|
||||
runDesktopMenuAction: (action: DesktopMenuAction) => Promise<void>
|
||||
loadingWindowComplete: () => void
|
||||
|
||||
@@ -21,7 +21,7 @@ import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js
|
||||
import { render } from "solid-js/web"
|
||||
import pkg from "../../package.json"
|
||||
import { initI18n, t } from "./i18n"
|
||||
import { resetZoom, webviewZoom, zoomIn, zoomOut } from "./webview-zoom"
|
||||
import { resetZoom, setPinchZoomEnabled, webviewZoom, zoomIn, zoomOut } from "./webview-zoom"
|
||||
import "./styles.css"
|
||||
import { useTheme } from "@opencode-ai/ui/theme"
|
||||
|
||||
@@ -274,6 +274,10 @@ const createPlatform = (): Platform => {
|
||||
|
||||
webviewZoom,
|
||||
|
||||
getPinchZoomEnabled: () => window.api.getPinchZoomEnabled(),
|
||||
|
||||
setPinchZoomEnabled,
|
||||
|
||||
runDesktopMenuAction,
|
||||
|
||||
checkAppExists: async (appName: string) => {
|
||||
|
||||
@@ -13,9 +13,21 @@ const OS_NAME = (() => {
|
||||
|
||||
const [webviewZoom, setWebviewZoom] = createSignal(1)
|
||||
let requestedZoom = 1
|
||||
let pinchZoomEnabled = false
|
||||
let wheelPinch = undefined as
|
||||
| {
|
||||
active: boolean
|
||||
startZoom: number
|
||||
totalDelta: number
|
||||
timeout: ReturnType<typeof setTimeout> | undefined
|
||||
}
|
||||
| undefined
|
||||
|
||||
const MAX_ZOOM_LEVEL = 10
|
||||
const MIN_ZOOM_LEVEL = 0.2
|
||||
const WHEEL_PINCH_THRESHOLD = 20
|
||||
const WHEEL_PINCH_STEP = 0.2
|
||||
const WHEEL_PINCH_END_DELAY = 160
|
||||
|
||||
const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)
|
||||
|
||||
@@ -33,10 +45,77 @@ const applyZoom = (next: number) => {
|
||||
})
|
||||
}
|
||||
|
||||
window.api.onZoomFactorChanged((factor) => {
|
||||
requestedZoom = clamp(factor)
|
||||
setWebviewZoom(requestedZoom)
|
||||
})
|
||||
|
||||
void window.api.getPinchZoomEnabled().then((enabled) => {
|
||||
pinchZoomEnabled = enabled
|
||||
})
|
||||
|
||||
window.api.onPinchZoomEnabledChanged((enabled) => {
|
||||
pinchZoomEnabled = enabled
|
||||
resetWheelPinch()
|
||||
})
|
||||
|
||||
const setPinchZoomEnabled = (enabled: boolean) => {
|
||||
pinchZoomEnabled = enabled
|
||||
resetWheelPinch()
|
||||
return window.api.setPinchZoomEnabled(enabled)
|
||||
}
|
||||
|
||||
const resetZoom = () => applyZoom(1)
|
||||
const zoomIn = () => applyZoom(clamp(requestedZoom + 0.2))
|
||||
const zoomOut = () => applyZoom(clamp(requestedZoom - 0.2))
|
||||
|
||||
const resetWheelPinch = () => {
|
||||
clearTimeout(wheelPinch?.timeout)
|
||||
wheelPinch = undefined
|
||||
}
|
||||
|
||||
const normalizeWheelDelta = (event: WheelEvent) => {
|
||||
if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) return event.deltaY * 16
|
||||
if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) return event.deltaY * window.innerHeight
|
||||
return event.deltaY
|
||||
}
|
||||
|
||||
const updateWheelPinch = (event: WheelEvent) => {
|
||||
wheelPinch ??= {
|
||||
active: false,
|
||||
startZoom: requestedZoom,
|
||||
totalDelta: 0,
|
||||
timeout: undefined,
|
||||
}
|
||||
|
||||
clearTimeout(wheelPinch.timeout)
|
||||
wheelPinch.timeout = setTimeout(resetWheelPinch, WHEEL_PINCH_END_DELAY)
|
||||
wheelPinch.totalDelta += normalizeWheelDelta(event)
|
||||
|
||||
if (!wheelPinch.active && Math.abs(wheelPinch.totalDelta) < WHEEL_PINCH_THRESHOLD) return
|
||||
if (!wheelPinch.active) {
|
||||
wheelPinch.active = true
|
||||
wheelPinch.startZoom = requestedZoom
|
||||
wheelPinch.totalDelta = 0
|
||||
return
|
||||
}
|
||||
|
||||
wheelPinch.active = true
|
||||
applyZoom(clamp(wheelPinch.startZoom - (wheelPinch.totalDelta / WHEEL_PINCH_THRESHOLD) * WHEEL_PINCH_STEP))
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
"wheel",
|
||||
(event) => {
|
||||
if (!pinchZoomEnabled) return
|
||||
if (!event.ctrlKey) return
|
||||
|
||||
event.preventDefault()
|
||||
updateWheelPinch(event)
|
||||
},
|
||||
{ passive: false },
|
||||
)
|
||||
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return
|
||||
|
||||
@@ -56,4 +135,4 @@ window.addEventListener("keydown", (event) => {
|
||||
}
|
||||
})
|
||||
|
||||
export { webviewZoom, resetZoom, zoomIn, zoomOut }
|
||||
export { webviewZoom, resetZoom, setPinchZoomEnabled, zoomIn, zoomOut }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.5",
|
||||
"version": "1.15.9",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
Vendored
+1
-298
@@ -4,304 +4,7 @@
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_CHANNEL_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HoneycombWebhookSecret": {
|
||||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_INSTANCE_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_PUBLISHABLE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"Teams": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
"WebApp": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
"ZEN_BLACK_PRICE": {
|
||||
"plan100": string
|
||||
"plan20": string
|
||||
"plan200": string
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_LIMITS": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth100Coupon": string
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS10": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS11": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS12": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS13": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS14": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS15": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS16": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS17": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS18": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS19": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS20": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS21": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS22": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS23": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS24": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS25": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS26": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS27": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS28": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS29": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS30": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS5": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS6": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS7": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS8": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS9": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_SESSION_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"LogProcessor": cloudflare.Service
|
||||
"Stat": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
"ZenDataNew": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.15.6"
|
||||
version = "1.15.9"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.6/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.9/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.6/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.9/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.6/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.9/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.6/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.9/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.6/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.9/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
Vendored
+1
-298
@@ -4,304 +4,7 @@
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"ADMIN_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AUTH_API_URL": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_ACCESS_KEY_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"AWS_SES_SECRET_ACCESS_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_API_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"DISCORD_SUPPORT_CHANNEL_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Database": {
|
||||
"database": string
|
||||
"host": string
|
||||
"password": string
|
||||
"port": number
|
||||
"type": "sst.sst.Linkable"
|
||||
"username": string
|
||||
}
|
||||
"EMAILOCTOPUS_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"FEISHU_APP_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_PRIVATE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_ID_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_CLIENT_SECRET_CONSOLE": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GOOGLE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HONEYCOMB_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"HoneycombWebhookSecret": {
|
||||
"type": "random.index/randomPassword.RandomPassword"
|
||||
"value": string
|
||||
}
|
||||
"R2AccessKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"R2SecretKey": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_CLIENT_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"SALESFORCE_INSTANCE_URL": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_PUBLISHABLE_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"Teams": {
|
||||
"type": "sst.cloudflare.SolidStart"
|
||||
"url": string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
"WebApp": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
"ZEN_BLACK_PRICE": {
|
||||
"plan100": string
|
||||
"plan20": string
|
||||
"plan200": string
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_LIMITS": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth100Coupon": string
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"sixMonths100Coupon": string
|
||||
"threeMonths100Coupon": string
|
||||
"twelveMonths100Coupon": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
"ZEN_MODELS1": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS10": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS11": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS12": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS13": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS14": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS15": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS16": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS17": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS18": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS19": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS2": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS20": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS21": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS22": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS23": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS24": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS25": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS26": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS27": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS28": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS29": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS3": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS30": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS4": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS5": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS6": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS7": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS8": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_MODELS9": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"ZEN_SESSION_SECRET": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
import * as cloudflare from "@cloudflare/workers-types";
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"Api": cloudflare.Service
|
||||
"AuthApi": cloudflare.Service
|
||||
"AuthStorage": cloudflare.KVNamespace
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"EnterpriseStorage": cloudflare.R2Bucket
|
||||
"LogProcessor": cloudflare.Service
|
||||
"Stat": cloudflare.Service
|
||||
"ZenData": cloudflare.R2Bucket
|
||||
"ZenDataNew": cloudflare.R2Bucket
|
||||
}
|
||||
}
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"name": "@opencode-ai/llm",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
ToolChoice,
|
||||
ToolDefinition,
|
||||
type ContentPart,
|
||||
ToolCallPart,
|
||||
ToolResultPart,
|
||||
} from "./schema"
|
||||
import { make as makeTool, type ToolSchema } from "./tool"
|
||||
|
||||
@@ -10,9 +10,11 @@ import {
|
||||
type CacheHint,
|
||||
type FinishReason,
|
||||
type LLMRequest,
|
||||
type MediaPart,
|
||||
type ProviderMetadata,
|
||||
type ToolCallPart,
|
||||
type ToolDefinition,
|
||||
type ToolResultContentPart,
|
||||
type ToolResultPart,
|
||||
} from "../schema"
|
||||
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
||||
@@ -39,6 +41,17 @@ const AnthropicTextBlock = Schema.Struct({
|
||||
})
|
||||
type AnthropicTextBlock = Schema.Schema.Type<typeof AnthropicTextBlock>
|
||||
|
||||
const AnthropicImageBlock = Schema.Struct({
|
||||
type: Schema.tag("image"),
|
||||
source: Schema.Struct({
|
||||
type: Schema.tag("base64"),
|
||||
media_type: Schema.String,
|
||||
data: Schema.String,
|
||||
}),
|
||||
cache_control: Schema.optional(AnthropicCacheControl),
|
||||
})
|
||||
type AnthropicImageBlock = Schema.Schema.Type<typeof AnthropicImageBlock>
|
||||
|
||||
const AnthropicThinkingBlock = Schema.Struct({
|
||||
type: Schema.tag("thinking"),
|
||||
thinking: Schema.String,
|
||||
@@ -84,15 +97,24 @@ const AnthropicServerToolResultBlock = Schema.Struct({
|
||||
})
|
||||
type AnthropicServerToolResultBlock = Schema.Schema.Type<typeof AnthropicServerToolResultBlock>
|
||||
|
||||
// Anthropic accepts either a plain string or an ordered array of text/image
|
||||
// blocks inside `tool_result.content`. The array form is required when a tool
|
||||
// returns image bytes (screenshot, image search, etc.) so they can be passed
|
||||
// to the model as proper image inputs instead of being JSON-stringified into
|
||||
// the prompt — which silently inflates context by megabytes and can push the
|
||||
// conversation over the model's token limit.
|
||||
const AnthropicToolResultContent = Schema.Union([AnthropicTextBlock, AnthropicImageBlock])
|
||||
|
||||
const AnthropicToolResultBlock = Schema.Struct({
|
||||
type: Schema.tag("tool_result"),
|
||||
tool_use_id: Schema.String,
|
||||
content: Schema.String,
|
||||
content: Schema.Union([Schema.String, Schema.Array(AnthropicToolResultContent)]),
|
||||
is_error: Schema.optional(Schema.Boolean),
|
||||
cache_control: Schema.optional(AnthropicCacheControl),
|
||||
})
|
||||
|
||||
const AnthropicUserBlock = Schema.Union([AnthropicTextBlock, AnthropicToolResultBlock])
|
||||
const AnthropicUserBlock = Schema.Union([AnthropicTextBlock, AnthropicImageBlock, AnthropicToolResultBlock])
|
||||
type AnthropicUserBlock = Schema.Schema.Type<typeof AnthropicUserBlock>
|
||||
const AnthropicAssistantBlock = Schema.Union([
|
||||
AnthropicTextBlock,
|
||||
AnthropicThinkingBlock,
|
||||
@@ -184,7 +206,13 @@ const AnthropicEvent = Schema.Struct({
|
||||
content_block: Schema.optional(AnthropicStreamBlock),
|
||||
delta: Schema.optional(AnthropicStreamDelta),
|
||||
usage: Schema.optional(AnthropicUsage),
|
||||
error: Schema.optional(Schema.Struct({ type: Schema.String, message: Schema.String })),
|
||||
// `type` and `message` are both required per Anthropic's spec, but
|
||||
// OpenAI-compatible proxies and gateway translations occasionally drop one
|
||||
// or the other; mark them optional so a partial payload still parses and
|
||||
// the parser can fall back to whichever field is populated.
|
||||
error: Schema.optional(
|
||||
Schema.Struct({ type: Schema.optional(Schema.String), message: Schema.optional(Schema.String) }),
|
||||
),
|
||||
})
|
||||
type AnthropicEvent = Schema.Schema.Type<typeof AnthropicEvent>
|
||||
|
||||
@@ -272,6 +300,44 @@ const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult
|
||||
return { type: wireType, tool_use_id: part.id, content: part.result.value } satisfies AnthropicServerToolResultBlock
|
||||
})
|
||||
|
||||
const lowerImage = Effect.fn("AnthropicMessages.lowerImage")(function* (part: MediaPart) {
|
||||
if (!part.mediaType.startsWith("image/"))
|
||||
return yield* invalid(`Anthropic Messages user media content only supports images`)
|
||||
return {
|
||||
type: "image" as const,
|
||||
source: {
|
||||
type: "base64" as const,
|
||||
media_type: part.mediaType,
|
||||
data: ProviderShared.mediaBase64(part),
|
||||
},
|
||||
} satisfies AnthropicImageBlock
|
||||
})
|
||||
|
||||
// Tool results may carry structured text/images. Keep media as provider-native
|
||||
// content instead of JSON-stringifying base64 into a prompt string.
|
||||
const lowerToolResultContentItem = Effect.fn("AnthropicMessages.lowerToolResultContentItem")(function* (
|
||||
item: ToolResultContentPart,
|
||||
) {
|
||||
if (item.type === "text") return { type: "text" as const, text: item.text } satisfies AnthropicTextBlock
|
||||
if (item.mediaType.startsWith("image/"))
|
||||
return {
|
||||
type: "image" as const,
|
||||
source: {
|
||||
type: "base64" as const,
|
||||
media_type: item.mediaType,
|
||||
data: ProviderShared.mediaBase64(item),
|
||||
},
|
||||
} satisfies AnthropicImageBlock
|
||||
return yield* invalid(`Anthropic Messages tool-result media content only supports images, got ${item.mediaType}`)
|
||||
})
|
||||
|
||||
const lowerToolResultContent = Effect.fn("AnthropicMessages.lowerToolResultContent")(function* (part: ToolResultPart) {
|
||||
// Text / json / error results stay as a string for backward compatibility
|
||||
// with existing cassettes and provider expectations.
|
||||
if (part.result.type !== "content") return ProviderShared.toolResultText(part)
|
||||
return yield* Effect.forEach(part.result.value, lowerToolResultContentItem)
|
||||
})
|
||||
|
||||
const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
|
||||
request: LLMRequest,
|
||||
breakpoints: Cache.Breakpoints,
|
||||
@@ -280,11 +346,17 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
|
||||
|
||||
for (const message of request.messages) {
|
||||
if (message.role === "user") {
|
||||
const content: AnthropicTextBlock[] = []
|
||||
const content: AnthropicUserBlock[] = []
|
||||
for (const part of message.content) {
|
||||
if (!ProviderShared.supportsContent(part, ["text"]))
|
||||
return yield* ProviderShared.unsupportedContent("Anthropic Messages", "user", ["text"])
|
||||
content.push({ type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) })
|
||||
if (part.type === "text") {
|
||||
content.push({ type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) })
|
||||
continue
|
||||
}
|
||||
if (part.type === "media") {
|
||||
content.push(yield* lowerImage(part))
|
||||
continue
|
||||
}
|
||||
return yield* ProviderShared.unsupportedContent("Anthropic Messages", "user", ["text", "media"])
|
||||
}
|
||||
messages.push({ role: "user", content })
|
||||
continue
|
||||
@@ -328,7 +400,7 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
|
||||
content.push({
|
||||
type: "tool_result",
|
||||
tool_use_id: part.id,
|
||||
content: ProviderShared.toolResultText(part),
|
||||
content: yield* lowerToolResultContent(part),
|
||||
is_error: part.result.type === "error" ? true : undefined,
|
||||
cache_control: cacheControl(breakpoints, part.cache),
|
||||
})
|
||||
@@ -635,9 +707,18 @@ const onMessageDelta = (state: ParserState, event: AnthropicEvent): StepResult =
|
||||
return [{ ...state, lifecycle, usage }, events]
|
||||
}
|
||||
|
||||
// Prefix `error.type` so overloads, rate limits, and quota errors are visible
|
||||
// even when the provider message is generic or empty.
|
||||
const providerErrorMessage = (event: AnthropicEvent): string => {
|
||||
const type = event.error?.type
|
||||
const message = event.error?.message
|
||||
if (type && message) return `${type}: ${message}`
|
||||
return message || type || "Anthropic Messages stream error"
|
||||
}
|
||||
|
||||
const onError = (state: ParserState, event: AnthropicEvent): StepResult => [
|
||||
state,
|
||||
[LLMEvent.providerError({ message: event.error?.message ?? "Anthropic Messages stream error" })],
|
||||
[LLMEvent.providerError({ message: providerErrorMessage(event) })],
|
||||
]
|
||||
|
||||
const step = (state: ParserState, event: AnthropicEvent) => {
|
||||
|
||||
@@ -6,14 +6,16 @@ import { HttpTransport, WebSocketTransport } from "../route/transport"
|
||||
import { Protocol } from "../route/protocol"
|
||||
import {
|
||||
LLMEvent,
|
||||
type MediaPart,
|
||||
Usage,
|
||||
type FinishReason,
|
||||
type LLMRequest,
|
||||
type ProviderMetadata,
|
||||
type ReasoningPart,
|
||||
type TextPart,
|
||||
type ToolCallPart,
|
||||
type ToolDefinition,
|
||||
type ToolResultContentPart,
|
||||
type ToolResultPart,
|
||||
} from "../schema"
|
||||
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
||||
import { OpenAIOptions } from "./utils/openai-options"
|
||||
@@ -43,10 +45,33 @@ const OpenAIResponsesOutputText = Schema.Struct({
|
||||
text: Schema.String,
|
||||
})
|
||||
|
||||
const OpenAIResponsesReasoningSummaryText = Schema.Struct({
|
||||
type: Schema.tag("summary_text"),
|
||||
text: Schema.String,
|
||||
})
|
||||
|
||||
const OpenAIResponsesReasoningItem = Schema.Struct({
|
||||
type: Schema.tag("reasoning"),
|
||||
id: Schema.String,
|
||||
summary: Schema.Array(OpenAIResponsesReasoningSummaryText),
|
||||
encrypted_content: optionalNull(Schema.String),
|
||||
})
|
||||
|
||||
// `function_call_output.output` accepts either a plain string or an ordered
|
||||
// array of content items so tools can return images in addition to text.
|
||||
// https://platform.openai.com/docs/api-reference/responses/object
|
||||
const OpenAIResponsesFunctionCallOutputContent = Schema.Union([OpenAIResponsesInputText, OpenAIResponsesInputImage])
|
||||
|
||||
const OpenAIResponsesFunctionCallOutput = Schema.Union([
|
||||
Schema.String,
|
||||
Schema.Array(OpenAIResponsesFunctionCallOutputContent),
|
||||
])
|
||||
|
||||
const OpenAIResponsesInputItem = Schema.Union([
|
||||
Schema.Struct({ role: Schema.tag("system"), content: Schema.String }),
|
||||
Schema.Struct({ role: Schema.tag("user"), content: Schema.Array(OpenAIResponsesInputContent) }),
|
||||
Schema.Struct({ role: Schema.tag("assistant"), content: Schema.Array(OpenAIResponsesOutputText) }),
|
||||
OpenAIResponsesReasoningItem,
|
||||
Schema.Struct({
|
||||
type: Schema.tag("function_call"),
|
||||
call_id: Schema.String,
|
||||
@@ -56,7 +81,7 @@ const OpenAIResponsesInputItem = Schema.Union([
|
||||
Schema.Struct({
|
||||
type: Schema.tag("function_call_output"),
|
||||
call_id: Schema.String,
|
||||
output: Schema.String,
|
||||
output: OpenAIResponsesFunctionCallOutput,
|
||||
}),
|
||||
])
|
||||
type OpenAIResponsesInputItem = Schema.Schema.Type<typeof OpenAIResponsesInputItem>
|
||||
@@ -149,9 +174,21 @@ const OpenAIResponsesStreamItem = Schema.Struct({
|
||||
server_label: Schema.optional(Schema.String),
|
||||
output: Schema.optional(Schema.Unknown),
|
||||
error: Schema.optional(Schema.Unknown),
|
||||
encrypted_content: optionalNull(Schema.String),
|
||||
})
|
||||
type OpenAIResponsesStreamItem = Schema.Schema.Type<typeof OpenAIResponsesStreamItem>
|
||||
|
||||
// OpenAI Responses surfaces provider failures in two related shapes. The
|
||||
// streaming `error` event carries the details at the top level
|
||||
// (`{ type: "error", code, message, param, sequence_number }`), while
|
||||
// `response.failed` carries them under `response.error`. We capture both so
|
||||
// the parser can surface a useful provider-error message in either path.
|
||||
const OpenAIResponsesErrorPayload = Schema.Struct({
|
||||
code: optionalNull(Schema.String),
|
||||
message: optionalNull(Schema.String),
|
||||
param: optionalNull(Schema.String),
|
||||
})
|
||||
|
||||
const OpenAIResponsesEvent = Schema.Struct({
|
||||
type: Schema.String,
|
||||
delta: Schema.optional(Schema.String),
|
||||
@@ -164,12 +201,14 @@ const OpenAIResponsesEvent = Schema.Struct({
|
||||
service_tier: optionalNull(Schema.String),
|
||||
incomplete_details: optionalNull(Schema.Struct({ reason: Schema.String })),
|
||||
usage: optionalNull(OpenAIResponsesUsage),
|
||||
error: optionalNull(OpenAIResponsesErrorPayload),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Unknown)],
|
||||
),
|
||||
),
|
||||
code: Schema.optional(Schema.String),
|
||||
message: Schema.optional(Schema.String),
|
||||
param: Schema.optional(Schema.String),
|
||||
})
|
||||
type OpenAIResponsesEvent = Schema.Schema.Type<typeof OpenAIResponsesEvent>
|
||||
|
||||
@@ -206,26 +245,62 @@ const lowerToolCall = (part: ToolCallPart): OpenAIResponsesInputItem => ({
|
||||
arguments: ProviderShared.encodeJson(part.input),
|
||||
})
|
||||
|
||||
const imageUrl = (part: MediaPart) =>
|
||||
typeof part.data === "string" && part.data.startsWith("data:")
|
||||
? part.data
|
||||
: `data:${part.mediaType};base64,${ProviderShared.mediaBytes(part)}`
|
||||
const lowerReasoning = (part: ReasoningPart, store: boolean | undefined): OpenAIResponsesInputItem | undefined => {
|
||||
const openai = part.providerMetadata?.openai
|
||||
if (!ProviderShared.isRecord(openai) || typeof openai.itemId !== "string") return undefined
|
||||
// With store:false, OpenAI only accepts previous reasoning items when the
|
||||
// encrypted state is present. Bare rs_* ids point to non-persisted items.
|
||||
if (store === false && typeof openai.reasoningEncryptedContent !== "string") return undefined
|
||||
return {
|
||||
type: "reasoning",
|
||||
id: openai.itemId,
|
||||
summary: part.text.length > 0 ? [{ type: "summary_text", text: part.text }] : [],
|
||||
encrypted_content:
|
||||
typeof openai.reasoningEncryptedContent === "string"
|
||||
? openai.reasoningEncryptedContent
|
||||
: openai.reasoningEncryptedContent === null
|
||||
? null
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const lowerUserContent = Effect.fn("OpenAIResponses.lowerUserContent")(function* (
|
||||
part: LLMRequest["messages"][number]["content"][number],
|
||||
) {
|
||||
if (part.type === "text") return { type: "input_text" as const, text: part.text }
|
||||
if (part.type === "media" && part.mediaType.startsWith("image/")) {
|
||||
return { type: "input_image" as const, image_url: imageUrl(part) }
|
||||
return { type: "input_image" as const, image_url: ProviderShared.mediaDataUrl(part) }
|
||||
}
|
||||
if (part.type === "media") return yield* invalid("OpenAI Responses user media content only supports images")
|
||||
return yield* ProviderShared.unsupportedContent("OpenAI Responses", "user", ["text", "media"])
|
||||
})
|
||||
|
||||
// Tool results may carry structured text/images. Keep media as provider-native
|
||||
// content instead of JSON-stringifying base64 into a prompt string.
|
||||
const lowerToolResultContentItem = Effect.fn("OpenAIResponses.lowerToolResultContentItem")(function* (
|
||||
item: ToolResultContentPart,
|
||||
) {
|
||||
if (item.type === "text") return { type: "input_text" as const, text: item.text }
|
||||
if (item.mediaType.startsWith("image/"))
|
||||
return {
|
||||
type: "input_image" as const,
|
||||
image_url: ProviderShared.mediaDataUrl(item),
|
||||
}
|
||||
return yield* invalid(`OpenAI Responses tool-result media content only supports images, got ${item.mediaType}`)
|
||||
})
|
||||
|
||||
const lowerToolResultOutput = Effect.fn("OpenAIResponses.lowerToolResultOutput")(function* (part: ToolResultPart) {
|
||||
// Text/json/error results are encoded as a plain string for backward
|
||||
// compatibility with existing cassettes and provider expectations.
|
||||
if (part.result.type !== "content") return ProviderShared.toolResultText(part)
|
||||
return yield* Effect.forEach(part.result.value, lowerToolResultContentItem)
|
||||
})
|
||||
|
||||
const lowerMessages = Effect.fn("OpenAIResponses.lowerMessages")(function* (request: LLMRequest) {
|
||||
const system: OpenAIResponsesInputItem[] =
|
||||
request.system.length === 0 ? [] : [{ role: "system", content: ProviderShared.joinText(request.system) }]
|
||||
const input: OpenAIResponsesInputItem[] = [...system]
|
||||
const store = OpenAIOptions.store(request)
|
||||
|
||||
for (const message of request.messages) {
|
||||
if (message.role === "user") {
|
||||
@@ -235,27 +310,45 @@ const lowerMessages = Effect.fn("OpenAIResponses.lowerMessages")(function* (requ
|
||||
|
||||
if (message.role === "assistant") {
|
||||
const content: TextPart[] = []
|
||||
const flushText = () => {
|
||||
if (content.length === 0) return
|
||||
input.push({ role: "assistant", content: content.map((part) => ({ type: "output_text", text: part.text })) })
|
||||
content.splice(0, content.length)
|
||||
}
|
||||
for (const part of message.content) {
|
||||
if (!ProviderShared.supportsContent(part, ["text", "tool-call"]))
|
||||
return yield* ProviderShared.unsupportedContent("OpenAI Responses", "assistant", ["text", "tool-call"])
|
||||
if (part.type === "text") {
|
||||
content.push(part)
|
||||
continue
|
||||
}
|
||||
if (part.type === "reasoning") {
|
||||
flushText()
|
||||
const reasoning = lowerReasoning(part, store)
|
||||
if (reasoning) input.push(reasoning)
|
||||
continue
|
||||
}
|
||||
if (part.type === "tool-call") {
|
||||
flushText()
|
||||
input.push(lowerToolCall(part))
|
||||
continue
|
||||
}
|
||||
return yield* ProviderShared.unsupportedContent("OpenAI Responses", "assistant", [
|
||||
"text",
|
||||
"reasoning",
|
||||
"tool-call",
|
||||
])
|
||||
}
|
||||
if (content.length > 0)
|
||||
input.push({ role: "assistant", content: content.map((part) => ({ type: "output_text", text: part.text })) })
|
||||
flushText()
|
||||
continue
|
||||
}
|
||||
|
||||
for (const part of message.content) {
|
||||
if (!ProviderShared.supportsContent(part, ["tool-result"]))
|
||||
return yield* ProviderShared.unsupportedContent("OpenAI Responses", "tool", ["tool-result"])
|
||||
input.push({ type: "function_call_output", call_id: part.id, output: ProviderShared.toolResultText(part) })
|
||||
input.push({
|
||||
type: "function_call_output",
|
||||
call_id: part.id,
|
||||
output: yield* lowerToolResultOutput(part),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,6 +460,11 @@ const isHostedToolItem = (
|
||||
): item is OpenAIResponsesStreamItem & { type: HostedToolType; id: string } =>
|
||||
item.type in HOSTED_TOOLS && typeof item.id === "string" && item.id.length > 0
|
||||
|
||||
const isReasoningItem = (
|
||||
item: OpenAIResponsesStreamItem,
|
||||
): item is OpenAIResponsesStreamItem & { type: "reasoning"; id: string } =>
|
||||
item.type === "reasoning" && typeof item.id === "string" && item.id.length > 0
|
||||
|
||||
// Round-trip the full item as the structured result so consumers can extract
|
||||
// outputs / sources / status without re-decoding.
|
||||
const hostedToolResult = (item: OpenAIResponsesStreamItem) => {
|
||||
@@ -428,16 +526,12 @@ const onReasoningDelta = (state: ParserState, event: OpenAIResponsesEvent): Step
|
||||
]
|
||||
}
|
||||
|
||||
const onReasoningDone = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||
const events: LLMEvent[] = []
|
||||
return [
|
||||
{
|
||||
...state,
|
||||
lifecycle: Lifecycle.reasoningEnd(state.lifecycle, events, event.item_id ?? "reasoning-0"),
|
||||
},
|
||||
events,
|
||||
]
|
||||
}
|
||||
// The summary done event does not carry encrypted continuation state. Finish the
|
||||
// common reasoning block when the full reasoning item arrives in output_item.done.
|
||||
const onReasoningDone = (state: ParserState, _event: OpenAIResponsesEvent): StepResult => [state, NO_EVENTS]
|
||||
|
||||
const reasoningMetadata = (item: OpenAIResponsesStreamItem & { id: string }) =>
|
||||
openaiMetadata({ itemId: item.id, reasoningEncryptedContent: item.encrypted_content ?? null })
|
||||
|
||||
const onOutputItemAdded = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||
const item = event.item
|
||||
@@ -518,6 +612,21 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function*
|
||||
return [{ ...state, lifecycle }, events] satisfies StepResult
|
||||
}
|
||||
|
||||
if (isReasoningItem(item)) {
|
||||
const events: LLMEvent[] = []
|
||||
const providerMetadata = reasoningMetadata(item)
|
||||
if (!state.lifecycle.reasoning.has(item.id)) {
|
||||
const lifecycle = Lifecycle.stepStart(state.lifecycle, events)
|
||||
events.push(LLMEvent.reasoningStart({ id: item.id, providerMetadata }))
|
||||
events.push(LLMEvent.reasoningEnd({ id: item.id, providerMetadata }))
|
||||
return [{ ...state, lifecycle }, events] satisfies StepResult
|
||||
}
|
||||
return [
|
||||
{ ...state, lifecycle: Lifecycle.reasoningEnd(state.lifecycle, events, item.id, providerMetadata) },
|
||||
events,
|
||||
] satisfies StepResult
|
||||
}
|
||||
|
||||
return [state, NO_EVENTS] satisfies StepResult
|
||||
})
|
||||
|
||||
@@ -537,14 +646,27 @@ const onResponseFinish = (state: ParserState, event: OpenAIResponsesEvent): Step
|
||||
return [{ ...state, lifecycle }, events]
|
||||
}
|
||||
|
||||
// Build a single human-readable message from whatever the provider supplied.
|
||||
// When both code and message are present, prefix the code so consumers see
|
||||
// the failure mode (e.g. `rate_limit_exceeded: Slow down`) instead of just
|
||||
// the bare message — production rate limits and context-length failures used
|
||||
// to be indistinguishable from generic stream drops.
|
||||
const providerErrorMessage = (event: OpenAIResponsesEvent, fallback: string): string => {
|
||||
const nested = event.response?.error ?? undefined
|
||||
const message = event.message || nested?.message || undefined
|
||||
const code = event.code || nested?.code || undefined
|
||||
if (message && code) return `${code}: ${message}`
|
||||
return message || code || fallback
|
||||
}
|
||||
|
||||
const onResponseFailed = (state: ParserState, event: OpenAIResponsesEvent): StepResult => [
|
||||
state,
|
||||
[LLMEvent.providerError({ message: event.message ?? event.code ?? "OpenAI Responses response failed" })],
|
||||
[LLMEvent.providerError({ message: providerErrorMessage(event, "OpenAI Responses response failed") })],
|
||||
]
|
||||
|
||||
const onError = (state: ParserState, event: OpenAIResponsesEvent): StepResult => [
|
||||
state,
|
||||
[LLMEvent.providerError({ message: event.message ?? event.code ?? "OpenAI Responses stream error" })],
|
||||
[LLMEvent.providerError({ message: providerErrorMessage(event, "OpenAI Responses stream error") })],
|
||||
]
|
||||
|
||||
const step = (state: ParserState, event: OpenAIResponsesEvent) => {
|
||||
|
||||
@@ -80,7 +80,7 @@ export const subtractTokens = (total: number | undefined, subtrahend: number | u
|
||||
*/
|
||||
export const sumTokens = (...values: ReadonlyArray<number | undefined>): number | undefined => {
|
||||
if (values.every((value) => value === undefined)) return undefined
|
||||
return values.reduce<number>((acc, value) => acc + (value ?? 0), 0)
|
||||
return values.reduce((acc: number, value) => acc + (value ?? 0), 0)
|
||||
}
|
||||
|
||||
export const eventError = (route: string, message: string, raw?: string) =>
|
||||
@@ -122,6 +122,16 @@ export const parseToolInput = (route: string, name: string, raw: string) =>
|
||||
export const mediaBytes = (part: MediaPart) =>
|
||||
typeof part.data === "string" ? part.data : Buffer.from(part.data).toString("base64")
|
||||
|
||||
export const mediaBase64 = (part: MediaPart) => {
|
||||
if (typeof part.data !== "string" || !part.data.startsWith("data:")) return mediaBytes(part)
|
||||
return part.data.slice(part.data.indexOf(",") + 1)
|
||||
}
|
||||
|
||||
export const mediaDataUrl = (part: MediaPart) =>
|
||||
typeof part.data === "string" && part.data.startsWith("data:")
|
||||
? part.data
|
||||
: `data:${part.mediaType};base64,${mediaBytes(part)}`
|
||||
|
||||
export const trimBaseUrl = (value: string) => value.replace(/\/+$/, "")
|
||||
|
||||
export const toolResultText = (part: ToolResultPart) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Cause, Context, Effect, Layer, Queue, Stream } from "effect"
|
||||
import { Headers } from "effect/unstable/http"
|
||||
import { Auth } from "../auth"
|
||||
import { LLMError, TransportReason, type LLMRequest } from "../../schema"
|
||||
import { LLMError, TransportReason } from "../../schema"
|
||||
import * as HttpTransport from "./http"
|
||||
import type { Transport } from "./index"
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { LLM, Message, ToolCallPart, ToolDefinition, ToolResultPart, type ContentPart, type Model } from "../src"
|
||||
|
||||
export const basicContinuation = ["system", "user-text", "assistant-text", "user-follow-up"] as const
|
||||
export const toolContinuation = ["tool-call", "tool-result"] as const
|
||||
export const reasoningContinuation = ["assistant-reasoning", "encrypted-reasoning"] as const
|
||||
export const mediaContinuation = ["user-image"] as const
|
||||
export const maximalContinuation = [
|
||||
...basicContinuation,
|
||||
...toolContinuation,
|
||||
...reasoningContinuation,
|
||||
...mediaContinuation,
|
||||
] as const
|
||||
|
||||
export type ContinuationFeature = (typeof maximalContinuation)[number]
|
||||
|
||||
export const nativeOpenAIResponsesContinuation = [
|
||||
...basicContinuation,
|
||||
...toolContinuation,
|
||||
"encrypted-reasoning",
|
||||
...mediaContinuation,
|
||||
] as const satisfies ReadonlyArray<ContinuationFeature>
|
||||
|
||||
export const nativeAnthropicMessagesContinuation = [
|
||||
...basicContinuation,
|
||||
...toolContinuation,
|
||||
"assistant-reasoning",
|
||||
...mediaContinuation,
|
||||
] as const satisfies ReadonlyArray<ContinuationFeature>
|
||||
|
||||
export const continuationTool = ToolDefinition.make({
|
||||
name: "get_weather",
|
||||
description: "Get current weather for a city.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: { city: { type: "string" } },
|
||||
required: ["city"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
})
|
||||
|
||||
export function continuationRequest(input: {
|
||||
readonly id: string
|
||||
readonly model: Model
|
||||
readonly features: ReadonlyArray<ContinuationFeature>
|
||||
readonly image?: string
|
||||
}) {
|
||||
const features = new Set(input.features)
|
||||
const messages = []
|
||||
const firstUser: ContentPart[] = []
|
||||
const firstAssistant: ContentPart[] = []
|
||||
|
||||
if (features.has("user-text")) firstUser.push({ type: "text", text: "What is shown here?" })
|
||||
if (features.has("user-image"))
|
||||
firstUser.push({ type: "media", mediaType: "image/png", data: input.image ?? "AAECAw==" })
|
||||
if (firstUser.length > 0) messages.push(Message.user(firstUser))
|
||||
|
||||
if (features.has("assistant-reasoning"))
|
||||
firstAssistant.push({
|
||||
type: "reasoning",
|
||||
text: "I inspected the previous turn.",
|
||||
providerMetadata: { anthropic: { signature: "sig_continuation_1" } },
|
||||
})
|
||||
if (features.has("encrypted-reasoning"))
|
||||
firstAssistant.push({
|
||||
type: "reasoning",
|
||||
text: "I inspected the previous turn.",
|
||||
providerMetadata: {
|
||||
openai: {
|
||||
itemId: "rs_continuation_1",
|
||||
reasoningEncryptedContent: "encrypted-continuation-state",
|
||||
},
|
||||
},
|
||||
})
|
||||
if (features.has("assistant-text")) firstAssistant.push({ type: "text", text: "It shows a small test image." })
|
||||
if (firstAssistant.length > 0) messages.push(Message.assistant(firstAssistant))
|
||||
|
||||
if (features.has("tool-call")) {
|
||||
messages.push(Message.user("Check the weather in Paris before continuing."))
|
||||
messages.push(
|
||||
Message.assistant([ToolCallPart.make({ id: "call_weather_1", name: "get_weather", input: { city: "Paris" } })]),
|
||||
)
|
||||
}
|
||||
if (features.has("tool-result")) {
|
||||
messages.push(
|
||||
Message.tool(ToolResultPart.make({ id: "call_weather_1", name: "get_weather", result: { temperature: 22 } })),
|
||||
)
|
||||
if (features.has("assistant-text")) messages.push(Message.assistant("Paris is 22 degrees."))
|
||||
}
|
||||
if (features.has("user-follow-up"))
|
||||
messages.push(Message.user("Continue from this conversation in one short sentence."))
|
||||
|
||||
return LLM.request({
|
||||
id: input.id,
|
||||
model: input.model,
|
||||
system: features.has("system") ? "You are concise. Continue from the provided history." : undefined,
|
||||
messages,
|
||||
tools: features.has("tool-call") ? [continuationTool] : [],
|
||||
cache: "none",
|
||||
providerOptions: features.has("encrypted-reasoning")
|
||||
? { openai: { store: false, includeEncryptedReasoning: true, reasoningSummary: "auto" } }
|
||||
: undefined,
|
||||
generation: { maxTokens: 80, temperature: 0 },
|
||||
})
|
||||
}
|
||||
Vendored
+43
File diff suppressed because one or more lines are too long
+42
File diff suppressed because one or more lines are too long
+58
File diff suppressed because one or more lines are too long
@@ -1,4 +1,3 @@
|
||||
import { Stream } from "effect"
|
||||
import { LLMClient } from "../../src/route"
|
||||
import type { Tools } from "../../src/tool"
|
||||
import type { RunOptions } from "../../src/tool-runtime"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { HttpClientRequest } from "effect/unstable/http"
|
||||
import { CacheHint, LLM, LLMError, Message, ToolCallPart, Usage } from "../../src"
|
||||
import { Auth, LLMClient } from "../../src/route"
|
||||
import * as AnthropicMessages from "../../src/protocols/anthropic-messages"
|
||||
import { continuationRequest, nativeAnthropicMessagesContinuation } from "../continuation-scenarios"
|
||||
import { it } from "../lib/effect"
|
||||
import { fixedResponse } from "../lib/http"
|
||||
import { dynamicResponse, fixedResponse } from "../lib/http"
|
||||
import { sseEvents } from "../lib/sse"
|
||||
|
||||
const model = AnthropicMessages.route
|
||||
@@ -22,6 +24,19 @@ const request = LLM.request({
|
||||
generation: { maxTokens: 20, temperature: 0 },
|
||||
})
|
||||
|
||||
type AnthropicToolResult = Extract<
|
||||
AnthropicMessages.AnthropicMessagesBody["messages"][number]["content"][number],
|
||||
{ readonly type: "tool_result" }
|
||||
>
|
||||
|
||||
const expectToolResult = (body: AnthropicMessages.AnthropicMessagesBody): AnthropicToolResult => {
|
||||
const result = body.messages
|
||||
.flatMap((message) => (message.role === "user" ? message.content : []))
|
||||
.find((block): block is AnthropicToolResult => block.type === "tool_result")
|
||||
expect(result).toBeDefined()
|
||||
return result!
|
||||
}
|
||||
|
||||
describe("Anthropic Messages route", () => {
|
||||
it.effect("prepares Anthropic Messages target", () =>
|
||||
Effect.gen(function* () {
|
||||
@@ -40,7 +55,7 @@ describe("Anthropic Messages route", () => {
|
||||
|
||||
it.effect("prepares tool call and tool result messages", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare(
|
||||
const prepared = yield* LLMClient.prepare<AnthropicMessages.AnthropicMessagesBody>(
|
||||
LLM.request({
|
||||
id: "req_tool_result",
|
||||
model,
|
||||
@@ -69,6 +84,131 @@ describe("Anthropic Messages route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
// Regression: screenshot/read tool results must stay structured so base64
|
||||
// image data is not JSON-stringified into `tool_result.content`.
|
||||
it.effect("lowers image tool-result content as structured image blocks", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare<AnthropicMessages.AnthropicMessagesBody>(
|
||||
LLM.request({
|
||||
id: "req_tool_result_image",
|
||||
model,
|
||||
messages: [
|
||||
Message.user("Show me the screenshot."),
|
||||
Message.assistant([ToolCallPart.make({ id: "call_1", name: "read", input: { filePath: "shot.png" } })]),
|
||||
Message.tool({
|
||||
id: "call_1",
|
||||
name: "read",
|
||||
resultType: "content",
|
||||
result: [
|
||||
{ type: "text", text: "Image read successfully" },
|
||||
{ type: "media", mediaType: "image/png", data: "AAECAw==" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
cache: "none",
|
||||
}),
|
||||
)
|
||||
|
||||
expect(expectToolResult(prepared.body).content).toEqual([
|
||||
{ type: "text", text: "Image read successfully" },
|
||||
{ type: "image", source: { type: "base64", media_type: "image/png", data: "AAECAw==" } },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("lowers single-image tool-result content as a structured image block", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare<AnthropicMessages.AnthropicMessagesBody>(
|
||||
LLM.request({
|
||||
id: "req_tool_result_image_only",
|
||||
model,
|
||||
messages: [
|
||||
Message.assistant([ToolCallPart.make({ id: "call_1", name: "screenshot", input: {} })]),
|
||||
Message.tool({
|
||||
id: "call_1",
|
||||
name: "screenshot",
|
||||
resultType: "content",
|
||||
result: [{ type: "media", mediaType: "image/jpeg", data: "/9j/AA==" }],
|
||||
}),
|
||||
],
|
||||
cache: "none",
|
||||
}),
|
||||
)
|
||||
|
||||
expect(expectToolResult(prepared.body).content).toEqual([
|
||||
{ type: "image", source: { type: "base64", media_type: "image/jpeg", data: "/9j/AA==" } },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects non-image media in tool-result content with a clear error", () =>
|
||||
Effect.gen(function* () {
|
||||
const error = yield* LLMClient.prepare(
|
||||
LLM.request({
|
||||
id: "req_tool_result_unsupported_media",
|
||||
model,
|
||||
messages: [
|
||||
Message.assistant([ToolCallPart.make({ id: "call_1", name: "fetch", input: {} })]),
|
||||
Message.tool({
|
||||
id: "call_1",
|
||||
name: "fetch",
|
||||
resultType: "content",
|
||||
result: [{ type: "media", mediaType: "audio/mpeg", data: "AAECAw==" }],
|
||||
}),
|
||||
],
|
||||
cache: "none",
|
||||
}),
|
||||
).pipe(Effect.flip)
|
||||
|
||||
expect(error.message).toContain("Anthropic Messages")
|
||||
expect(error.message).toContain("audio/mpeg")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("prepares the composed native continuation request", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare<AnthropicMessages.AnthropicMessagesBody>(
|
||||
continuationRequest({
|
||||
id: "req_native_continuation_anthropic",
|
||||
model,
|
||||
features: nativeAnthropicMessagesContinuation,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body).toMatchObject({
|
||||
system: [{ type: "text", text: "You are concise. Continue from the provided history." }],
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "What is shown here?" },
|
||||
{ type: "image", source: { type: "base64", media_type: "image/png", data: "AAECAw==" } },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "thinking", thinking: "I inspected the previous turn.", signature: "sig_continuation_1" },
|
||||
{ type: "text", text: "It shows a small test image." },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: [{ type: "text", text: "Check the weather in Paris before continuing." }] },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "tool_use", id: "call_weather_1", name: "get_weather", input: { city: "Paris" } }],
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "tool_result", tool_use_id: "call_weather_1", content: '{"temperature":22}' }],
|
||||
},
|
||||
{ role: "assistant", content: [{ type: "text", text: "Paris is 22 degrees." }] },
|
||||
{ role: "user", content: [{ type: "text", text: "Continue from this conversation in one short sentence." }] },
|
||||
],
|
||||
})
|
||||
expect(prepared.body.tools).toEqual([expect.objectContaining({ name: "get_weather" })])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("lowers preserved Anthropic reasoning signature metadata", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare(
|
||||
@@ -197,7 +337,29 @@ describe("Anthropic Messages route", () => {
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "Overloaded" }])
|
||||
// Prefix the error type so consumers can distinguish overloads, rate
|
||||
// limits, and quota errors without parsing the message string.
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "overloaded_error: Overloaded" }])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("falls back to error type when no message is present", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(fixedResponse(sseEvents({ type: "error", error: { type: "overloaded_error", message: "" } }))),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "overloaded_error" }])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("falls back to a stable default when error payload is absent", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(fixedResponse(sseEvents({ type: "error" }))),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "Anthropic Messages stream error" }])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -392,17 +554,51 @@ describe("Anthropic Messages route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects unsupported user media content", () =>
|
||||
it.effect("continues a conversation with user image content", () =>
|
||||
Effect.gen(function* () {
|
||||
const error = yield* LLMClient.prepare(
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
id: "req_media",
|
||||
model,
|
||||
messages: [Message.user({ type: "media", mediaType: "image/png", data: "AAECAw==" })],
|
||||
messages: [
|
||||
Message.user([
|
||||
{ type: "text", text: "What is in this image?" },
|
||||
{ type: "media", mediaType: "image/png", data: "AAECAw==" },
|
||||
]),
|
||||
],
|
||||
}),
|
||||
).pipe(Effect.flip)
|
||||
).pipe(
|
||||
Effect.provide(
|
||||
dynamicResponse((input) =>
|
||||
Effect.gen(function* () {
|
||||
const web = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
|
||||
expect(yield* Effect.promise(() => web.json())).toMatchObject({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "What is in this image?" },
|
||||
{ type: "image", source: { type: "base64", media_type: "image/png", data: "AAECAw==" } },
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
return input.respond(
|
||||
sseEvents(
|
||||
{ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } },
|
||||
{ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "An image." } },
|
||||
{ type: "content_block_stop", index: 0 },
|
||||
{ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 3 } },
|
||||
{ type: "message_stop" },
|
||||
),
|
||||
{ headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(error.message).toContain("Anthropic Messages user messages only support text content for now")
|
||||
expect(response.text).toBe("An image.")
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Effect } from "effect"
|
||||
import { CacheHint, LLM, Message, ToolCallPart, ToolChoice } from "../../src"
|
||||
import { LLMClient } from "../../src/route"
|
||||
import { AmazonBedrock } from "../../src/providers"
|
||||
import * as BedrockConverse from "../../src/protocols/bedrock-converse"
|
||||
import { it } from "../lib/effect"
|
||||
import { fixedResponse } from "../lib/http"
|
||||
import {
|
||||
|
||||
@@ -84,8 +84,10 @@ describeRecordedGoldenScenarios([
|
||||
scenarios: [
|
||||
{ id: "text", temperature: false },
|
||||
{ id: "reasoning", temperature: false },
|
||||
{ id: "reasoning-continuation", temperature: false },
|
||||
{ id: "tool-call", temperature: false },
|
||||
{ id: "tool-loop", temperature: false },
|
||||
{ id: "image-tool-result", temperature: false, maxTokens: 40 },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -111,7 +113,10 @@ describeRecordedGoldenScenarios([
|
||||
requires: ["ANTHROPIC_API_KEY"],
|
||||
tags: ["flagship"],
|
||||
options: { redactor: Redactor.defaults({ requestHeaders: { allow: ["content-type", "anthropic-version"] } }) },
|
||||
scenarios: [{ id: "tool-loop", temperature: false }],
|
||||
scenarios: [
|
||||
{ id: "tool-loop", temperature: false },
|
||||
{ id: "image-tool-result", temperature: false, maxTokens: 40 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Gemini 2.5 Flash",
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as Azure from "../../src/providers/azure"
|
||||
import * as OpenAI from "../../src/providers/openai"
|
||||
import * as OpenAIResponses from "../../src/protocols/openai-responses"
|
||||
import * as ProviderShared from "../../src/protocols/shared"
|
||||
import { continuationRequest, nativeOpenAIResponsesContinuation } from "../continuation-scenarios"
|
||||
import { it } from "../lib/effect"
|
||||
import { dynamicResponse, fixedResponse } from "../lib/http"
|
||||
import { sseEvents } from "../lib/sse"
|
||||
@@ -25,6 +26,19 @@ const request = LLM.request({
|
||||
|
||||
const configEnv = (env: Record<string, string>) => Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env })))
|
||||
|
||||
type OpenAIToolOutput = Extract<
|
||||
OpenAIResponses.OpenAIResponsesBody["input"][number],
|
||||
{ readonly type: "function_call_output" }
|
||||
>
|
||||
|
||||
const expectToolOutput = (body: OpenAIResponses.OpenAIResponsesBody): OpenAIToolOutput => {
|
||||
const output = body.input.find(
|
||||
(item): item is OpenAIToolOutput => "type" in item && item.type === "function_call_output",
|
||||
)
|
||||
expect(output).toBeDefined()
|
||||
return output!
|
||||
}
|
||||
|
||||
describe("OpenAI Responses route", () => {
|
||||
it.effect("prepares OpenAI Responses target", () =>
|
||||
Effect.gen(function* () {
|
||||
@@ -247,6 +261,127 @@ describe("OpenAI Responses route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
// Regression: screenshot/read tool results must stay structured so base64
|
||||
// image data is not JSON-stringified into `function_call_output.output`.
|
||||
it.effect("lowers image tool-result content as structured input_image items", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||
LLM.request({
|
||||
id: "req_tool_result_image",
|
||||
model,
|
||||
messages: [
|
||||
Message.user("Show me the screenshot."),
|
||||
Message.assistant([ToolCallPart.make({ id: "call_1", name: "read", input: { filePath: "shot.png" } })]),
|
||||
Message.tool({
|
||||
id: "call_1",
|
||||
name: "read",
|
||||
resultType: "content",
|
||||
result: [
|
||||
{ type: "text", text: "Image read successfully" },
|
||||
{ type: "media", mediaType: "image/png", data: "AAECAw==" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(expectToolOutput(prepared.body).output).toEqual([
|
||||
{ type: "input_text", text: "Image read successfully" },
|
||||
{ type: "input_image", image_url: "data:image/png;base64,AAECAw==" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("lowers single-image tool-result content as structured input_image array", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||
LLM.request({
|
||||
id: "req_tool_result_image_only",
|
||||
model,
|
||||
messages: [
|
||||
Message.assistant([ToolCallPart.make({ id: "call_1", name: "screenshot", input: {} })]),
|
||||
Message.tool({
|
||||
id: "call_1",
|
||||
name: "screenshot",
|
||||
resultType: "content",
|
||||
result: [{ type: "media", mediaType: "image/png", data: "AAECAw==" }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(expectToolOutput(prepared.body).output).toEqual([
|
||||
{ type: "input_image", image_url: "data:image/png;base64,AAECAw==" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects non-image media in tool-result content with a clear error", () =>
|
||||
Effect.gen(function* () {
|
||||
const error = yield* LLMClient.prepare(
|
||||
LLM.request({
|
||||
id: "req_tool_result_unsupported_media",
|
||||
model,
|
||||
messages: [
|
||||
Message.assistant([ToolCallPart.make({ id: "call_1", name: "fetch", input: {} })]),
|
||||
Message.tool({
|
||||
id: "call_1",
|
||||
name: "fetch",
|
||||
resultType: "content",
|
||||
result: [{ type: "media", mediaType: "audio/mpeg", data: "AAECAw==" }],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
).pipe(Effect.flip)
|
||||
|
||||
expect(error.message).toContain("OpenAI Responses")
|
||||
expect(error.message).toContain("audio/mpeg")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("prepares the composed native continuation request", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||
continuationRequest({
|
||||
id: "req_native_continuation_openai",
|
||||
model,
|
||||
features: nativeOpenAIResponsesContinuation,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body).toMatchObject({
|
||||
input: [
|
||||
{ role: "system", content: "You are concise. Continue from the provided history." },
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "input_text", text: "What is shown here?" },
|
||||
{ type: "input_image", image_url: "data:image/png;base64,AAECAw==" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "reasoning",
|
||||
id: "rs_continuation_1",
|
||||
encrypted_content: "encrypted-continuation-state",
|
||||
summary: [{ type: "summary_text", text: "I inspected the previous turn." }],
|
||||
},
|
||||
{ role: "assistant", content: [{ type: "output_text", text: "It shows a small test image." }] },
|
||||
{ role: "user", content: [{ type: "input_text", text: "Check the weather in Paris before continuing." }] },
|
||||
{ type: "function_call", call_id: "call_weather_1", name: "get_weather", arguments: '{"city":"Paris"}' },
|
||||
{ type: "function_call_output", call_id: "call_weather_1", output: '{"temperature":22}' },
|
||||
{ role: "assistant", content: [{ type: "output_text", text: "Paris is 22 degrees." }] },
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "input_text", text: "Continue from this conversation in one short sentence." }],
|
||||
},
|
||||
],
|
||||
include: ["reasoning.encrypted_content"],
|
||||
store: false,
|
||||
})
|
||||
expect(prepared.body.tools).toEqual([expect.objectContaining({ type: "function", name: "get_weather" })])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("maps OpenAI provider options to Responses options", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||
@@ -380,6 +515,172 @@ describe("OpenAI Responses route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("preserves encrypted reasoning metadata for continuation", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(
|
||||
sseEvents(
|
||||
{ type: "response.reasoning_summary_text.delta", item_id: "rs_1", delta: "thinking" },
|
||||
{
|
||||
type: "response.output_item.done",
|
||||
item: {
|
||||
type: "reasoning",
|
||||
id: "rs_1",
|
||||
encrypted_content: "encrypted-state",
|
||||
summary: [{ type: "summary_text", text: "thinking" }],
|
||||
},
|
||||
},
|
||||
{ type: "response.completed", response: { id: "resp_1" } },
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.events).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: "reasoning-end",
|
||||
id: "rs_1",
|
||||
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("continues a stateless reasoning conversation", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
id: "req_reasoning_continue",
|
||||
model,
|
||||
messages: [
|
||||
Message.user("What changed?"),
|
||||
Message.assistant([
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Checked the previous diff.",
|
||||
providerMetadata: {
|
||||
openai: {
|
||||
itemId: "rs_1",
|
||||
reasoningEncryptedContent: "encrypted-state",
|
||||
},
|
||||
},
|
||||
},
|
||||
{ type: "text", text: "The parser changed." },
|
||||
]),
|
||||
Message.user("Summarize it."),
|
||||
],
|
||||
}),
|
||||
).pipe(
|
||||
Effect.provide(
|
||||
dynamicResponse((input) =>
|
||||
Effect.gen(function* () {
|
||||
const web = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
|
||||
expect(yield* Effect.promise(() => web.json())).toMatchObject({
|
||||
input: [
|
||||
{ role: "user", content: [{ type: "input_text", text: "What changed?" }] },
|
||||
{
|
||||
type: "reasoning",
|
||||
id: "rs_1",
|
||||
encrypted_content: "encrypted-state",
|
||||
summary: [{ type: "summary_text", text: "Checked the previous diff." }],
|
||||
},
|
||||
{ role: "assistant", content: [{ type: "output_text", text: "The parser changed." }] },
|
||||
{ role: "user", content: [{ type: "input_text", text: "Summarize it." }] },
|
||||
],
|
||||
})
|
||||
return input.respond(
|
||||
sseEvents(
|
||||
{ type: "response.output_text.delta", item_id: "msg_1", delta: "Parser now round-trips reasoning." },
|
||||
{ type: "response.completed", response: { id: "resp_1" } },
|
||||
),
|
||||
{ headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.text).toBe("Parser now round-trips reasoning.")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("preserves assistant content order around reasoning items", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||
LLM.request({
|
||||
id: "req_reasoning_order",
|
||||
model,
|
||||
messages: [
|
||||
Message.assistant([
|
||||
{ type: "text", text: "Before." },
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Checked order.",
|
||||
providerMetadata: {
|
||||
openai: {
|
||||
itemId: "rs_1",
|
||||
reasoningEncryptedContent: "encrypted-state",
|
||||
},
|
||||
},
|
||||
},
|
||||
{ type: "text", text: "After." },
|
||||
]),
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body.input).toEqual([
|
||||
{ role: "assistant", content: [{ type: "output_text", text: "Before." }] },
|
||||
{
|
||||
type: "reasoning",
|
||||
id: "rs_1",
|
||||
encrypted_content: "encrypted-state",
|
||||
summary: [{ type: "summary_text", text: "Checked order." }],
|
||||
},
|
||||
{ role: "assistant", content: [{ type: "output_text", text: "After." }] },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("skips non-persisted reasoning ids without encrypted state", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* LLMClient.prepare(
|
||||
LLM.request({
|
||||
id: "req_reasoning_without_encrypted_state",
|
||||
model,
|
||||
messages: [
|
||||
Message.user("What changed?"),
|
||||
Message.assistant([
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Checked the previous diff.",
|
||||
providerMetadata: {
|
||||
openai: {
|
||||
itemId: "rs_1",
|
||||
reasoningEncryptedContent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ type: "text", text: "The parser changed." },
|
||||
]),
|
||||
Message.user("Summarize it."),
|
||||
],
|
||||
providerOptions: { openai: { store: false } },
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body).toMatchObject({
|
||||
input: [
|
||||
{ role: "user", content: [{ type: "input_text", text: "What changed?" }] },
|
||||
{ role: "assistant", content: [{ type: "output_text", text: "The parser changed." }] },
|
||||
{ role: "user", content: [{ type: "input_text", text: "Summarize it." }] },
|
||||
],
|
||||
store: false,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("assembles streamed function call input", () =>
|
||||
Effect.gen(function* () {
|
||||
const body = sseEvents(
|
||||
@@ -576,7 +877,11 @@ describe("OpenAI Responses route", () => {
|
||||
Effect.provide(fixedResponse(sseEvents({ type: "error", code: "rate_limit_exceeded", message: "Slow down" }))),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "Slow down" }])
|
||||
// Prefix the code so consumers see the failure mode, not just the
|
||||
// sometimes-generic provider message. The bare message alone meant
|
||||
// production errors like rate limits were indistinguishable from
|
||||
// unrelated stream failures.
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "rate_limit_exceeded: Slow down" }])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -590,6 +895,99 @@ describe("OpenAI Responses route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("falls back to error code when message is empty", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(fixedResponse(sseEvents({ type: "error", code: "internal_error", message: "" }))),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "internal_error" }])
|
||||
}),
|
||||
)
|
||||
|
||||
// Regression: `response.failed` carries the failure details under
|
||||
// `response.error`, not at the top level. The previous handler only
|
||||
// checked top-level `message`/`code` and so always emitted the bare
|
||||
// "OpenAI Responses response failed" string, hiding the real cause.
|
||||
it.effect("surfaces response.failed details from response.error", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(
|
||||
sseEvents({
|
||||
type: "response.failed",
|
||||
response: {
|
||||
id: "resp_failed_1",
|
||||
error: { code: "server_error", message: "Upstream model unavailable" },
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "server_error: Upstream model unavailable" }])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("surfaces response.failed code when no nested message is present", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(
|
||||
sseEvents({
|
||||
type: "response.failed",
|
||||
response: { id: "resp_failed_2", error: { code: "invalid_prompt" } },
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "invalid_prompt" }])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("surfaces error event details even when they arrive nested under response.error", () =>
|
||||
Effect.gen(function* () {
|
||||
// Some OpenAI-compatible proxies and older SDK versions wrap the
|
||||
// top-level error fields into a nested `response.error` payload
|
||||
// when they bubble up an HTTP error as an SSE `error` event. Honour
|
||||
// both shapes so the user still sees the underlying cause instead
|
||||
// of the catch-all string.
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(
|
||||
sseEvents({
|
||||
type: "error",
|
||||
response: { error: { code: "context_length_exceeded", message: "prompt too long" } },
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "context_length_exceeded: prompt too long" }])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("falls back to a stable default when both error and response are absent", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(fixedResponse(sseEvents({ type: "error" }))),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "OpenAI Responses stream error" }])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("falls back to a stable default when response.failed has no error payload", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(fixedResponse(sseEvents({ type: "response.failed", response: { id: "resp_failed_3" } }))),
|
||||
)
|
||||
|
||||
expect(response.events).toEqual([{ type: "provider-error", message: "OpenAI Responses response failed" }])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("fails HTTP provider errors before stream parsing", () =>
|
||||
Effect.gen(function* () {
|
||||
const error = yield* LLMClient.generate(request).pipe(
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { HttpRecorder } from "@opencode-ai/http-recorder"
|
||||
import { describe } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import type { Model } from "../src"
|
||||
import { goldenScenarioTags, runGoldenScenario, type GoldenScenarioID } from "./recorded-scenarios"
|
||||
import { goldenScenarioTags, goldenScenarioTitle, runGoldenScenario, type GoldenScenarioID } from "./recorded-scenarios"
|
||||
import { recordedTests } from "./recorded-test"
|
||||
import { kebab } from "./recorded-utils"
|
||||
|
||||
@@ -35,14 +35,6 @@ type TargetInput = {
|
||||
|
||||
const scenarioInput = (input: ScenarioInput) => (typeof input === "string" ? { id: input } : input)
|
||||
|
||||
const scenarioTitle = (id: GoldenScenarioID) => {
|
||||
if (id === "text") return "streams text"
|
||||
if (id === "tool-call") return "streams tool call"
|
||||
if (id === "reasoning") return "uses reasoning"
|
||||
if (id === "image") return "reads image text"
|
||||
return "drives a tool loop"
|
||||
}
|
||||
|
||||
const defaultPrefix = (target: TargetInput) => {
|
||||
if (target.prefix) return target.prefix
|
||||
const transport = target.transport === "websocket" ? "-websocket" : ""
|
||||
@@ -77,7 +69,7 @@ const runTarget = (target: TargetInput) => {
|
||||
describe(`${target.name} recorded`, () => {
|
||||
target.scenarios.forEach((raw) => {
|
||||
const input = scenarioInput(raw)
|
||||
const name = input.name ?? scenarioTitle(input.id)
|
||||
const name = input.name ?? goldenScenarioTitle(input.id)
|
||||
recorded.effect.with(
|
||||
name,
|
||||
{
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { expect } from "bun:test"
|
||||
import { Effect, Schema, Stream } from "effect"
|
||||
import { LLM, LLMEvent, LLMResponse, Message, ToolChoice, ToolDefinition, type LLMRequest, type Model } from "../src"
|
||||
import {
|
||||
LLM,
|
||||
LLMEvent,
|
||||
LLMResponse,
|
||||
Message,
|
||||
ToolChoice,
|
||||
ToolDefinition,
|
||||
type ContentPart,
|
||||
type FinishReason,
|
||||
type LLMRequest,
|
||||
type Model,
|
||||
} from "../src"
|
||||
import { LLMClient } from "../src/route"
|
||||
import { tool } from "../src/tool"
|
||||
|
||||
@@ -39,47 +50,6 @@ export const weatherRuntimeTool = tool({
|
||||
),
|
||||
})
|
||||
|
||||
export const textRequest = (input: {
|
||||
readonly id: string
|
||||
readonly model: Model
|
||||
readonly prompt?: string
|
||||
readonly maxTokens?: number
|
||||
readonly temperature?: number | false
|
||||
}) =>
|
||||
LLM.request({
|
||||
id: input.id,
|
||||
model: input.model,
|
||||
system: "You are concise.",
|
||||
prompt: input.prompt ?? "Reply with exactly: Hello!",
|
||||
cache: "none",
|
||||
providerOptions:
|
||||
input.model.route.id === "gemini" ? { gemini: { thinkingConfig: { thinkingBudget: 0 } } } : undefined,
|
||||
generation:
|
||||
input.temperature === false
|
||||
? { maxTokens: input.maxTokens ?? 80 }
|
||||
: { maxTokens: input.maxTokens ?? 80, temperature: input.temperature ?? 0 },
|
||||
})
|
||||
|
||||
export const weatherToolRequest = (input: {
|
||||
readonly id: string
|
||||
readonly model: Model
|
||||
readonly maxTokens?: number
|
||||
readonly temperature?: number | false
|
||||
}) =>
|
||||
LLM.request({
|
||||
id: input.id,
|
||||
model: input.model,
|
||||
system: "Call tools exactly as requested.",
|
||||
prompt: "Call get_weather with city exactly Paris.",
|
||||
tools: [weatherTool],
|
||||
toolChoice: ToolChoice.make(weatherTool),
|
||||
cache: "none",
|
||||
generation:
|
||||
input.temperature === false
|
||||
? { maxTokens: input.maxTokens ?? 80 }
|
||||
: { maxTokens: input.maxTokens ?? 80, temperature: input.temperature ?? 0 },
|
||||
})
|
||||
|
||||
export const weatherToolLoopRequest = (input: {
|
||||
readonly id: string
|
||||
readonly model: Model
|
||||
@@ -116,52 +86,6 @@ const restroomImage = () =>
|
||||
Effect.map((bytes) => Buffer.from(bytes).toString("base64")),
|
||||
)
|
||||
|
||||
export const imageRequest = (input: {
|
||||
readonly id: string
|
||||
readonly model: Model
|
||||
readonly image: string
|
||||
readonly maxTokens?: number
|
||||
readonly temperature?: number | false
|
||||
}) =>
|
||||
LLM.request({
|
||||
id: input.id,
|
||||
model: input.model,
|
||||
system: "Read images carefully. Reply only with the visible text.",
|
||||
messages: [
|
||||
Message.user([
|
||||
{
|
||||
type: "text",
|
||||
text: "The image contains exactly three lowercase English words. Read them left to right and reply with only those words.",
|
||||
},
|
||||
{ type: "media", mediaType: "image/png", data: input.image },
|
||||
]),
|
||||
],
|
||||
cache: "none",
|
||||
generation:
|
||||
input.temperature === false
|
||||
? { maxTokens: input.maxTokens ?? 20 }
|
||||
: { maxTokens: input.maxTokens ?? 20, temperature: input.temperature ?? 0 },
|
||||
})
|
||||
|
||||
export const reasoningRequest = (input: {
|
||||
readonly id: string
|
||||
readonly model: Model
|
||||
readonly maxTokens?: number
|
||||
readonly temperature?: number | false
|
||||
}) =>
|
||||
LLM.request({
|
||||
id: input.id,
|
||||
model: input.model,
|
||||
system: "Show concise reasoning when the provider supports visible reasoning summaries.",
|
||||
prompt: "Think briefly, then reply exactly with: Hello!",
|
||||
cache: "none",
|
||||
providerOptions: { openai: { reasoningEffort: "low", reasoningSummary: "auto" } },
|
||||
generation:
|
||||
input.temperature === false
|
||||
? { maxTokens: input.maxTokens ?? 120 }
|
||||
: { maxTokens: input.maxTokens ?? 120, temperature: input.temperature ?? 0 },
|
||||
})
|
||||
|
||||
export const runWeatherToolLoop = (request: LLMRequest) =>
|
||||
LLMClient.stream({
|
||||
request,
|
||||
@@ -212,8 +136,6 @@ export const expectGoldenWeatherToolLoop = (events: ReadonlyArray<LLMEvent>) =>
|
||||
expect(LLMResponse.text({ events }).trim()).toMatch(/^Paris is sunny\.?$/)
|
||||
}
|
||||
|
||||
export type GoldenScenarioID = "text" | "tool-call" | "tool-loop" | "image" | "reasoning"
|
||||
|
||||
export interface GoldenScenarioContext {
|
||||
readonly id: string
|
||||
readonly model: Model
|
||||
@@ -223,6 +145,9 @@ export interface GoldenScenarioContext {
|
||||
|
||||
const generate = (request: LLMRequest) => LLMClient.generate(request)
|
||||
|
||||
const generation = (context: GoldenScenarioContext, maxTokens: number) =>
|
||||
context.temperature === false ? { maxTokens } : { maxTokens, temperature: context.temperature ?? 0 }
|
||||
|
||||
const normalizeImageText = (value: string) =>
|
||||
value
|
||||
.toLowerCase()
|
||||
@@ -230,75 +155,234 @@ const normalizeImageText = (value: string) =>
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
|
||||
export const goldenScenarioTags = (id: GoldenScenarioID) => {
|
||||
if (id === "text") return ["text", "golden"]
|
||||
if (id === "tool-call") return ["tool", "tool-call", "golden"]
|
||||
if (id === "image") return ["media", "image", "vision", "golden"]
|
||||
if (id === "reasoning") return ["reasoning", "golden"]
|
||||
return ["tool", "tool-loop", "golden"]
|
||||
const encryptedReasoningOptions = {
|
||||
openai: {
|
||||
store: false,
|
||||
includeEncryptedReasoning: true,
|
||||
reasoningEffort: "low",
|
||||
reasoningSummary: "auto",
|
||||
},
|
||||
} as const
|
||||
|
||||
type AssistantTextExpectation = string | RegExp
|
||||
|
||||
type UserStep = { readonly type: "user"; readonly content: Message.ContentInput }
|
||||
type AssistantStep = {
|
||||
readonly type: "assistant"
|
||||
readonly text?: AssistantTextExpectation
|
||||
readonly toolCall?: { readonly name: string; readonly input: unknown }
|
||||
readonly reasoning?: "openai-encrypted"
|
||||
readonly id?: string
|
||||
readonly system?: string
|
||||
readonly maxTokens?: number
|
||||
readonly finish?: FinishReason
|
||||
readonly tools?: LLM.RequestInput["tools"]
|
||||
readonly toolChoice?: LLM.RequestInput["toolChoice"]
|
||||
readonly providerOptions?: LLMRequest["providerOptions"]
|
||||
readonly assert?: (response: LLMResponse) => void
|
||||
}
|
||||
type ConversationStep = UserStep | AssistantStep
|
||||
|
||||
const user = (content: Message.ContentInput): ConversationStep => ({ type: "user", content })
|
||||
|
||||
const assistant = {
|
||||
expectText: (
|
||||
text: AssistantTextExpectation,
|
||||
options?: Omit<AssistantStep, "type" | "text" | "reasoning" | "toolCall">,
|
||||
): ConversationStep => ({ type: "assistant", text, ...options }),
|
||||
expectToolCall: (
|
||||
name: string,
|
||||
input: unknown,
|
||||
options?: Omit<AssistantStep, "type" | "text" | "reasoning" | "toolCall" | "finish">,
|
||||
): ConversationStep => ({ type: "assistant", toolCall: { name, input }, finish: "tool-calls", ...options }),
|
||||
expectEncryptedReasoningText: (
|
||||
text: AssistantTextExpectation,
|
||||
options?: Omit<AssistantStep, "type" | "text" | "reasoning" | "toolCall" | "providerOptions">,
|
||||
): ConversationStep => ({
|
||||
type: "assistant",
|
||||
text,
|
||||
reasoning: "openai-encrypted",
|
||||
providerOptions: encryptedReasoningOptions,
|
||||
...options,
|
||||
}),
|
||||
}
|
||||
|
||||
export const runGoldenScenario = (id: GoldenScenarioID, context: GoldenScenarioContext) =>
|
||||
const assertAssistantText = (actual: string, expected: AssistantTextExpectation) => {
|
||||
if (typeof expected === "string") {
|
||||
expect(actual.trim()).toBe(expected)
|
||||
return
|
||||
}
|
||||
expect(actual.trim()).toMatch(expected)
|
||||
}
|
||||
|
||||
const assertAssistantToolCall = (response: LLMResponse, expected: NonNullable<AssistantStep["toolCall"]>) => {
|
||||
expect(response.toolCalls).toMatchObject([
|
||||
{ type: "tool-call", id: expect.any(String), name: expected.name, input: expected.input },
|
||||
])
|
||||
}
|
||||
|
||||
// The generated golden scenarios only model one assistant shape at a time:
|
||||
// encrypted reasoning + text, text, or tool call. Keep mixed interleavings in
|
||||
// focused protocol tests where event order can be asserted directly.
|
||||
const assistantMessageFromResponse = (response: LLMResponse, step: AssistantStep) => {
|
||||
const content: ContentPart[] = []
|
||||
if (step.reasoning === "openai-encrypted") {
|
||||
const reasoning = response.events.find(
|
||||
(event): event is Extract<LLMEvent, { readonly type: "reasoning-end" }> =>
|
||||
LLMEvent.is.reasoningEnd(event) && typeof event.providerMetadata?.openai?.itemId === "string",
|
||||
)
|
||||
if (!reasoning) throw new Error("OpenAI Responses did not return reasoning metadata")
|
||||
expect(reasoning.providerMetadata?.openai?.reasoningEncryptedContent).toEqual(expect.any(String))
|
||||
content.push({ type: "reasoning", text: response.reasoning, providerMetadata: reasoning.providerMetadata })
|
||||
}
|
||||
|
||||
if (response.text.length > 0) content.push({ type: "text", text: response.text })
|
||||
content.push(...response.toolCalls)
|
||||
return Message.assistant(content)
|
||||
}
|
||||
|
||||
const runGeneratedConversation = (context: GoldenScenarioContext, steps: ReadonlyArray<ConversationStep>) =>
|
||||
Effect.gen(function* () {
|
||||
if (id === "text") {
|
||||
const messages: Message[] = []
|
||||
let generated = 0
|
||||
for (const step of steps) {
|
||||
if (step.type === "user") {
|
||||
messages.push(Message.user(step.content))
|
||||
continue
|
||||
}
|
||||
|
||||
generated += 1
|
||||
const response = yield* generate(
|
||||
textRequest({
|
||||
id: context.id,
|
||||
LLM.request({
|
||||
id: step.id ? `${context.id}_${step.id}` : `${context.id}_${generated}`,
|
||||
model: context.model,
|
||||
prompt: "Reply exactly with: Hello!",
|
||||
maxTokens: context.maxTokens ?? 40,
|
||||
temperature: context.temperature,
|
||||
system: step.system,
|
||||
cache: "none",
|
||||
messages,
|
||||
tools: step.tools,
|
||||
toolChoice: step.toolChoice,
|
||||
providerOptions: step.providerOptions,
|
||||
generation: generation(context, step.maxTokens ?? context.maxTokens ?? 80),
|
||||
}),
|
||||
)
|
||||
expect(response.text.trim()).toMatch(/^Hello!?$/)
|
||||
expectFinish(response.events, "stop")
|
||||
return
|
||||
if (step.text !== undefined) assertAssistantText(response.text, step.text)
|
||||
if (step.toolCall) assertAssistantToolCall(response, step.toolCall)
|
||||
step.assert?.(response)
|
||||
expectFinish(response.events, step.finish ?? "stop")
|
||||
messages.push(assistantMessageFromResponse(response, step))
|
||||
}
|
||||
})
|
||||
|
||||
if (id === "tool-call") {
|
||||
const response = yield* generate(
|
||||
weatherToolRequest({
|
||||
id: context.id,
|
||||
model: context.model,
|
||||
maxTokens: context.maxTokens ?? 80,
|
||||
temperature: context.temperature,
|
||||
}),
|
||||
)
|
||||
expectWeatherToolCall(response)
|
||||
expectFinish(response.events, "tool-calls")
|
||||
return
|
||||
}
|
||||
const runTextScenario = (context: GoldenScenarioContext) =>
|
||||
runGeneratedConversation(context, [
|
||||
user("Reply exactly with: Hello!"),
|
||||
assistant.expectText(/^Hello!?$/, {
|
||||
system: "You are concise.",
|
||||
maxTokens: context.maxTokens ?? 40,
|
||||
providerOptions:
|
||||
context.model.route.id === "gemini" ? { gemini: { thinkingConfig: { thinkingBudget: 0 } } } : undefined,
|
||||
}),
|
||||
])
|
||||
|
||||
if (id === "image") {
|
||||
const response = yield* generate(
|
||||
imageRequest({
|
||||
id: context.id,
|
||||
model: context.model,
|
||||
image: yield* restroomImage(),
|
||||
maxTokens: context.maxTokens ?? 20,
|
||||
temperature: context.temperature,
|
||||
}),
|
||||
)
|
||||
expect(normalizeImageText(response.text)).toBe(RESTROOM_IMAGE_TEXT)
|
||||
expectFinish(response.events, "stop")
|
||||
return
|
||||
}
|
||||
const runToolCallScenario = (context: GoldenScenarioContext) =>
|
||||
runGeneratedConversation(context, [
|
||||
user("Call get_weather with city exactly Paris."),
|
||||
assistant.expectToolCall(
|
||||
weatherToolName,
|
||||
{ city: "Paris" },
|
||||
{
|
||||
system: "Call tools exactly as requested.",
|
||||
tools: [weatherTool],
|
||||
toolChoice: ToolChoice.make(weatherTool),
|
||||
maxTokens: context.maxTokens ?? 80,
|
||||
},
|
||||
),
|
||||
])
|
||||
|
||||
if (id === "reasoning") {
|
||||
const response = yield* generate(
|
||||
reasoningRequest({
|
||||
id: context.id,
|
||||
model: context.model,
|
||||
maxTokens: context.maxTokens ?? 120,
|
||||
temperature: context.temperature,
|
||||
}),
|
||||
)
|
||||
expect(response.text.trim()).toMatch(/^Hello!?$/)
|
||||
expect(response.usage?.reasoningTokens ?? 0).toBeGreaterThan(0)
|
||||
expectFinish(response.events, "stop")
|
||||
return
|
||||
}
|
||||
const runImageScenario = (context: GoldenScenarioContext) =>
|
||||
Effect.gen(function* () {
|
||||
yield* runGeneratedConversation(context, [
|
||||
user([
|
||||
{
|
||||
type: "text",
|
||||
text: "The image contains exactly three lowercase English words. Read them left to right and reply with only those words.",
|
||||
},
|
||||
{ type: "media", mediaType: "image/png", data: yield* restroomImage() },
|
||||
]),
|
||||
assistant.expectText(/.+/, {
|
||||
system: "Read images carefully. Reply only with the visible text.",
|
||||
maxTokens: context.maxTokens ?? 20,
|
||||
assert: (response) => expect(normalizeImageText(response.text)).toBe(RESTROOM_IMAGE_TEXT),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
// Reproduces a tool-result image round trip: a tool returns image bytes, and
|
||||
// the next model turn must receive provider-native image content instead of a
|
||||
// JSON-stringified base64 blob.
|
||||
const screenshotToolName = "read_screenshot"
|
||||
const runImageToolResultScenario = (context: GoldenScenarioContext) =>
|
||||
Effect.gen(function* () {
|
||||
const image = yield* restroomImage()
|
||||
const response = yield* generate(
|
||||
LLM.request({
|
||||
id: `${context.id}_image_tool_result`,
|
||||
model: context.model,
|
||||
system: "Read images carefully. Reply only with the visible text, lowercase, no punctuation.",
|
||||
cache: "none",
|
||||
generation: generation(context, context.maxTokens ?? 40),
|
||||
messages: [
|
||||
Message.user("Use the read_screenshot tool, then reply with the words shown."),
|
||||
Message.assistant([{ type: "tool-call", id: "call_screenshot_1", name: screenshotToolName, input: {} }]),
|
||||
Message.tool({
|
||||
id: "call_screenshot_1",
|
||||
name: screenshotToolName,
|
||||
resultType: "content",
|
||||
result: [
|
||||
{ type: "text", text: "Image read successfully" },
|
||||
{ type: "media", mediaType: "image/png", data: image },
|
||||
],
|
||||
}),
|
||||
],
|
||||
tools: [
|
||||
ToolDefinition.make({
|
||||
name: screenshotToolName,
|
||||
description: "Capture a screenshot of the current screen.",
|
||||
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
expectFinish(response.events, "stop")
|
||||
expect(normalizeImageText(response.text)).toBe(RESTROOM_IMAGE_TEXT)
|
||||
})
|
||||
|
||||
const runReasoningScenario = (context: GoldenScenarioContext) =>
|
||||
runGeneratedConversation(context, [
|
||||
user("Think briefly, then reply exactly with: Hello!"),
|
||||
assistant.expectText(/^Hello!?$/, {
|
||||
system: "Show concise reasoning when the provider supports visible reasoning summaries.",
|
||||
providerOptions: { openai: { reasoningEffort: "low", reasoningSummary: "auto" } },
|
||||
maxTokens: context.maxTokens ?? 120,
|
||||
assert: (response) => expect(response.usage?.reasoningTokens ?? 0).toBeGreaterThan(0),
|
||||
}),
|
||||
])
|
||||
|
||||
const runReasoningContinuationScenario = (context: GoldenScenarioContext) =>
|
||||
runGeneratedConversation(context, [
|
||||
user("Think briefly, then reply exactly with: Hello!"),
|
||||
assistant.expectEncryptedReasoningText(/^Hello!?$/, {
|
||||
id: "first",
|
||||
system: "Show concise reasoning when the provider supports visible reasoning summaries.",
|
||||
maxTokens: context.maxTokens ?? 120,
|
||||
}),
|
||||
user("Now reply exactly with: Done."),
|
||||
assistant.expectText(/^Done\.?$/, { id: "second", maxTokens: 40, providerOptions: encryptedReasoningOptions }),
|
||||
])
|
||||
|
||||
const runToolLoopScenario = (context: GoldenScenarioContext) =>
|
||||
Effect.gen(function* () {
|
||||
expectGoldenWeatherToolLoop(
|
||||
yield* runWeatherToolLoop(
|
||||
goldenWeatherToolLoopRequest({
|
||||
@@ -311,6 +395,30 @@ export const runGoldenScenario = (id: GoldenScenarioID, context: GoldenScenarioC
|
||||
)
|
||||
})
|
||||
|
||||
const goldenScenarios = {
|
||||
text: { title: "streams text", tags: ["text", "golden"], run: runTextScenario },
|
||||
"tool-call": { title: "streams tool call", tags: ["tool", "tool-call", "golden"], run: runToolCallScenario },
|
||||
"tool-loop": { title: "drives a tool loop", tags: ["tool", "tool-loop", "golden"], run: runToolLoopScenario },
|
||||
image: { title: "reads image text", tags: ["media", "image", "vision", "golden"], run: runImageScenario },
|
||||
"image-tool-result": {
|
||||
title: "reads image returned from tool result",
|
||||
tags: ["media", "image", "vision", "tool", "tool-result", "golden"],
|
||||
run: runImageToolResultScenario,
|
||||
},
|
||||
reasoning: { title: "uses reasoning", tags: ["reasoning", "golden"], run: runReasoningScenario },
|
||||
"reasoning-continuation": {
|
||||
title: "continues encrypted reasoning",
|
||||
tags: ["reasoning", "continuation", "encrypted-reasoning", "golden"],
|
||||
run: runReasoningContinuationScenario,
|
||||
},
|
||||
} as const
|
||||
|
||||
export type GoldenScenarioID = keyof typeof goldenScenarios
|
||||
export const goldenScenarioTitle = (id: GoldenScenarioID) => goldenScenarios[id].title
|
||||
export const goldenScenarioTags = (id: GoldenScenarioID) => [...goldenScenarios[id].tags]
|
||||
export const runGoldenScenario = (id: GoldenScenarioID, context: GoldenScenarioContext) =>
|
||||
goldenScenarios[id].run(context)
|
||||
|
||||
const usageSummary = (usage: LLMResponse["usage"] | undefined) => {
|
||||
if (!usage) return undefined
|
||||
return Object.fromEntries(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.6",
|
||||
"version": "1.15.9",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -135,7 +135,7 @@
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "3.1.0",
|
||||
"gitlab-ai-provider": "6.6.0",
|
||||
"gitlab-ai-provider": "6.7.0",
|
||||
"glob": "13.0.5",
|
||||
"google-auth-library": "10.5.0",
|
||||
"gray-matter": "4.0.3",
|
||||
|
||||
@@ -42,9 +42,7 @@ import type { ACPConfig } from "./types"
|
||||
import { ACPRuntime } from "./runtime"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ModelID, ProviderID } from "../provider/schema"
|
||||
import { Installation } from "@/installation"
|
||||
import { MessageV2 } from "@/session/message-v2"
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigMCP } from "@/config/mcp"
|
||||
import { Todo } from "@/session/todo"
|
||||
import { Result, Schema } from "effect"
|
||||
|
||||
@@ -69,7 +69,7 @@ export interface Interface {
|
||||
whenToUse: string
|
||||
systemPrompt: string
|
||||
},
|
||||
Provider.ModelNotFoundError
|
||||
Provider.DefaultModelError
|
||||
>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EOL } from "os"
|
||||
import { basename } from "path"
|
||||
import { Effect } from "effect"
|
||||
import { Cause, Effect } from "effect"
|
||||
import { Agent } from "../../../agent/agent"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { Session } from "@/session/session"
|
||||
@@ -80,7 +80,21 @@ const run = Effect.fn("Cli.debug.agent.body")(function* (
|
||||
const getAvailableTools = Effect.fn("Cli.debug.agent.getAvailableTools")(function* (agent: Agent.Info) {
|
||||
const provider = yield* Provider.Service
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const model = agent.model ?? (yield* provider.defaultModel())
|
||||
const model =
|
||||
agent.model ??
|
||||
(yield* provider.defaultModel().pipe(
|
||||
Effect.matchCauseEffect({
|
||||
onSuccess: Effect.succeed,
|
||||
onFailure: (cause) => {
|
||||
const error = Cause.squash(cause) as Provider.DefaultModelError
|
||||
if (error instanceof Provider.ModelNotFoundError) {
|
||||
return fail(`Model not found: ${error.providerID}/${error.modelID}`)
|
||||
}
|
||||
if (error instanceof Provider.NoModelsError) return fail(`No models found for provider ${error.providerID}`)
|
||||
return fail("No providers found")
|
||||
},
|
||||
}),
|
||||
))
|
||||
return yield* registry.tools({ ...model, agent })
|
||||
})
|
||||
|
||||
@@ -133,7 +147,20 @@ const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(functio
|
||||
? agent.model
|
||||
: yield* Effect.gen(function* () {
|
||||
const provider = yield* Provider.Service
|
||||
return yield* provider.defaultModel()
|
||||
return yield* provider.defaultModel().pipe(
|
||||
Effect.matchCauseEffect({
|
||||
onSuccess: Effect.succeed,
|
||||
onFailure: (cause) => {
|
||||
const error = Cause.squash(cause) as Provider.DefaultModelError
|
||||
if (error instanceof Provider.ModelNotFoundError) {
|
||||
return fail(`Model not found: ${error.providerID}/${error.modelID}`)
|
||||
}
|
||||
if (error instanceof Provider.NoModelsError)
|
||||
return fail(`No models found for provider ${error.providerID}`)
|
||||
return fail("No providers found")
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
const now = Date.now()
|
||||
const message: MessageV2.Assistant = {
|
||||
|
||||
@@ -12,7 +12,6 @@ import { McpOAuthProvider } from "../../mcp/oauth-provider"
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigMCP } from "../../config/mcp"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { Installation } from "../../installation"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
|
||||
@@ -25,7 +25,7 @@ import { DialogProvider, useDialog } from "@tui/ui/dialog"
|
||||
import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
|
||||
import { ErrorComponent } from "@tui/component/error-component"
|
||||
import { PluginRouteMissing } from "@tui/component/plugin-route-missing"
|
||||
import { ProjectProvider } from "@tui/context/project"
|
||||
import { ProjectProvider, useProject } from "@tui/context/project"
|
||||
import { EditorContextProvider } from "@tui/context/editor"
|
||||
import { useEvent } from "@tui/context/event"
|
||||
import { SDKProvider, useSDK } from "@tui/context/sdk"
|
||||
@@ -279,6 +279,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
const themeState = useTheme()
|
||||
const { theme, mode, setMode, locked, lock, unlock } = themeState
|
||||
const sync = useSync()
|
||||
const project = useProject()
|
||||
const exit = useExit()
|
||||
const promptRef = usePromptRef()
|
||||
const routes: RouteMap = new Map()
|
||||
@@ -447,6 +448,13 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
)
|
||||
|
||||
const connected = useConnected()
|
||||
const currentWorktreeWorkspace = createMemo(() => {
|
||||
const workspaceID = project.workspace.current()
|
||||
if (!workspaceID) return
|
||||
const workspace = project.workspace.get(workspaceID)
|
||||
if (workspace?.type !== "worktree" || !workspace.directory) return
|
||||
return workspace
|
||||
})
|
||||
const appCommands = createMemo(() =>
|
||||
[
|
||||
{
|
||||
@@ -483,6 +491,20 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "workspace.copy_path",
|
||||
title: "Copy worktree path",
|
||||
category: "Workspace",
|
||||
enabled: () => currentWorktreeWorkspace() !== undefined,
|
||||
run: async () => {
|
||||
const workspace = currentWorktreeWorkspace()
|
||||
if (!workspace?.directory) return
|
||||
await Clipboard.copy(workspace.directory)
|
||||
.then(() => toast.show({ message: "Copied worktree path", variant: "info" }))
|
||||
.catch(toast.error)
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
...Array.from({ length: 9 }, (_, i) => ({
|
||||
name: `session.quick_switch.${i + 1}`,
|
||||
title: `Switch to session in quick slot ${i + 1}`,
|
||||
|
||||
@@ -543,22 +543,8 @@ export function Autocomplete(props: {
|
||||
),
|
||||
)
|
||||
|
||||
function insertSlashCommand(name: string) {
|
||||
const newText = `/${name} `
|
||||
const cursor = props.input().logicalCursor
|
||||
props.input().deleteRange(0, 0, cursor.row, cursor.col)
|
||||
props.input().insertText(newText)
|
||||
props.input().cursorOffset = Bun.stringWidth(newText)
|
||||
}
|
||||
|
||||
const commands = createMemo((): AutocompleteOption[] => {
|
||||
const results: AutocompleteOption[] = slashes().map((command) => {
|
||||
if (!command.input) return command
|
||||
return {
|
||||
...command,
|
||||
onSelect: () => insertSlashCommand(command.name),
|
||||
}
|
||||
})
|
||||
const results: AutocompleteOption[] = [...slashes()]
|
||||
|
||||
for (const serverCommand of sync.data.command) {
|
||||
if (serverCommand.source === "skill") continue
|
||||
@@ -566,7 +552,13 @@ export function Autocomplete(props: {
|
||||
results.push({
|
||||
display: "/" + serverCommand.name + label,
|
||||
description: serverCommand.description,
|
||||
onSelect: () => insertSlashCommand(serverCommand.name),
|
||||
onSelect: () => {
|
||||
const newText = "/" + serverCommand.name + " "
|
||||
const cursor = props.input().logicalCursor
|
||||
props.input().deleteRange(0, 0, cursor.row, cursor.col)
|
||||
props.input().insertText(newText)
|
||||
props.input().cursorOffset = Bun.stringWidth(newText)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -59,14 +59,7 @@ import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable"
|
||||
import { useArgs } from "@tui/context/args"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { type WorkspaceStatus } from "../workspace-label"
|
||||
import {
|
||||
OPENCODE_BASE_MODE,
|
||||
useBindings,
|
||||
useCommandShortcut,
|
||||
useCommandSlashes,
|
||||
useLeaderActive,
|
||||
useOpencodeKeymap,
|
||||
} from "../../keymap"
|
||||
import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap"
|
||||
import { useTuiConfig } from "../../context/tui-config"
|
||||
|
||||
export type PromptProps = {
|
||||
@@ -158,7 +151,6 @@ export function Prompt(props: PromptProps) {
|
||||
const history = usePromptHistory()
|
||||
const stash = usePromptStash()
|
||||
const keymap = useOpencodeKeymap()
|
||||
const slashCommands = useCommandSlashes()
|
||||
const agentShortcut = useCommandShortcut("agent.cycle")
|
||||
const paletteShortcut = useCommandShortcut("command.palette.show")
|
||||
const renderer = useRenderer()
|
||||
@@ -1140,8 +1132,6 @@ export function Prompt(props: PromptProps) {
|
||||
]
|
||||
: []
|
||||
|
||||
const slash = parseSlashCommand(inputText)
|
||||
|
||||
if (store.mode === "shell") {
|
||||
void sdk.client.session.shell({
|
||||
sessionID,
|
||||
@@ -1153,13 +1143,25 @@ export function Prompt(props: PromptProps) {
|
||||
command: inputText,
|
||||
})
|
||||
setStore("mode", "normal")
|
||||
} else if (slash && slashCommand(slash.name)?.input) {
|
||||
slashCommand(slash.name)?.onSelect()
|
||||
} else if (slash && sync.data.command.some((x) => x.name === slash.name)) {
|
||||
} else if (
|
||||
inputText.startsWith("/") &&
|
||||
iife(() => {
|
||||
const firstLine = inputText.split("\n")[0]
|
||||
const command = firstLine.split(" ")[0].slice(1)
|
||||
return sync.data.command.some((x) => x.name === command)
|
||||
})
|
||||
) {
|
||||
// Parse command from first line, preserve multi-line content in arguments
|
||||
const firstLineEnd = inputText.indexOf("\n")
|
||||
const firstLine = firstLineEnd === -1 ? inputText : inputText.slice(0, firstLineEnd)
|
||||
const [command, ...firstLineArgs] = firstLine.split(" ")
|
||||
const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1)
|
||||
const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
|
||||
|
||||
void sdk.client.session.command({
|
||||
sessionID,
|
||||
command: slash.name,
|
||||
arguments: slash.arguments,
|
||||
command: command.slice(1),
|
||||
arguments: args,
|
||||
agent: agent.name,
|
||||
model: `${selectedModel.providerID}/${selectedModel.modelID}`,
|
||||
messageID,
|
||||
@@ -1218,22 +1220,6 @@ export function Prompt(props: PromptProps) {
|
||||
input.clear()
|
||||
return true
|
||||
}
|
||||
|
||||
function parseSlashCommand(text: string) {
|
||||
if (!text.startsWith("/")) return
|
||||
const firstLineEnd = text.indexOf("\n")
|
||||
const firstLine = firstLineEnd === -1 ? text : text.slice(0, firstLineEnd)
|
||||
const [command, ...firstLineArgs] = firstLine.split(" ")
|
||||
const restOfInput = firstLineEnd === -1 ? "" : text.slice(firstLineEnd + 1)
|
||||
return {
|
||||
name: command.slice(1),
|
||||
arguments: firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : ""),
|
||||
}
|
||||
}
|
||||
|
||||
function slashCommand(name: string) {
|
||||
return slashCommands().find((item) => item.name === name || item.aliases?.includes(`/${name}`))
|
||||
}
|
||||
const exit = useExit()
|
||||
|
||||
function pasteText(text: string, virtualText: string) {
|
||||
@@ -1452,7 +1438,10 @@ export function Prompt(props: PromptProps) {
|
||||
})
|
||||
|
||||
const spinnerDef = createMemo(() => {
|
||||
const agent = local.agent.current()
|
||||
const agent =
|
||||
status().type !== "idle"
|
||||
? (local.agent.list().find((a) => a.name === lastUserMessage()?.agent) ?? local.agent.current())
|
||||
: local.agent.current()
|
||||
const color = agent ? local.agent.color(agent.name) : theme.border
|
||||
return {
|
||||
frames: createFrames({
|
||||
|
||||
@@ -62,14 +62,16 @@ export const Definitions = {
|
||||
diff_close: keybind("escape,q", "Close diff viewer"),
|
||||
diff_toggle: keybind("enter,space", "Toggle diff viewer item"),
|
||||
diff_expand: keybind("right", "Expand diff viewer item"),
|
||||
diff_expand_all: keybind("E", "Expand all diff viewer folders"),
|
||||
diff_collapse: keybind("left", "Collapse diff viewer item"),
|
||||
diff_switch_focus: keybind("tab", "Switch diff viewer focus"),
|
||||
diff_next_file: keybind("n", "Jump to next diff file"),
|
||||
diff_previous_file: keybind("p", "Jump to previous diff file"),
|
||||
diff_toggle_file_tree: keybind("b", "Toggle diff viewer file tree"),
|
||||
diff_single_patch: keybind("s", "Toggle single patch view"),
|
||||
diff_switch_diff: keybind("d", "Switch diff viewer source"),
|
||||
diff_switch_source: keybind("d", "Switch diff viewer source"),
|
||||
diff_toggle_view: keybind("v", "Toggle diff viewer split or unified view"),
|
||||
diff_help: keybind("?", "Show more diff viewer shortcuts"),
|
||||
|
||||
editor_open: keybind("<leader>e", "Open external editor"),
|
||||
theme_list: keybind("<leader>t", "List available themes"),
|
||||
@@ -259,14 +261,16 @@ export const CommandMap = {
|
||||
diff_close: "diff.close",
|
||||
diff_toggle: "diff.toggle",
|
||||
diff_expand: "diff.expand",
|
||||
diff_expand_all: "diff.expand_all",
|
||||
diff_collapse: "diff.collapse",
|
||||
diff_switch_focus: "diff.switch_focus",
|
||||
diff_next_file: "diff.next_file",
|
||||
diff_previous_file: "diff.previous_file",
|
||||
diff_toggle_file_tree: "diff.toggle_file_tree",
|
||||
diff_single_patch: "diff.single_patch",
|
||||
diff_switch_diff: "diff.switch_diff",
|
||||
diff_switch_source: "diff.switch_source",
|
||||
diff_toggle_view: "diff.toggle_view",
|
||||
diff_help: "diff.help",
|
||||
editor_open: "prompt.editor",
|
||||
theme_list: "theme.switch",
|
||||
theme_switch_mode: "theme.switch_mode",
|
||||
|
||||
@@ -49,6 +49,7 @@ type Theme = TuiThemeCurrent & {
|
||||
_hasSelectedListItemText: boolean
|
||||
}
|
||||
type ThemeColor = Exclude<keyof TuiThemeCurrent, "thinkingOpacity">
|
||||
type SyntaxStyleOverrides = Record<string, { italic?: boolean }>
|
||||
|
||||
export function selectedForeground(theme: Theme, bg?: RGBA): RGBA {
|
||||
// If theme explicitly defines selectedListItemText, use it
|
||||
@@ -718,16 +719,18 @@ export function generateSyntax(theme: Theme) {
|
||||
return SyntaxStyle.fromTheme(getSyntaxRules(theme))
|
||||
}
|
||||
|
||||
export function generateSubtleSyntax(theme: Theme) {
|
||||
export function generateSubtleSyntax(theme: Theme, overrides?: SyntaxStyleOverrides) {
|
||||
const rules = getSyntaxRules(theme)
|
||||
return SyntaxStyle.fromTheme(
|
||||
rules.map((rule) => {
|
||||
const override = rule.scope.reduce((acc, scope) => ({ ...acc, ...overrides?.[scope] }), {})
|
||||
if (rule.style.foreground) {
|
||||
const fg = rule.style.foreground
|
||||
return {
|
||||
...rule,
|
||||
style: {
|
||||
...rule.style,
|
||||
...override,
|
||||
foreground: RGBA.fromInts(
|
||||
Math.round(fg.r * 255),
|
||||
Math.round(fg.g * 255),
|
||||
|
||||
+57
@@ -5,6 +5,7 @@
|
||||
|
||||
export type FileTreeItem = {
|
||||
readonly file: string
|
||||
readonly status?: "added" | "deleted" | "modified"
|
||||
}
|
||||
|
||||
export type FileTreeNode = {
|
||||
@@ -125,6 +126,21 @@ export function moveFileTreeSelection(rows: readonly FileTreeRow[], selected: nu
|
||||
return rows[Math.max(0, Math.min(rows.length - 1, index + offset))]!.id
|
||||
}
|
||||
|
||||
export function moveFileTreeSelectionToFirstChild(rows: readonly FileTreeRow[], selected: number | undefined) {
|
||||
const index = selected === undefined ? -1 : rows.findIndex((row) => row.id === selected)
|
||||
const row = index === -1 ? undefined : rows[index]
|
||||
if (row?.kind !== "directory") return selected
|
||||
const child = rows[index + 1]
|
||||
return child && child.depth > row.depth ? child.id : selected
|
||||
}
|
||||
|
||||
export function moveFileTreeSelectionToParent(rows: readonly FileTreeRow[], selected: number | undefined) {
|
||||
const index = selected === undefined ? -1 : rows.findIndex((row) => row.id === selected)
|
||||
const row = index === -1 ? undefined : rows[index]
|
||||
if (!row || row.depth === 0) return selected
|
||||
return rows.findLast((item, itemIndex) => itemIndex < index && item.depth < row.depth)?.id ?? selected
|
||||
}
|
||||
|
||||
export function moveFileTreeSelectionToFile(
|
||||
rows: readonly FileTreeRow[],
|
||||
selected: number | undefined,
|
||||
@@ -141,6 +157,39 @@ export function moveFileTreeSelectionToFile(
|
||||
return next?.id ?? (offset < 0 ? fileRows[0]!.id : fileRows[fileRows.length - 1]!.id)
|
||||
}
|
||||
|
||||
export function fileTreeFileSelection(tree: FileTree, fileIndex: number) {
|
||||
const node = tree.nodes.find((item) => item.kind === "file" && item.fileIndex === fileIndex)
|
||||
if (!node) return undefined
|
||||
return {
|
||||
highlightedNode: node.id,
|
||||
expandedNodes: fileTreeParentDirectories(tree, node.id),
|
||||
}
|
||||
}
|
||||
|
||||
export function singlePatchFileIndex(
|
||||
selected: number | undefined,
|
||||
active: number | undefined,
|
||||
current: number | undefined,
|
||||
first: number | undefined,
|
||||
) {
|
||||
return selected ?? active ?? current ?? first
|
||||
}
|
||||
|
||||
export function orderedPatchFileIndexes(rows: readonly FileTreeRow[]) {
|
||||
return rows.flatMap((row) => (row.fileIndex === undefined ? [] : [row.fileIndex]))
|
||||
}
|
||||
|
||||
export function showDiffViewerFileTree(showFileTree: boolean, fileCount: number) {
|
||||
return showFileTree && fileCount > 0
|
||||
}
|
||||
|
||||
export function movePatchFileIndex(fileIndexes: readonly number[], current: number | undefined, offset: number) {
|
||||
if (fileIndexes.length === 0) return undefined
|
||||
const index = current === undefined ? -1 : fileIndexes.indexOf(current)
|
||||
if (index === -1) return fileIndexes[0]
|
||||
return fileIndexes[Math.max(0, Math.min(fileIndexes.length - 1, index + offset))]
|
||||
}
|
||||
|
||||
export function allExpandedFileTreeDirectories(tree: FileTree) {
|
||||
return new Set(tree.nodes.filter((node) => node.kind === "directory").map((node) => node.id))
|
||||
}
|
||||
@@ -173,3 +222,11 @@ function addFileTreeNode(nodes: FileTreeNode[], roots: number[], input: Omit<Fil
|
||||
else nodes[input.parent]!.children.push(id)
|
||||
return id
|
||||
}
|
||||
|
||||
function fileTreeParentDirectories(tree: FileTree, id: number) {
|
||||
const result = new Set<number>()
|
||||
for (let parent = tree.nodes[id]?.parent; parent !== undefined; parent = tree.nodes[parent]?.parent) {
|
||||
result.add(parent)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+73
-44
@@ -1,31 +1,37 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import type { ColorInput, ScrollBoxRenderable } from "@opentui/core"
|
||||
import type { ColorInput, RGBA, ScrollBoxRenderable } from "@opentui/core"
|
||||
import { Locale } from "@/util/locale"
|
||||
import { tint } from "@tui/context/theme"
|
||||
import { createEffect, createMemo, For, Match, Switch } from "solid-js"
|
||||
import { buildFileTree, flattenFileTree, type FileTreeItem } from "./diff-viewer-file-tree-utils"
|
||||
import { buildFileTree, flattenFileTree, type FileTreeItem, type FileTreeRow } from "./diff-viewer-file-tree-utils"
|
||||
import { Panel } from "./diff-viewer-ui"
|
||||
|
||||
const FILE_TREE_WIDTH = 32
|
||||
const FILE_TREE_HORIZONTAL_PADDING = 2
|
||||
const FILE_TREE_STATUS_WIDTH = 2
|
||||
|
||||
export type DiffViewerFileTreeTheme = {
|
||||
readonly background: ColorInput
|
||||
readonly background: RGBA
|
||||
readonly backgroundPanel: ColorInput
|
||||
readonly backgroundElement: ColorInput
|
||||
readonly primary: ColorInput
|
||||
readonly secondary: ColorInput
|
||||
readonly selectedListItemText: ColorInput
|
||||
readonly text: ColorInput
|
||||
readonly textMuted: ColorInput
|
||||
readonly text: RGBA
|
||||
readonly textMuted: RGBA
|
||||
readonly error: ColorInput
|
||||
}
|
||||
|
||||
export type DiffViewerFileTreeProps = {
|
||||
readonly width: number
|
||||
readonly files: readonly FileTreeItem[]
|
||||
readonly loading: boolean
|
||||
readonly error: unknown
|
||||
readonly theme: DiffViewerFileTreeTheme
|
||||
readonly focused?: boolean
|
||||
readonly highlightedNode?: number
|
||||
readonly selectedFileIndex?: number
|
||||
readonly reviewedFileNames?: ReadonlySet<string>
|
||||
readonly expandedNodes?: ReadonlySet<number>
|
||||
readonly onRowClick?: (row: FileTreeRow) => void
|
||||
}
|
||||
|
||||
export function DiffViewerFileTree(props: DiffViewerFileTreeProps) {
|
||||
@@ -43,21 +49,12 @@ export function DiffViewerFileTree(props: DiffViewerFileTreeProps) {
|
||||
requestAnimationFrame(scrollSelectedIntoView)
|
||||
})
|
||||
|
||||
const fadedColor = () => tint(props.theme.text, props.theme.background, 0.75)
|
||||
|
||||
return (
|
||||
<box
|
||||
width={FILE_TREE_WIDTH}
|
||||
flexShrink={0}
|
||||
backgroundColor={props.theme.backgroundPanel}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
paddingTop={1}
|
||||
gap={1}
|
||||
minHeight={0}
|
||||
>
|
||||
<Panel border="both" width={props.width}>
|
||||
<scrollbox
|
||||
ref={(element: ScrollBoxRenderable) => (scroll = element)}
|
||||
flexGrow={1}
|
||||
minHeight={0}
|
||||
verticalScrollbarOptions={{ visible: false }}
|
||||
horizontalScrollbarOptions={{ visible: false }}
|
||||
>
|
||||
@@ -70,29 +67,25 @@ export function DiffViewerFileTree(props: DiffViewerFileTreeProps) {
|
||||
</Match>
|
||||
<Match when={props.files.length > 0}>
|
||||
<For each={rows()}>
|
||||
{(row) => {
|
||||
{(row, index) => {
|
||||
const highlighted = () => props.focused && props.highlightedNode === row.id
|
||||
const prefix = () =>
|
||||
`${" ".repeat(row.depth)}${row.kind === "directory" ? (props.expandedNodes && !props.expandedNodes.has(row.id) ? "▸ " : "▾ ") : " "}`
|
||||
const selected = () => row.fileIndex !== undefined && props.selectedFileIndex === row.fileIndex
|
||||
const reviewed = () => {
|
||||
const file = row.fileIndex === undefined ? undefined : props.files[row.fileIndex]?.file
|
||||
return file !== undefined && (props.reviewedFileNames?.has(file) ?? false)
|
||||
}
|
||||
const prefix = () => fileTreeRowPrefix(rows(), index(), row, props.expandedNodes)
|
||||
const status = () => fileTreeRowStatus(row, props.files, reviewed())
|
||||
const name = () =>
|
||||
Locale.truncate(
|
||||
row.name,
|
||||
Math.max(1, FILE_TREE_WIDTH - FILE_TREE_HORIZONTAL_PADDING - prefix().length),
|
||||
)
|
||||
Locale.truncate(row.name, Math.max(1, props.width - FILE_TREE_STATUS_WIDTH - prefix().length))
|
||||
return (
|
||||
<box flexDirection="row" width="100%">
|
||||
<text
|
||||
fg={
|
||||
highlighted()
|
||||
? props.theme.background
|
||||
: row.kind === "directory"
|
||||
? props.theme.textMuted
|
||||
: props.theme.text
|
||||
}
|
||||
bg={highlighted() ? props.theme.primary : undefined}
|
||||
wrapMode="none"
|
||||
flexShrink={0}
|
||||
>
|
||||
<box
|
||||
flexDirection="row"
|
||||
width="100%"
|
||||
backgroundColor={highlighted() ? props.theme.primary : undefined}
|
||||
onMouseUp={() => props.onRowClick?.(row)}
|
||||
>
|
||||
<text fg={highlighted() ? props.theme.background : fadedColor()} wrapMode="none" flexShrink={0}>
|
||||
{prefix()}
|
||||
</text>
|
||||
<box flexGrow={1} minWidth={0}>
|
||||
@@ -100,16 +93,24 @@ export function DiffViewerFileTree(props: DiffViewerFileTreeProps) {
|
||||
fg={
|
||||
highlighted()
|
||||
? props.theme.background
|
||||
: row.kind === "directory"
|
||||
? props.theme.textMuted
|
||||
: props.theme.text
|
||||
: selected()
|
||||
? props.theme.primary
|
||||
: reviewed() || row.kind === "directory"
|
||||
? props.theme.textMuted
|
||||
: props.theme.text
|
||||
}
|
||||
bg={highlighted() ? props.theme.primary : undefined}
|
||||
wrapMode="none"
|
||||
>
|
||||
{name()}
|
||||
</text>
|
||||
</box>
|
||||
<text
|
||||
fg={highlighted() ? props.theme.background : props.theme.textMuted}
|
||||
wrapMode="none"
|
||||
flexShrink={0}
|
||||
>
|
||||
{status()}
|
||||
</text>
|
||||
</box>
|
||||
)
|
||||
}}
|
||||
@@ -117,7 +118,7 @@ export function DiffViewerFileTree(props: DiffViewerFileTreeProps) {
|
||||
</Match>
|
||||
</Switch>
|
||||
</scrollbox>
|
||||
</box>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -131,3 +132,31 @@ function scrollFileTreeRowIntoView(scroll: ScrollBoxRenderable | undefined, inde
|
||||
scroll.scrollTo(index - scroll.viewport.height + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function fileTreeRowPrefix(
|
||||
rows: readonly FileTreeRow[],
|
||||
index: number,
|
||||
row: FileTreeRow,
|
||||
expandedNodes: ReadonlySet<number> | undefined,
|
||||
) {
|
||||
const indentation = Array.from({ length: row.depth }, (_, depth) => {
|
||||
if (depth === 0 && !hasLaterSibling(rows, 0, 0)) return " "
|
||||
return hasLaterSibling(rows, index, depth) ? "│ " : " "
|
||||
}).join("")
|
||||
const topRoot = index === 0 && row.depth === 0
|
||||
const branch = topRoot ? " " : hasLaterSibling(rows, index, row.depth) ? "├─ " : "└─ "
|
||||
const marker = row.kind === "directory" ? (expandedNodes && !expandedNodes.has(row.id) ? "▸ " : "▾ ") : ""
|
||||
|
||||
return `${indentation}${branch}${marker}`
|
||||
}
|
||||
|
||||
function hasLaterSibling(rows: readonly FileTreeRow[], index: number, depth: number) {
|
||||
return rows.slice(index + 1).find((row) => row.depth <= depth)?.depth === depth
|
||||
}
|
||||
|
||||
function fileTreeRowStatus(row: FileTreeRow, files: readonly FileTreeItem[], reviewed: boolean) {
|
||||
if (row.fileIndex === undefined) return ""
|
||||
const status = files[row.fileIndex]?.status
|
||||
const marker = status === "modified" ? "M" : status === "added" ? "A" : status === "deleted" ? "D" : "?"
|
||||
return `${reviewed ? "✓" : " "}${marker}`.padStart(FILE_TREE_STATUS_WIDTH)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import type { BorderSides, ColorInput } from "@opentui/core"
|
||||
import type { JSX } from "@opentui/solid"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { createContext, Show, splitProps, useContext } from "solid-js"
|
||||
|
||||
export type Axis = "x" | "y"
|
||||
export type SeparatorEdge = "edge" | "edge-in" | "edge-out"
|
||||
export type PanelBorder = "start" | "end" | "both" | "none"
|
||||
|
||||
const PanelGroupContext = createContext<{ axis: Axis }>()
|
||||
|
||||
function crossAxis(axis: Axis) {
|
||||
return axis === "x" ? "y" : "x"
|
||||
}
|
||||
|
||||
function usePanelGroup() {
|
||||
return useContext(PanelGroupContext)
|
||||
}
|
||||
|
||||
export function PanelGroup(props: JSX.IntrinsicElements["box"] & { axis: Axis }) {
|
||||
const [local, boxProps] = splitProps(props, ["axis", "children"])
|
||||
return (
|
||||
<PanelGroupContext.Provider value={{ axis: local.axis }}>
|
||||
<box minWidth={0} minHeight={0} padding={0} flexDirection={local.axis === "x" ? "row" : "column"} {...boxProps}>
|
||||
{local.children}
|
||||
</box>
|
||||
</PanelGroupContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function Panel(props: Omit<JSX.IntrinsicElements["box"], "border"> & { border?: PanelBorder }) {
|
||||
const group = usePanelGroup()
|
||||
const { theme } = useTheme()
|
||||
const [local, boxProps] = splitProps(props, ["border"])
|
||||
const border = local.border ?? "start"
|
||||
const borderProps =
|
||||
border === "none"
|
||||
? {}
|
||||
: {
|
||||
border: panelBorderSides(group?.axis ?? "y", border),
|
||||
borderColor: theme.border,
|
||||
}
|
||||
|
||||
return (
|
||||
<box
|
||||
minWidth={0}
|
||||
minHeight={0}
|
||||
flexDirection={crossAxis(group?.axis || "y") === "x" ? "row" : "column"}
|
||||
{...borderProps}
|
||||
{...boxProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function panelBorderSides(axis: Axis, border: Exclude<PanelBorder, "none">): BorderSides[] {
|
||||
if (axis === "x") return border === "both" ? ["top", "bottom"] : [border === "start" ? "top" : "bottom"]
|
||||
return border === "both" ? ["left", "right"] : [border === "start" ? "left" : "right"]
|
||||
}
|
||||
|
||||
export function Separator(props: { axis?: Axis; color?: ColorInput; start?: SeparatorEdge; end?: SeparatorEdge }) {
|
||||
const group = usePanelGroup()
|
||||
const { theme } = useTheme()
|
||||
const color = () => props.color ?? theme.border
|
||||
const axis = () => props.axis ?? crossAxis(group?.axis ?? "y")
|
||||
if (axis() === "y") {
|
||||
return (
|
||||
<Show
|
||||
when={props.start || props.end}
|
||||
fallback={<box width={1} flexShrink={0} border={["left"]} borderColor={color()} />}
|
||||
>
|
||||
<box width={1} flexShrink={0} flexDirection="column">
|
||||
<Show when={props.start}>{(edge) => <text fg={color()}>{verticalEdge(edge(), "start")}</text>}</Show>
|
||||
<box flexGrow={1} border={["left"]} borderColor={color()} />
|
||||
<Show when={props.end}>{(edge) => <text fg={color()}>{verticalEdge(edge(), "end")}</text>}</Show>
|
||||
</box>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Show
|
||||
when={props.start || props.end}
|
||||
fallback={<box height={1} flexShrink={0} border={["top"]} borderColor={color()} />}
|
||||
>
|
||||
<box height={1} flexShrink={0} flexDirection="row">
|
||||
<Show when={props.start}>{(edge) => <text fg={color()}>{horizontalEdge(edge(), "start")}</text>}</Show>
|
||||
<box flexGrow={1} border={["top"]} borderColor={color()} />
|
||||
<Show when={props.end}>{(edge) => <text fg={color()}>{horizontalEdge(edge(), "end")}</text>}</Show>
|
||||
</box>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function horizontalEdge(edge: SeparatorEdge, side: "start" | "end") {
|
||||
if (edge === "edge") return side === "start" ? "├" : "┤"
|
||||
if (edge === "edge-in") return "┴"
|
||||
return "┬"
|
||||
}
|
||||
|
||||
function verticalEdge(edge: SeparatorEdge, side: "start" | "end") {
|
||||
if (edge === "edge") return side === "start" ? "┬" : "┴"
|
||||
if (edge === "edge-in") return "┤"
|
||||
return "├"
|
||||
}
|
||||
@@ -1,29 +1,44 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { TuiPlugin, TuiPluginApi, TuiRouteCurrent } from "@opencode-ai/plugin/tui"
|
||||
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
|
||||
import type { BoxRenderable, ScrollBoxRenderable } from "@opentui/core"
|
||||
import { TextAttributes, type BorderSides, type BoxRenderable, type ScrollBoxRenderable } from "@opentui/core"
|
||||
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
||||
import { useBindings, useCommandShortcut } from "@tui/keymap"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import path from "path"
|
||||
import { createEffect, createMemo, createResource, createSignal, For, Match, Show, Switch } from "solid-js"
|
||||
import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js"
|
||||
import { DiffViewerFileTree } from "./diff-viewer-file-tree"
|
||||
import { Panel, PanelGroup, Separator } from "./diff-viewer-ui"
|
||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
import {
|
||||
allExpandedFileTreeDirectories,
|
||||
buildFileTree,
|
||||
fileTreeFileSelection,
|
||||
type FileTreeRow,
|
||||
flattenFileTree,
|
||||
moveFileTreeSelection,
|
||||
moveFileTreeSelectionToFile,
|
||||
moveFileTreeSelectionToFirstChild,
|
||||
moveFileTreeSelectionToParent,
|
||||
movePatchFileIndex,
|
||||
orderedPatchFileIndexes,
|
||||
setFileTreeDirectoryExpanded,
|
||||
showDiffViewerFileTree,
|
||||
singlePatchFileIndex,
|
||||
toggleFileTreeDirectory,
|
||||
} from "./diff-viewer-file-tree-utils"
|
||||
|
||||
const ROUTE = "diff"
|
||||
const MIN_SPLIT_WIDTH = 100
|
||||
const FILE_TREE_WIDTH = 32
|
||||
const PLAIN_TEXT_FILETYPE = "opencode-plain-text"
|
||||
const WORKING_TREE_DIFF_CONTEXT_LINES = 12
|
||||
const KV_SHOW_FILE_TREE = "diff_viewer_show_file_tree"
|
||||
const KV_SINGLE_PATCH = "diff_viewer_single_patch"
|
||||
const KV_VIEW = "diff_viewer_view"
|
||||
type DiffMode = "git" | "last-turn"
|
||||
type DiffViewerFocus = "patches" | "files"
|
||||
type DiffView = "split" | "unified"
|
||||
|
||||
type DiffFile = {
|
||||
readonly file: string
|
||||
@@ -55,13 +70,22 @@ function filetype(input?: string) {
|
||||
return language
|
||||
}
|
||||
|
||||
function storedView(value: unknown): DiffView | undefined {
|
||||
if (value === "split" || value === "unified") return value
|
||||
}
|
||||
|
||||
function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
const dimensions = useTerminalDimensions()
|
||||
const themeState = useTheme()
|
||||
const theme = () => props.api.theme.current
|
||||
const params = () =>
|
||||
("params" in props.api.route.current ? props.api.route.current.params : undefined) as
|
||||
| { mode?: DiffMode; sessionID?: string; messageID?: string }
|
||||
| {
|
||||
mode?: DiffMode
|
||||
sessionID?: string
|
||||
messageID?: string
|
||||
returnRoute?: TuiRouteCurrent
|
||||
}
|
||||
| undefined
|
||||
const mode = () => params()?.mode ?? "git"
|
||||
const diffInput = createMemo(() => ({
|
||||
@@ -80,44 +104,61 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
return normalizeDiffs(result.data ?? [])
|
||||
}
|
||||
|
||||
const result = await props.api.client.vcs.diff({ mode: "git" }, { throwOnError: true })
|
||||
const result = await props.api.client.vcs.diff(
|
||||
{ mode: "git", context: WORKING_TREE_DIFF_CONTEXT_LINES },
|
||||
{ throwOnError: true },
|
||||
)
|
||||
return normalizeDiffs(result.data ?? [])
|
||||
})
|
||||
const files = createMemo(() => diff() ?? [])
|
||||
const [focus, setFocus] = createSignal<DiffViewerFocus>("patches")
|
||||
const [showFileTree, setShowFileTree] = createSignal(true)
|
||||
const [singlePatch, setSinglePatch] = createSignal(false)
|
||||
const [fileTreeEnabled, setFileTreeEnabled] = createSignal(
|
||||
props.api.kv.get<boolean>(KV_SHOW_FILE_TREE, true) !== false,
|
||||
)
|
||||
const showFileTree = createMemo(() => showDiffViewerFileTree(fileTreeEnabled(), files().length))
|
||||
const [singlePatch, setSinglePatch] = createSignal(props.api.kv.get<boolean>(KV_SINGLE_PATCH, false) === true)
|
||||
const patchPaneWidth = createMemo(() => dimensions().width - (showFileTree() ? 33 : 0) - 4)
|
||||
const patchLeftBorder = createMemo<BorderSides[]>(() => (showFileTree() ? ["left"] : []))
|
||||
const splitAvailable = createMemo(() => patchPaneWidth() >= MIN_SPLIT_WIDTH)
|
||||
const defaultView = createMemo(() => {
|
||||
if (props.api.tuiConfig.diff_style === "stacked") return "unified"
|
||||
return splitAvailable() ? "split" : "unified"
|
||||
})
|
||||
const [viewOverride, setViewOverride] = createSignal<"split" | "unified">()
|
||||
const [viewOverride, setViewOverride] = createSignal<DiffView | undefined>(storedView(props.api.kv.get(KV_VIEW)))
|
||||
const view = createMemo(() => (splitAvailable() ? (viewOverride() ?? defaultView()) : "unified"))
|
||||
const fileTree = createMemo(() => buildFileTree(files()))
|
||||
const [expandedFileNodes, setExpandedFileNodes] = createSignal<ReadonlySet<number>>(new Set())
|
||||
const [highlightedFileNode, setHighlightedFileNode] = createSignal<number | undefined>()
|
||||
const [lastHighlightedFileNode, setLastHighlightedFileNode] = createSignal<number | undefined>()
|
||||
const [activePatchFileIndex, setActivePatchFileIndex] = createSignal<number | undefined>()
|
||||
const [selectedFileIndex, setSelectedFileIndex] = createSignal<number | undefined>()
|
||||
const [reviewedFileNames, setReviewedFileNames] = createSignal<ReadonlySet<string>>(new Set())
|
||||
const fileRows = createMemo(() => flattenFileTree(fileTree(), expandedFileNodes()))
|
||||
const patchFileIndexes = createMemo(() => orderedPatchFileIndexes(flattenFileTree(fileTree())))
|
||||
const focusRunner = (input: Record<DiffViewerFocus, () => void>) => () => input[focus()]()
|
||||
const switchFocusShortcut = useCommandShortcut("diff.switch_focus")
|
||||
const nextFileShortcut = useCommandShortcut("diff.next_file")
|
||||
const previousFileShortcut = useCommandShortcut("diff.previous_file")
|
||||
const toggleFileTreeShortcut = useCommandShortcut("diff.toggle_file_tree")
|
||||
const singlePatchShortcut = useCommandShortcut("diff.single_patch")
|
||||
const switchDiffShortcut = useCommandShortcut("diff.switch_diff")
|
||||
const switchSourceShortcut = useCommandShortcut("diff.switch_source")
|
||||
const toggleViewShortcut = useCommandShortcut("diff.toggle_view")
|
||||
const markReviewedShortcut = useCommandShortcut("diff.mark_reviewed")
|
||||
const helpShortcut = useCommandShortcut("diff.help")
|
||||
let scroll: ScrollBoxRenderable | undefined
|
||||
const patchNodeByFileIndex = new Map<number, BoxRenderable>()
|
||||
const [pendingPatchScrollFileIndex, setPendingPatchScrollFileIndex] = createSignal<number | undefined>()
|
||||
const [patchFillerHeight, setPatchFillerHeight] = createSignal(0)
|
||||
|
||||
onCleanup(() => props.api.ui.dialog.clear())
|
||||
|
||||
createEffect(() => {
|
||||
setExpandedFileNodes(allExpandedFileTreeDirectories(fileTree()))
|
||||
setHighlightedFileNode(undefined)
|
||||
setLastHighlightedFileNode(undefined)
|
||||
setActivePatchFileIndex(undefined)
|
||||
setSelectedFileIndex(undefined)
|
||||
setReviewedFileNames(new Set<string>())
|
||||
})
|
||||
|
||||
const ensureHighlightedFileNode = () => {
|
||||
@@ -145,86 +186,137 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
}
|
||||
|
||||
const scrollPatchNodeToTop = (patchNode: BoxRenderable) => {
|
||||
if (!scroll) return
|
||||
scroll.scrollBy(patchNode.y - scroll.viewport.y)
|
||||
requestAnimationFrame(() => {
|
||||
if (scroll) scroll.scrollBy(patchNode.y - scroll.viewport.y)
|
||||
if (!scroll) return
|
||||
const scrollDelta = patchNode.y - scroll.viewport.y
|
||||
const contentY = scroll.scrollTop + scrollDelta
|
||||
const offset = contentY === 0 ? 0 : 1
|
||||
scroll.scrollBy(scrollDelta + offset)
|
||||
})
|
||||
}
|
||||
|
||||
const revealFileTreeFile = (fileIndex: number) => {
|
||||
const node = fileTree().nodes.find((item) => item.kind === "file" && item.fileIndex === fileIndex)
|
||||
if (!node) return
|
||||
const selection = fileTreeFileSelection(fileTree(), fileIndex)
|
||||
if (!selection) return
|
||||
setExpandedFileNodes((expanded) => {
|
||||
const next = new Set(expanded)
|
||||
for (let parent = node.parent; parent !== undefined; parent = fileTree().nodes[parent]?.parent) {
|
||||
next.add(parent)
|
||||
}
|
||||
selection.expandedNodes.forEach((node) => next.add(node))
|
||||
return next
|
||||
})
|
||||
setHighlighted(node.id)
|
||||
setHighlighted(selection.highlightedNode)
|
||||
}
|
||||
|
||||
const selectPatchFile = (fileIndex: number) => {
|
||||
revealFileTreeFile(fileIndex)
|
||||
setActivePatchFileIndex(fileIndex)
|
||||
setSelectedFileIndex(fileIndex)
|
||||
}
|
||||
|
||||
const scrollToFileIndex = (fileIndex: number | undefined) => {
|
||||
if (fileIndex === undefined) return
|
||||
setActivePatchFileIndex(fileIndex)
|
||||
selectPatchFile(fileIndex)
|
||||
const patchNode = patchNodeByFileIndex.get(fileIndex)
|
||||
if (patchNode) scrollPatchNodeToTop(patchNode)
|
||||
}
|
||||
|
||||
const jumpToFileIndex = (fileIndex: number | undefined) => {
|
||||
if (fileIndex === undefined) return
|
||||
revealFileTreeFile(fileIndex)
|
||||
scrollToFileIndex(fileIndex)
|
||||
}
|
||||
|
||||
const currentPatchFileIndex = () => {
|
||||
if (!scroll) return undefined
|
||||
const entries = files()
|
||||
.map((_, fileIndex) => ({ fileIndex, node: patchNodeByFileIndex.get(fileIndex) }))
|
||||
const viewportContentY = scroll.scrollTop + 1
|
||||
const entries = patchFileIndexes()
|
||||
.map((fileIndex) => ({
|
||||
fileIndex,
|
||||
node: patchNodeByFileIndex.get(fileIndex),
|
||||
}))
|
||||
.filter((entry): entry is { fileIndex: number; node: BoxRenderable } => Boolean(entry.node))
|
||||
.sort((left, right) => left.node.y - right.node.y)
|
||||
return entries.findLast((entry) => entry.node.y <= scroll!.viewport.y + 1)?.fileIndex ?? entries[0]?.fileIndex
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
contentY: scroll!.scrollTop + entry.node.y - scroll!.viewport.y,
|
||||
}))
|
||||
.sort((left, right) => left.contentY - right.contentY)
|
||||
return entries.findLast((entry) => entry.contentY <= viewportContentY)?.fileIndex ?? entries[0]?.fileIndex
|
||||
}
|
||||
|
||||
const jumpRelativePatchFile = (offset: number) => {
|
||||
const current = focus() === "files" ? highlightedFileNode() : undefined
|
||||
const nextFromSelection =
|
||||
current === undefined ? undefined : moveFileTreeSelectionToFile(fileRows(), current, offset)
|
||||
if (nextFromSelection !== undefined) {
|
||||
jumpToFileIndex(fileRows().find((row) => row.id === nextFromSelection)?.fileIndex)
|
||||
const next = movePatchFileIndex(patchFileIndexes(), selectedFileIndex() ?? activePatchFileIndex(), offset)
|
||||
if (singlePatch()) {
|
||||
if (next === undefined) return
|
||||
selectPatchFile(next)
|
||||
scrollSinglePatchToTop()
|
||||
return
|
||||
}
|
||||
const currentFileIndex = activePatchFileIndex() ?? currentPatchFileIndex()
|
||||
const currentRow = fileRows().find((row) => row.fileIndex === currentFileIndex)
|
||||
scrollToFileIndex(
|
||||
fileRows().find((row) => row.id === moveFileTreeSelectionToFile(fileRows(), currentRow?.id, offset))?.fileIndex,
|
||||
)
|
||||
scrollToFileIndex(next)
|
||||
}
|
||||
|
||||
const highlightedPatchFileIndex = () => fileRows().find((row) => row.id === highlightedFileNode())?.fileIndex
|
||||
const firstPatchFileIndex = () => fileRows().find((row) => row.fileIndex !== undefined)?.fileIndex
|
||||
const visiblePatchFiles = createMemo(() => {
|
||||
if (!singlePatch()) return files().map((file, fileIndex) => ({ file, fileIndex }))
|
||||
const fileIndex = activePatchFileIndex() ?? currentPatchFileIndex() ?? firstPatchFileIndex()
|
||||
if (!singlePatch()) {
|
||||
return patchFileIndexes().flatMap((fileIndex) => {
|
||||
const file = files()[fileIndex]
|
||||
return file ? [{ file, fileIndex }] : []
|
||||
})
|
||||
}
|
||||
const fileIndex = singlePatchFileIndex(
|
||||
selectedFileIndex(),
|
||||
activePatchFileIndex(),
|
||||
currentPatchFileIndex(),
|
||||
firstPatchFileIndex(),
|
||||
)
|
||||
const file = fileIndex === undefined ? undefined : files()[fileIndex]
|
||||
return file && fileIndex !== undefined ? [{ file, fileIndex }] : []
|
||||
})
|
||||
|
||||
const ensureHighlightedPatchFile = () => {
|
||||
if (activePatchFileIndex() !== undefined) return
|
||||
const fileIndex = currentPatchFileIndex() ?? firstPatchFileIndex()
|
||||
if (fileIndex !== undefined) setActivePatchFileIndex(fileIndex)
|
||||
const fileIndex = currentPatchFileIndex() ?? activePatchFileIndex() ?? firstPatchFileIndex()
|
||||
if (fileIndex === undefined) return
|
||||
selectPatchFile(fileIndex)
|
||||
}
|
||||
|
||||
const scrollToHighlightedPatchFile = () => {
|
||||
const fileIndex = activePatchFileIndex()
|
||||
if (fileIndex === undefined) return
|
||||
const scrollToPatchFileIndexAfterRender = (fileIndex: number) => {
|
||||
setPendingPatchScrollFileIndex(fileIndex)
|
||||
requestAnimationFrame(() => {
|
||||
const patchNode = patchNodeByFileIndex.get(fileIndex)
|
||||
if (patchNode) scrollPatchNodeToTop(patchNode)
|
||||
requestAnimationFrame(() => {
|
||||
const patchNode = patchNodeByFileIndex.get(fileIndex)
|
||||
if (patchNode) scrollPatchNodeToTop(patchNode)
|
||||
setPendingPatchScrollFileIndex(undefined)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const scrollSinglePatchToTop = () => {
|
||||
requestAnimationFrame(() => {
|
||||
scroll?.scrollTo(0)
|
||||
requestAnimationFrame(() => scroll?.scrollTo(0))
|
||||
})
|
||||
}
|
||||
|
||||
const measurePatchFiller = () => {
|
||||
requestAnimationFrame(() => {
|
||||
if (!scroll) return
|
||||
const entries = visiblePatchFiles()
|
||||
.map((entry) => patchNodeByFileIndex.get(entry.fileIndex))
|
||||
.filter((node): node is BoxRenderable => Boolean(node))
|
||||
if (entries.length === 0) {
|
||||
setPatchFillerHeight(0)
|
||||
return
|
||||
}
|
||||
const contentHeight = Math.max(
|
||||
...entries.map((node) => scroll!.scrollTop + node.y - scroll!.viewport.y + node.height),
|
||||
)
|
||||
setPatchFillerHeight(Math.max(0, scroll.viewport.height - contentHeight))
|
||||
})
|
||||
}
|
||||
|
||||
const registerPatchNode = (fileIndex: number, element: BoxRenderable) => {
|
||||
patchNodeByFileIndex.set(fileIndex, element)
|
||||
measurePatchFiller()
|
||||
if (pendingPatchScrollFileIndex() !== fileIndex) return
|
||||
requestAnimationFrame(() => {
|
||||
scrollPatchNodeToTop(element)
|
||||
@@ -235,6 +327,13 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
})
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
visiblePatchFiles()
|
||||
dimensions()
|
||||
view()
|
||||
measurePatchFiller()
|
||||
})
|
||||
|
||||
const toggleSelectedFileTreeRow = () => {
|
||||
const highlighted = fileRows().find((row) => row.id === highlightedFileNode())
|
||||
if (highlighted?.fileIndex !== undefined) {
|
||||
@@ -244,13 +343,44 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
setExpandedFileNodes((expanded) => toggleFileTreeDirectory(fileTree(), expanded, highlightedFileNode()))
|
||||
}
|
||||
|
||||
const clickFileTreeRow = (row: FileTreeRow) => {
|
||||
setFocus("files")
|
||||
setHighlighted(row.id)
|
||||
if (row.fileIndex !== undefined) {
|
||||
jumpToFileIndex(row.fileIndex)
|
||||
return
|
||||
}
|
||||
setExpandedFileNodes((expanded) => toggleFileTreeDirectory(fileTree(), expanded, row.id))
|
||||
}
|
||||
|
||||
const toggleSelectedFileReviewed = () => {
|
||||
const fileIndex =
|
||||
focus() === "files"
|
||||
? fileRows().find((row) => row.id === highlightedFileNode())?.fileIndex
|
||||
: (selectedFileIndex() ?? activePatchFileIndex() ?? currentPatchFileIndex())
|
||||
const file = fileIndex === undefined ? undefined : files()[fileIndex]?.file
|
||||
if (!file) return
|
||||
setReviewedFileNames((reviewed) => {
|
||||
const next = new Set(reviewed)
|
||||
if (next.has(file)) next.delete(file)
|
||||
else next.add(file)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const commands = [
|
||||
{
|
||||
name: "diff.close",
|
||||
title: "Close diff viewer",
|
||||
category: "VCS",
|
||||
run() {
|
||||
props.api.route.navigate("home")
|
||||
const returnRoute = params()?.returnRoute
|
||||
props.api.ui.dialog.clear()
|
||||
|
||||
props.api.route.navigate(
|
||||
returnRoute?.name ?? "home",
|
||||
returnRoute && "params" in returnRoute ? returnRoute.params : undefined,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -326,6 +456,11 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
category: "VCS",
|
||||
run: focusRunner({
|
||||
files() {
|
||||
const highlighted = highlightedFileNode()
|
||||
if (highlighted !== undefined && expandedFileNodes().has(highlighted)) {
|
||||
setHighlighted(moveFileTreeSelectionToFirstChild(fileRows(), highlighted))
|
||||
return
|
||||
}
|
||||
setExpandedFileNodes((expanded) =>
|
||||
setFileTreeDirectoryExpanded(fileTree(), expanded, highlightedFileNode(), true),
|
||||
)
|
||||
@@ -333,12 +468,29 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
patches() {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "diff.expand_all",
|
||||
title: "Expand all diff viewer folders",
|
||||
category: "VCS",
|
||||
run: focusRunner({
|
||||
files() {
|
||||
setExpandedFileNodes(allExpandedFileTreeDirectories(fileTree()))
|
||||
},
|
||||
patches() {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "diff.collapse",
|
||||
title: "Collapse diff viewer item",
|
||||
category: "VCS",
|
||||
run: focusRunner({
|
||||
files() {
|
||||
const highlighted = highlightedFileNode()
|
||||
const node = highlighted === undefined ? undefined : fileTree().nodes[highlighted]
|
||||
if (node?.kind !== "directory" || !expandedFileNodes().has(node.id)) {
|
||||
setHighlighted(moveFileTreeSelectionToParent(fileRows(), highlighted))
|
||||
return
|
||||
}
|
||||
setExpandedFileNodes((expanded) =>
|
||||
setFileTreeDirectoryExpanded(fileTree(), expanded, highlightedFileNode(), false),
|
||||
)
|
||||
@@ -362,6 +514,14 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
jumpRelativePatchFile(-1)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "diff.mark_reviewed",
|
||||
title: "Toggle selected diff file reviewed",
|
||||
category: "VCS",
|
||||
run() {
|
||||
toggleSelectedFileReviewed()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "diff.switch_focus",
|
||||
title: "Switch diff viewer focus",
|
||||
@@ -380,10 +540,10 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
title: "Toggle diff viewer file tree",
|
||||
category: "VCS",
|
||||
run() {
|
||||
setShowFileTree((value) => {
|
||||
if (value) setFocus("patches")
|
||||
return !value
|
||||
})
|
||||
const next = !fileTreeEnabled()
|
||||
if (!next) setFocus("patches")
|
||||
setFileTreeEnabled(next)
|
||||
props.api.kv.set(KV_SHOW_FILE_TREE, next)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -391,16 +551,29 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
title: "Toggle single patch view",
|
||||
category: "VCS",
|
||||
run() {
|
||||
setSinglePatch((value) => {
|
||||
const next = !value
|
||||
if (next) ensureHighlightedPatchFile()
|
||||
else scrollToHighlightedPatchFile()
|
||||
return next
|
||||
})
|
||||
if (!singlePatch()) {
|
||||
ensureHighlightedPatchFile()
|
||||
setSinglePatch(true)
|
||||
props.api.kv.set(KV_SINGLE_PATCH, true)
|
||||
scrollSinglePatchToTop()
|
||||
return
|
||||
}
|
||||
const fileIndex =
|
||||
visiblePatchFiles()[0]?.fileIndex ??
|
||||
singlePatchFileIndex(
|
||||
selectedFileIndex(),
|
||||
activePatchFileIndex(),
|
||||
currentPatchFileIndex(),
|
||||
firstPatchFileIndex(),
|
||||
)
|
||||
if (fileIndex !== undefined) selectPatchFile(fileIndex)
|
||||
setSinglePatch(false)
|
||||
props.api.kv.set(KV_SINGLE_PATCH, false)
|
||||
if (fileIndex !== undefined) scrollToPatchFileIndexAfterRender(fileIndex)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "diff.switch_diff",
|
||||
name: "diff.switch_source",
|
||||
title: "Switch diff viewer source",
|
||||
category: "VCS",
|
||||
run() {
|
||||
@@ -413,7 +586,17 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
category: "VCS",
|
||||
run() {
|
||||
if (!splitAvailable()) return
|
||||
setViewOverride(view() === "split" ? "unified" : "split")
|
||||
const next = view() === "split" ? "unified" : "split"
|
||||
setViewOverride(next)
|
||||
props.api.kv.set(KV_VIEW, next)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "diff.help",
|
||||
title: "Show more diff viewer shortcuts",
|
||||
category: "VCS",
|
||||
run() {
|
||||
openHelpDialog()
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -434,7 +617,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
const openSwitchDiffDialog = () => {
|
||||
props.api.ui.dialog.replace(() => (
|
||||
<DialogSelect
|
||||
title="Switch diff"
|
||||
title="Switch source"
|
||||
skipFilter={true}
|
||||
renderFilter={false}
|
||||
current={mode()}
|
||||
@@ -446,6 +629,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
mode: option.value,
|
||||
sessionID: params()?.sessionID,
|
||||
messageID: params()?.messageID,
|
||||
returnRoute: params()?.returnRoute,
|
||||
})
|
||||
},
|
||||
}))}
|
||||
@@ -453,6 +637,11 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
))
|
||||
}
|
||||
|
||||
const openHelpDialog = () => {
|
||||
props.api.ui.dialog.replace(() => <DiffViewerHelpDialog />)
|
||||
props.api.ui.dialog.setSize("large")
|
||||
}
|
||||
|
||||
useBindings(() => ({
|
||||
commands,
|
||||
bindings: [
|
||||
@@ -460,6 +649,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
{ key: "k,up", cmd: "diff.up", desc: "Move diff viewer up" },
|
||||
{ key: "pagedown,ctrl+f", cmd: "diff.page.down", desc: "Page diff viewer down" },
|
||||
{ key: "pageup,ctrl+b", cmd: "diff.page.up", desc: "Page diff viewer up" },
|
||||
{ key: "m", cmd: "diff.mark_reviewed", desc: "Mark selected file reviewed" },
|
||||
...props.api.tuiConfig.keybinds.gather(
|
||||
"diff",
|
||||
commands.map((command) => command.name),
|
||||
@@ -468,67 +658,57 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
}))
|
||||
|
||||
return (
|
||||
<box
|
||||
position="absolute"
|
||||
zIndex={2500}
|
||||
left={0}
|
||||
top={0}
|
||||
width={dimensions().width}
|
||||
height={dimensions().height}
|
||||
backgroundColor={theme().background}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
gap={1}
|
||||
>
|
||||
<box flexDirection="row" justifyContent="space-between" flexShrink={0}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme().text}>Diff</text>
|
||||
<box position="absolute" zIndex={2500} left={0} top={0} width={dimensions().width} height={dimensions().height}>
|
||||
<PanelGroup axis="y" width="100%" height="100%">
|
||||
<Panel border="none" flexShrink={0} padding={0} paddingLeft={1}>
|
||||
<text fg={theme().text}>Diff </text>
|
||||
<text fg={theme().textMuted}>{mode() === "last-turn" ? "last turn" : "working tree"}</text>
|
||||
</box>
|
||||
</box>
|
||||
<box flexGrow={1} />
|
||||
<text fg={theme().textMuted}>
|
||||
{files().length} {files().length === 1 ? "file" : "files"}
|
||||
</text>
|
||||
</Panel>
|
||||
|
||||
<Switch>
|
||||
<Match when={diff.loading}>
|
||||
<box flexGrow={1} alignItems="center" justifyContent="center">
|
||||
<text fg={theme().textMuted}>Loading diff...</text>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={!diff.loading}>
|
||||
<box flexDirection="row" flexGrow={1} minHeight={0} gap={1}>
|
||||
<Show when={showFileTree()}>
|
||||
<DiffViewerFileTree
|
||||
files={files()}
|
||||
loading={diff.loading}
|
||||
error={diff.error}
|
||||
theme={theme()}
|
||||
focused={focus() === "files"}
|
||||
highlightedNode={highlightedFileNode()}
|
||||
expandedNodes={expandedFileNodes()}
|
||||
/>
|
||||
</Show>
|
||||
<box flexGrow={1} minHeight={0}>
|
||||
<Switch>
|
||||
<Match when={diff.loading}>
|
||||
<Separator axis="x" />
|
||||
<box flexGrow={1} paddingLeft={1}>
|
||||
<text fg={theme().textMuted}>Loading diff...</text>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={!diff.loading && files().length === 0}>
|
||||
<Separator axis="x" />
|
||||
<box flexGrow={1} paddingLeft={1}>
|
||||
<text fg={theme().textMuted}>No diff!</text>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={!diff.loading && diff.error}>
|
||||
<Separator axis="x" />
|
||||
<box flexGrow={1} paddingLeft={1}>
|
||||
<text fg={theme().error}>Failed to load diff</text>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={!diff.loading}>
|
||||
<PanelGroup axis="x">
|
||||
<Show when={showFileTree()}>
|
||||
<DiffViewerFileTree
|
||||
files={files()}
|
||||
loading={diff.loading}
|
||||
error={diff.error}
|
||||
theme={theme()}
|
||||
focused={focus() === "files"}
|
||||
width={FILE_TREE_WIDTH}
|
||||
highlightedNode={highlightedFileNode()}
|
||||
selectedFileIndex={selectedFileIndex()}
|
||||
reviewedFileNames={reviewedFileNames()}
|
||||
expandedNodes={expandedFileNodes()}
|
||||
onRowClick={clickFileTreeRow}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<box
|
||||
flexGrow={1}
|
||||
minWidth={0}
|
||||
backgroundColor={theme().background}
|
||||
paddingLeft={0}
|
||||
paddingRight={2}
|
||||
gap={1}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={diff.error}>
|
||||
<box paddingTop={1}>
|
||||
<text fg={theme().error}>Failed to load diff</text>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={files().length === 0}>
|
||||
<box paddingTop={1}>
|
||||
<text fg={theme().textMuted}>No diff to show</text>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={files().length > 0}>
|
||||
<Panel flexGrow={1} minHeight={0} border="none">
|
||||
<Separator axis="x" start={showFileTree() ? "edge-out" : undefined} />
|
||||
<scrollbox
|
||||
ref={(element: ScrollBoxRenderable) => (scroll = element)}
|
||||
flexGrow={1}
|
||||
@@ -537,117 +717,209 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
horizontalScrollbarOptions={{ visible: false }}
|
||||
>
|
||||
<For each={visiblePatchFiles()}>
|
||||
{(entry) => (
|
||||
<box
|
||||
ref={(element: BoxRenderable) => registerPatchNode(entry.fileIndex, element)}
|
||||
marginBottom={1}
|
||||
backgroundColor={theme().backgroundPanel}
|
||||
>
|
||||
<box
|
||||
flexDirection="row"
|
||||
gap={2}
|
||||
flexShrink={0}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
paddingRight={1}
|
||||
backgroundColor={theme().backgroundPanel}
|
||||
>
|
||||
<text fg={theme().text}>{entry.file.file}</text>
|
||||
<text fg={theme().diffAdded}>+{entry.file.additions}</text>
|
||||
<text fg={theme().diffRemoved}>-{entry.file.deletions}</text>
|
||||
{(entry, index) => {
|
||||
const reviewed = () => reviewedFileNames().has(entry.file.file)
|
||||
return (
|
||||
<box ref={(element: BoxRenderable) => registerPatchNode(entry.fileIndex, element)}>
|
||||
{index() !== 0 ? <Separator axis="x" start={showFileTree() ? "edge" : undefined} /> : null}
|
||||
<box
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
flexShrink={0}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
border={patchLeftBorder()}
|
||||
borderColor={theme().border}
|
||||
>
|
||||
<text fg={reviewed() ? theme().textMuted : theme().text}>{entry.file.file}</text>
|
||||
<box flexGrow={1} />
|
||||
<text fg={reviewed() ? theme().textMuted : theme().diffAdded}>
|
||||
+{entry.file.additions}
|
||||
</text>
|
||||
<text fg={reviewed() ? theme().textMuted : theme().diffRemoved}>
|
||||
-{entry.file.deletions}
|
||||
</text>
|
||||
</box>
|
||||
<Separator axis="x" start={showFileTree() ? "edge" : undefined} />
|
||||
<Show
|
||||
when={entry.file.patch}
|
||||
fallback={<text fg={theme().textMuted}>No patch available for this file.</text>}
|
||||
>
|
||||
{(patch) => (
|
||||
<box border={patchLeftBorder()} borderColor={theme().border}>
|
||||
<diff
|
||||
diff={patch()}
|
||||
view={view()}
|
||||
filetype={reviewed() ? PLAIN_TEXT_FILETYPE : filetype(entry.file.file)}
|
||||
syntaxStyle={themeState.syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode="char"
|
||||
fg={reviewed() ? theme().textMuted : theme().text}
|
||||
addedBg={reviewed() ? theme().backgroundElement : theme().diffAddedBg}
|
||||
removedBg={reviewed() ? theme().backgroundElement : theme().diffRemovedBg}
|
||||
addedSignColor={reviewed() ? theme().textMuted : theme().diffHighlightAdded}
|
||||
removedSignColor={reviewed() ? theme().textMuted : theme().diffHighlightRemoved}
|
||||
lineNumberFg={theme().diffLineNumber}
|
||||
addedLineNumberBg={
|
||||
reviewed() ? theme().backgroundElement : theme().diffAddedLineNumberBg
|
||||
}
|
||||
removedLineNumberBg={
|
||||
reviewed() ? theme().backgroundElement : theme().diffRemovedLineNumberBg
|
||||
}
|
||||
/>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
</box>
|
||||
<Show
|
||||
when={entry.file.patch}
|
||||
fallback={<text fg={theme().textMuted}>No patch available for this file.</text>}
|
||||
>
|
||||
{(patch) => (
|
||||
<diff
|
||||
diff={patch()}
|
||||
view={view()}
|
||||
filetype={filetype(entry.file.file)}
|
||||
syntaxStyle={themeState.syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode="word"
|
||||
fg={theme().text}
|
||||
addedBg={theme().diffAddedBg}
|
||||
removedBg={theme().diffRemovedBg}
|
||||
contextBg={theme().diffContextBg}
|
||||
addedSignColor={theme().diffHighlightAdded}
|
||||
removedSignColor={theme().diffHighlightRemoved}
|
||||
lineNumberFg={theme().diffLineNumber}
|
||||
lineNumberBg={theme().diffContextBg}
|
||||
addedLineNumberBg={theme().diffAddedLineNumberBg}
|
||||
removedLineNumberBg={theme().diffRemovedLineNumberBg}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</box>
|
||||
)}
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
<Show when={patchFillerHeight() > 0}>
|
||||
<box height={patchFillerHeight()} border={patchLeftBorder()} borderColor={theme().border} />
|
||||
</Show>
|
||||
</scrollbox>
|
||||
</Match>
|
||||
</Switch>
|
||||
</box>
|
||||
</box>
|
||||
</Match>
|
||||
</Switch>
|
||||
<Separator axis="x" start={showFileTree() ? "edge-in" : undefined} />
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
</Match>
|
||||
</Switch>
|
||||
</box>
|
||||
|
||||
<box flexDirection="row" gap={2} flexShrink={0}>
|
||||
<Show when={switchFocusShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>focus file tree</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={nextFileShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>next file</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={previousFileShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>previous file</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={toggleFileTreeShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()}{" "}
|
||||
<span style={{ fg: theme().textMuted }}>{showFileTree() ? "hide file tree" : "show file tree"}</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={singlePatchShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()}{" "}
|
||||
<span style={{ fg: theme().textMuted }}>{singlePatch() ? "all patches" : "single patch"}</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={switchDiffShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>switch diff</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={toggleViewShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()}{" "}
|
||||
<span style={{ fg: theme().textMuted }}>{view() === "split" ? "unified view" : "split view"}</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Panel flexShrink={0} gap={2} paddingLeft={1} border="none">
|
||||
<Show when={switchFocusShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>focus file tree</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={nextFileShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>next file</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={previousFileShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>previous file</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={switchSourceShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>switch source</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={markReviewedShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>mark reviewed</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={helpShortcut()}>
|
||||
{(shortcut) => (
|
||||
<text fg={theme().text}>
|
||||
{shortcut()} <span style={{ fg: theme().textMuted }}>all</span>
|
||||
</text>
|
||||
)}
|
||||
</Show>
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
function DiffViewerHelpDialog() {
|
||||
const { theme } = useTheme()
|
||||
const rows = [
|
||||
{
|
||||
shortcut: () => "q",
|
||||
action: "Close viewer",
|
||||
description: "Quit the diff viewer",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.switch_focus"),
|
||||
action: "Focus file tree",
|
||||
description: "Move keyboard focus between the file tree and patch pane",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.next_file"),
|
||||
action: "Next file",
|
||||
description: "Select the next changed file in file-tree order",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.previous_file"),
|
||||
action: "Previous file",
|
||||
description: "Select the previous changed file in file-tree order",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.toggle_file_tree"),
|
||||
action: "Toggle file tree",
|
||||
description: "Show or hide the file tree sidebar",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.single_patch"),
|
||||
action: "Toggle patches",
|
||||
description: "Switch between one selected patch and all patches",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.switch_source"),
|
||||
action: "Switch source",
|
||||
description: "Choose working tree or last-turn changes",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.toggle_view"),
|
||||
action: "Toggle view",
|
||||
description: "Switch between split and unified diff layout",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.expand_all"),
|
||||
action: "Expand all folders",
|
||||
description: "Open every folder in the file tree",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.mark_reviewed"),
|
||||
action: "Mark reviewed",
|
||||
description: "Toggle reviewed state for the selected file",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} paddingBottom={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
Diff shortcuts
|
||||
</text>
|
||||
<text fg={theme.textMuted}>esc</text>
|
||||
</box>
|
||||
<box flexDirection="row">
|
||||
<text fg={theme.textMuted} width={5} wrapMode="none">
|
||||
Key
|
||||
</text>
|
||||
<text fg={theme.textMuted} width={22} wrapMode="none">
|
||||
Action
|
||||
</text>
|
||||
<text fg={theme.textMuted}>Description</text>
|
||||
</box>
|
||||
<For each={rows}>
|
||||
{(row) => (
|
||||
<box flexDirection="row">
|
||||
<text fg={theme.text} width={5} wrapMode="none">
|
||||
{row.shortcut() || "-"}
|
||||
</text>
|
||||
<text fg={theme.text} width={22} wrapMode="none">
|
||||
{row.action}
|
||||
</text>
|
||||
<text fg={theme.textMuted}>{row.description}</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
@@ -672,6 +944,7 @@ const tui: TuiPlugin = async (api) => {
|
||||
api.route.navigate(ROUTE, {
|
||||
mode: "git",
|
||||
sessionID: "params" in api.route.current ? api.route.current.params?.sessionID : undefined,
|
||||
returnRoute: api.route.current,
|
||||
})
|
||||
api.ui.dialog.clear()
|
||||
},
|
||||
|
||||
@@ -24,7 +24,6 @@ import type {
|
||||
SessionMessageCompaction,
|
||||
SessionMessageModelSwitched,
|
||||
SessionMessageShell,
|
||||
SessionMessageSynthetic,
|
||||
SessionMessageUser,
|
||||
ToolFileContent,
|
||||
ToolTextContent,
|
||||
|
||||
@@ -25,11 +25,9 @@ export { useBindings, useKeymapSelector }
|
||||
export type OpenTuiKeymap = ReturnType<typeof useKeymap>
|
||||
type OpencodeModeStack = ReturnType<typeof createOpencodeModeStack>
|
||||
type CommandSlashEntry = {
|
||||
name: string
|
||||
display: string
|
||||
description?: string
|
||||
aliases?: string[]
|
||||
input?: boolean
|
||||
onSelect: () => void
|
||||
}
|
||||
type Command = ReturnType<OpenTuiKeymap["getCommands"]>[number]
|
||||
@@ -258,7 +256,6 @@ export function useCommandSlashes(): Accessor<readonly CommandSlashEntry[]> {
|
||||
if (typeof slashName !== "string" || !slashName) return []
|
||||
const slashAliases = entry.command.slashAliases
|
||||
return {
|
||||
name: slashName,
|
||||
display: `/${slashName}`,
|
||||
description:
|
||||
typeof entry.command.desc === "string"
|
||||
@@ -269,7 +266,6 @@ export function useCommandSlashes(): Accessor<readonly CommandSlashEntry[]> {
|
||||
aliases: Array.isArray(slashAliases)
|
||||
? slashAliases.filter((alias): alias is string => typeof alias === "string").map((alias) => `/${alias}`)
|
||||
: undefined,
|
||||
input: entry.command.slashInput === true,
|
||||
onSelect: () => keymap.dispatchCommand(entry.command.name),
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -20,9 +20,7 @@ export type InternalTuiPlugin = Omit<TuiPluginModule, "id"> & {
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export function internalTuiPlugins(
|
||||
flags: Pick<RuntimeFlags.Info, "diffViewer" | "experimentalEventSystem">,
|
||||
): InternalTuiPlugin[] {
|
||||
export function internalTuiPlugins(flags: Pick<RuntimeFlags.Info, "experimentalEventSystem">): InternalTuiPlugin[] {
|
||||
return [
|
||||
HomeFooter,
|
||||
HomeTips,
|
||||
@@ -35,7 +33,7 @@ export function internalTuiPlugins(
|
||||
Notifications,
|
||||
PluginManager,
|
||||
WhichKey,
|
||||
...(flags.diffViewer ? [DiffViewer] : []),
|
||||
DiffViewer,
|
||||
...(flags.experimentalEventSystem ? [SessionV2Debug] : []),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useSync } from "@tui/context/sync"
|
||||
import { useEvent } from "@tui/context/event"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { Spinner } from "@tui/component/spinner"
|
||||
import { selectedForeground, useTheme } from "@tui/context/theme"
|
||||
import { generateSubtleSyntax, selectedForeground, useTheme } from "@tui/context/theme"
|
||||
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core"
|
||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
||||
import type {
|
||||
@@ -53,7 +53,6 @@ import type { SkillTool } from "@/tool/skill"
|
||||
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useEditorContext } from "@tui/context/editor"
|
||||
import type { DialogContext } from "@tui/ui/dialog"
|
||||
import { useDialog } from "../../ui/dialog"
|
||||
import { TodoItem } from "../../component/todo-item"
|
||||
import { DialogMessage } from "./dialog-message"
|
||||
@@ -314,17 +313,6 @@ export function Session() {
|
||||
const dialog = useDialog()
|
||||
const renderer = useRenderer()
|
||||
|
||||
function slashArguments(name: string) {
|
||||
const input = prompt?.current.input
|
||||
if (!input?.startsWith("/")) return
|
||||
const firstLineEnd = input.indexOf("\n")
|
||||
const firstLine = firstLineEnd === -1 ? input : input.slice(0, firstLineEnd)
|
||||
const [command, ...firstLineArgs] = firstLine.split(" ")
|
||||
if (command !== `/${name}`) return
|
||||
const restOfInput = firstLineEnd === -1 ? "" : input.slice(firstLineEnd + 1)
|
||||
return firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
|
||||
}
|
||||
|
||||
event.on("session.status", (evt) => {
|
||||
if (evt.properties.sessionID !== route.sessionID) return
|
||||
if (evt.properties.status.type !== "retry") return
|
||||
@@ -557,7 +545,6 @@ export function Session() {
|
||||
slash: {
|
||||
name: "compact",
|
||||
aliases: ["summarize"],
|
||||
input: true,
|
||||
},
|
||||
run: () => {
|
||||
const selectedModel = local.model.current()
|
||||
@@ -569,14 +556,11 @@ export function Session() {
|
||||
})
|
||||
return
|
||||
}
|
||||
const instructions = slashArguments("compact") ?? slashArguments("summarize")
|
||||
const payload = {
|
||||
void sdk.client.session.summarize({
|
||||
sessionID: route.sessionID,
|
||||
modelID: selectedModel.modelID,
|
||||
providerID: selectedModel.providerID,
|
||||
...(instructions?.trim() ? { $body_instructions: instructions.trim() } : {}),
|
||||
} satisfies Parameters<typeof sdk.client.session.summarize>[0] & { $body_instructions?: string }
|
||||
void sdk.client.session.summarize(payload)
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
@@ -1061,7 +1045,6 @@ export function Session() {
|
||||
desc: "description" in command ? command.description : undefined,
|
||||
slashName: "slash" in command ? command.slash?.name : undefined,
|
||||
slashAliases: "slash" in command ? command.slash?.aliases : undefined,
|
||||
slashInput: "slash" in command ? command.slash?.input : undefined,
|
||||
...command,
|
||||
})),
|
||||
)
|
||||
@@ -1514,7 +1497,7 @@ const PART_MAPPING = {
|
||||
}
|
||||
|
||||
function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: AssistantMessage }) {
|
||||
const { theme, subtleSyntax } = useTheme()
|
||||
const { theme } = useTheme()
|
||||
const ctx = use()
|
||||
// Collapsed by default in hide mode: a single line throughout, so the
|
||||
// layout never shifts. Click to open the full markdown block, click to close.
|
||||
@@ -1536,6 +1519,8 @@ function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: Ass
|
||||
// blocks. Surface the title both while streaming and after settling so the
|
||||
// collapsed line carries real signal, not just a duration.
|
||||
const title = createMemo(() => reasoningTitle(content()))
|
||||
// Keep markdown emphasis for the existing thinking color/concealment, but render it without italics.
|
||||
const syntax = createMemo(() => generateSubtleSyntax(theme, { "markup.italic": { italic: false } }))
|
||||
|
||||
const toggle = () => {
|
||||
if (!inMinimal()) return
|
||||
@@ -1552,7 +1537,9 @@ function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: Ass
|
||||
filetype="markdown"
|
||||
drawUnstyledText={false}
|
||||
streaming={true}
|
||||
syntaxStyle={subtleSyntax()}
|
||||
syntaxStyle={syntax()}
|
||||
// `_Thinking:_`/`_Thought:_` still drives markdown emphasis color and conceals the underscores;
|
||||
// the syntax override above removes only the italic attribute from that emphasis token.
|
||||
content={(inMinimal() ? "- " : "") + (isDone() ? "_Thought:_ " : "_Thinking:_ ") + content()}
|
||||
conceal={ctx.conceal()}
|
||||
fg={theme.textMuted}
|
||||
@@ -1580,7 +1567,7 @@ function CollapsedReasoningText(props: { title: string | null; duration: number
|
||||
|
||||
return (
|
||||
<text fg={theme.warning} wrapMode="none">
|
||||
<span style={{ fg: theme.warning, italic: true }}>
|
||||
<span style={{ fg: theme.warning }}>
|
||||
{props.title ? "+ Thought: " + props.title + " · " + duration() : "+ Thought: " + duration()}
|
||||
</span>
|
||||
</text>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createMemo, createSignal, For, Match, Show, Switch } from "solid-js"
|
||||
import { createMemo, For, Match, Show, Switch } from "solid-js"
|
||||
import { Portal, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||
import type { TextareaRenderable } from "@opentui/core"
|
||||
import { useTheme, selectedForeground } from "../../context/theme"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user