Compare commits

..

1 Commits

Author SHA1 Message Date
Kit Langton 5d37d68bc4 fix(tui): move debug overlay to devtools 2026-08-07 20:33:54 +00:00
427 changed files with 24754 additions and 28020 deletions
-1
View File
@@ -4,7 +4,6 @@ on:
push: push:
branches: branches:
- dev - dev
- v2
jobs: jobs:
generate: generate:
+1
View File
@@ -251,3 +251,4 @@ opencode-drive stop --name demo
```bash ```bash
opencode-drive dir --name demo opencode-drive dir --name demo
``` ```
-56
View File
@@ -571,20 +571,6 @@
"@typescript/native-preview": "catalog:", "@typescript/native-preview": "catalog:",
}, },
}, },
"packages/merman": {
"name": "@opencode-ai/merman",
"version": "0.0.0",
"dependencies": {
"@opencode-ai/plugin": "workspace:*",
"@opentui/core": "catalog:",
"string-width": "catalog:",
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
},
},
"packages/plugin": { "packages/plugin": {
"name": "@opencode-ai/plugin", "name": "@opencode-ai/plugin",
"version": "1.18.8", "version": "1.18.8",
@@ -693,7 +679,6 @@
"@opencode-ai/util": "workspace:*", "@opencode-ai/util": "workspace:*",
"drizzle-orm": "catalog:", "drizzle-orm": "catalog:",
"effect": "catalog:", "effect": "catalog:",
"modal": "0.9.0",
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/bun": "catalog:", "@tsconfig/bun": "catalog:",
@@ -896,7 +881,6 @@
"dependencies": { "dependencies": {
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/merman": "workspace:*",
"@opencode-ai/plugin": "workspace:*", "@opencode-ai/plugin": "workspace:*",
"@opencode-ai/schema": "workspace:*", "@opencode-ai/schema": "workspace:*",
"@opencode-ai/simulation": "workspace:*", "@opencode-ai/simulation": "workspace:*",
@@ -1528,18 +1512,6 @@
"@capsizecss/unpack": ["@capsizecss/unpack@2.4.0", "", { "dependencies": { "blob-to-buffer": "^1.2.8", "cross-fetch": "^3.0.4", "fontkit": "^2.0.2" } }, "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q=="], "@capsizecss/unpack": ["@capsizecss/unpack@2.4.0", "", { "dependencies": { "blob-to-buffer": "^1.2.8", "cross-fetch": "^3.0.4", "fontkit": "^2.0.2" } }, "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q=="],
"@cbor-extract/cbor-extract-darwin-arm64": ["@cbor-extract/cbor-extract-darwin-arm64@2.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZKZ/F8US7JR92J4DMct6cLW/Y66o2K576+zjlEN/MevH70bFIsB10wkZEQPLzl2oNh2SMGy55xpJ9JoBRl5DOA=="],
"@cbor-extract/cbor-extract-darwin-x64": ["@cbor-extract/cbor-extract-darwin-x64@2.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-32b1mgc+P61Js+KW9VZv/c+xRw5EfmOcPx990JbCBSkYJFY0l25VinvyyWfl+3KjibQmAcYwmyzKF9J4DyKP/Q=="],
"@cbor-extract/cbor-extract-linux-arm": ["@cbor-extract/cbor-extract-linux-arm@2.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-tNg0za41TpQfkhWjptD+0gSD2fggMiDCSacuIeELyb2xZhr7PrhPe5h66Jc67B/5dmpIhI2QOUtv4SBsricyYQ=="],
"@cbor-extract/cbor-extract-linux-arm64": ["@cbor-extract/cbor-extract-linux-arm64@2.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-wfqgzqCAy/Vn8i6WVIh7qZd0DdBFaWBjPdB6ma+Wihcjv0gHqD/mw3ouVv7kbbUNrab6dKEx/w3xQZEdeXIlzg=="],
"@cbor-extract/cbor-extract-linux-x64": ["@cbor-extract/cbor-extract-linux-x64@2.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rpiLnVEsqtPJ+mXTdx1rfz4RtUGYIUg2rUAZgd1KjiC1SehYUSkJN7Yh+aVfSjvCGtVP0/bfkQkXpPXKbmSUaA=="],
"@cbor-extract/cbor-extract-win32-x64": ["@cbor-extract/cbor-extract-win32-x64@2.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-dI+9P7cfWxkTQ+oE+7Aa6onEn92PHgfWXZivjNheCRmTBDBf2fx6RyTi0cmgpYLnD1KLZK9ZYrMxaPZ4oiXhGA=="],
"@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="], "@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
"@clack/core": ["@clack/core@1.0.0-alpha.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg=="], "@clack/core": ["@clack/core@1.0.0-alpha.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg=="],
@@ -1748,10 +1720,6 @@
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.4", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ=="],
"@grpc/proto-loader": ["@grpc/proto-loader@0.8.1", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg=="],
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="], "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
"@hono/node-server": ["@hono/node-server@1.19.15", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Za2ai6TLdKjUvnur+eenO6nuYYipVAEhyCAdaV8IRvmU9kK8crOZUSYvIXn72E4f8fJqyAbpcJuTsYYmZp9Deg=="], "@hono/node-server": ["@hono/node-server@1.19.15", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Za2ai6TLdKjUvnur+eenO6nuYYipVAEhyCAdaV8IRvmU9kK8crOZUSYvIXn72E4f8fJqyAbpcJuTsYYmZp9Deg=="],
@@ -1846,8 +1814,6 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
"@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="], "@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],
"@jsx-email/all": ["@jsx-email/all@2.2.3", "", { "dependencies": { "@jsx-email/body": "1.0.2", "@jsx-email/button": "1.0.4", "@jsx-email/column": "1.0.3", "@jsx-email/container": "1.0.2", "@jsx-email/font": "1.0.3", "@jsx-email/head": "1.0.2", "@jsx-email/heading": "1.0.2", "@jsx-email/hr": "1.0.2", "@jsx-email/html": "1.0.2", "@jsx-email/img": "1.0.2", "@jsx-email/link": "1.0.2", "@jsx-email/markdown": "2.0.4", "@jsx-email/preview": "1.0.2", "@jsx-email/render": "1.1.1", "@jsx-email/row": "1.0.2", "@jsx-email/section": "1.0.2", "@jsx-email/tailwind": "2.4.4", "@jsx-email/text": "1.0.2" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-OBvLe/hVSQc0LlMSTJnkjFoqs3bmxcC4zpy/5pT5agPCSKMvAKQjzmsc2xJ2wO73jSpRV1K/g38GmvdCfrhSoQ=="], "@jsx-email/all": ["@jsx-email/all@2.2.3", "", { "dependencies": { "@jsx-email/body": "1.0.2", "@jsx-email/button": "1.0.4", "@jsx-email/column": "1.0.3", "@jsx-email/container": "1.0.2", "@jsx-email/font": "1.0.3", "@jsx-email/head": "1.0.2", "@jsx-email/heading": "1.0.2", "@jsx-email/hr": "1.0.2", "@jsx-email/html": "1.0.2", "@jsx-email/img": "1.0.2", "@jsx-email/link": "1.0.2", "@jsx-email/markdown": "2.0.4", "@jsx-email/preview": "1.0.2", "@jsx-email/render": "1.1.1", "@jsx-email/row": "1.0.2", "@jsx-email/section": "1.0.2", "@jsx-email/tailwind": "2.4.4", "@jsx-email/text": "1.0.2" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-OBvLe/hVSQc0LlMSTJnkjFoqs3bmxcC4zpy/5pT5agPCSKMvAKQjzmsc2xJ2wO73jSpRV1K/g38GmvdCfrhSoQ=="],
@@ -2098,8 +2064,6 @@
"@opencode-ai/httpapi-codegen": ["@opencode-ai/httpapi-codegen@workspace:packages/httpapi-codegen"], "@opencode-ai/httpapi-codegen": ["@opencode-ai/httpapi-codegen@workspace:packages/httpapi-codegen"],
"@opencode-ai/merman": ["@opencode-ai/merman@workspace:packages/merman"],
"@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"], "@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"],
"@opencode-ai/protocol": ["@opencode-ai/protocol@workspace:packages/protocol"], "@opencode-ai/protocol": ["@opencode-ai/protocol@workspace:packages/protocol"],
@@ -3334,8 +3298,6 @@
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"abort-controller-x": ["abort-controller-x@0.5.0", "", {}, "sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
@@ -3576,10 +3538,6 @@
"caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="], "caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="],
"cbor-extract": ["cbor-extract@2.2.2", "", { "dependencies": { "node-gyp-build-optional-packages": "5.1.1" }, "optionalDependencies": { "@cbor-extract/cbor-extract-darwin-arm64": "2.2.2", "@cbor-extract/cbor-extract-darwin-x64": "2.2.2", "@cbor-extract/cbor-extract-linux-arm": "2.2.2", "@cbor-extract/cbor-extract-linux-arm64": "2.2.2", "@cbor-extract/cbor-extract-linux-x64": "2.2.2", "@cbor-extract/cbor-extract-win32-x64": "2.2.2" }, "bin": { "download-cbor-prebuilds": "bin/download-prebuilds.js" } }, "sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng=="],
"cbor-x": ["cbor-x@1.6.5", "", { "optionalDependencies": { "cbor-extract": "^2.2.2" } }, "sha512-yO64CxnSh6kp+pHNRK9IfwnMvCB+c8HvmUjQY/9l9YRF0/cAPka/tUHLwS64QqUpFCq3/OtbKziVJYXH2EaRig=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
@@ -4612,8 +4570,6 @@
"lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
"lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
"lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="],
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
@@ -4840,8 +4796,6 @@
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
"modal": ["modal@0.9.0", "", { "dependencies": { "cbor-x": "^1.6.0", "long": "^5.3.1", "nice-grpc": "^2.1.12", "protobufjs": "^7.5.0", "smol-toml": "^1.3.3", "uuid": "^11.1.0" } }, "sha512-kCXcdJkhbJorf/q/6T9Wdlg6in9JmRnCNQnV6rVBMyeqNV/iXI6BYk4IzY4cvZ6dbauNeDMjk/Q08cbxvoIaXg=="],
"morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="], "morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="],
"motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="], "motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="],
@@ -4880,10 +4834,6 @@
"nf3": ["nf3@0.1.12", "", {}, "sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ=="], "nf3": ["nf3@0.1.12", "", {}, "sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ=="],
"nice-grpc": ["nice-grpc@2.1.16", "", { "dependencies": { "@grpc/grpc-js": "^1.14.0", "abort-controller-x": "^0.5.0", "nice-grpc-common": "^2.0.3" } }, "sha512-Cl3Pn00212Hl8/U6bpgMxmhZj5lyv3nWoJov4cd3FjWarktrMHP4DNvSjCnDwkMWYx4W1tyscEia4JX6Y4GVCQ=="],
"nice-grpc-common": ["nice-grpc-common@2.0.3", "", { "dependencies": { "ts-error": "^1.0.6" } }, "sha512-MEhnD3JMah0mgyivpb9hpRDbOBuXBxI/TVO+OK1h6rC97WM42HsPMR+zzRNQ0C5BqYJTw1nyWiQRD0DucO+pjQ=="],
"nitro": ["nitro@3.0.1-alpha.1", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.1", "db0": "^0.3.4", "h3": "2.0.1-rc.5", "jiti": "^2.6.1", "nf3": "^0.1.10", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.96.0", "oxc-transform": "^0.96.0", "srvx": "^0.9.5", "undici": "^7.16.0", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.4" }, "peerDependencies": { "rolldown": "*", "rollup": "^4", "vite": "^7", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-U4AxIsXxdkxzkFrK0XAw0e5Qbojk8jQ50MjjRBtBakC4HurTtQoiZvF+lSe382jhuQZCfAyywGWOFa9QzXLFaw=="], "nitro": ["nitro@3.0.1-alpha.1", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.1", "db0": "^0.3.4", "h3": "2.0.1-rc.5", "jiti": "^2.6.1", "nf3": "^0.1.10", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.96.0", "oxc-transform": "^0.96.0", "srvx": "^0.9.5", "undici": "^7.16.0", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.4" }, "peerDependencies": { "rolldown": "*", "rollup": "^4", "vite": "^7", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-U4AxIsXxdkxzkFrK0XAw0e5Qbojk8jQ50MjjRBtBakC4HurTtQoiZvF+lSe382jhuQZCfAyywGWOFa9QzXLFaw=="],
"nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
@@ -5682,8 +5632,6 @@
"ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="], "ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="],
"ts-error": ["ts-error@1.0.6", "", {}, "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA=="],
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
@@ -6642,8 +6590,6 @@
"builder-util/js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="], "builder-util/js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="],
"cbor-extract/node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.1.1", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-test": "build-test.js", "node-gyp-build-optional-packages-optional": "optional.js" } }, "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw=="],
"cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -6788,8 +6734,6 @@
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"modal/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="],
"motion/framer-motion": ["framer-motion@12.42.2", "", { "dependencies": { "motion-dom": "^12.42.2", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw=="], "motion/framer-motion": ["framer-motion@12.42.2", "", { "dependencies": { "motion-dom": "^12.42.2", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw=="],
"nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="],
+1 -2
View File
@@ -133,8 +133,7 @@ const countHints = (request: LLMRequest) =>
export const applyCachePolicy = (request: LLMRequest): LLMRequest => { export const applyCachePolicy = (request: LLMRequest): LLMRequest => {
if (!RESPECTS_INLINE_HINTS.has(request.model.route.id)) return request if (!RESPECTS_INLINE_HINTS.has(request.model.route.id)) return request
if (request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto")) if (request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto")) return request
return request
const policy = resolve(request.cache) const policy = resolve(request.cache)
if (!policy.tools && !policy.system && !policy.messages) return request if (!policy.tools && !policy.system && !policy.messages) return request
+17 -18
View File
@@ -371,13 +371,12 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
return text return text
})() })()
const cached = message.content.findLast((part) => "cache" in part && part.cache !== undefined) const cached = message.content.findLast((part) => "cache" in part && part.cache !== undefined)
const cacheControl = options.cacheControl?.(cached && "cache" in cached ? cached.cache : undefined)
const result = { const result = {
role: "assistant" as const, role: "assistant" as const,
content: content.length > 0 ? content.map((part) => part.text).join("") : toolCalls.length > 0 ? null : "", content: content.length === 0 ? null : ProviderShared.joinText(content),
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}), tool_calls: toolCalls.length === 0 ? undefined : toolCalls,
...(details !== undefined ? { reasoning_details: details } : {}), reasoning_details: details,
...(cacheControl !== undefined ? { cache_control: cacheControl } : {}), cache_control: options.cacheControl?.(cached && "cache" in cached ? cached.cache : undefined),
} }
if (field === undefined || reasoningText === undefined) return result if (field === undefined || reasoningText === undefined) return result
return { ...result, [field]: reasoningText } return { ...result, [field]: reasoningText }
@@ -710,20 +709,23 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
Boolean(delta?.content) || Boolean(delta?.content) ||
reasoning !== undefined || reasoning !== undefined ||
(Array.isArray(delta?.reasoning_details) && delta.reasoning_details.length > 0) || (Array.isArray(delta?.reasoning_details) && delta.reasoning_details.length > 0) ||
toolDeltas.some((tool) => Boolean(tool.id) || Boolean(tool.function?.name) || Boolean(tool.function?.arguments)) toolDeltas.some(
(tool) => Boolean(tool.id) || Boolean(tool.function?.name) || Boolean(tool.function?.arguments),
)
if (state.finishReason !== undefined) { if (state.finishReason !== undefined) {
if (hasLateContent) if (hasLateContent)
return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason") return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason")
return [{ ...state, usage }, events] as const return [{ ...state, usage }, events] as const
} }
const reasoningField = state.reasoningField ?? reasoning?.field const reasoningField = state.reasoningField ?? (!state.lifecycle.text.has("text-0") ? reasoning?.field : undefined)
const detailDelta = Array.isArray(delta?.reasoning_details) ? delta.reasoning_details : undefined const detailDelta = Array.isArray(delta?.reasoning_details) ? delta.reasoning_details : undefined
if (detailDelta !== undefined) appendReasoningDetails(state.reasoningDetails, detailDelta) if (detailDelta !== undefined) appendReasoningDetails(state.reasoningDetails, detailDelta)
const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined
const deltaMetadata = reasoningMetadata(reasoningField) const deltaMetadata = reasoningMetadata(reasoningField)
const text = detailDelta?.length ? (detailText(detailDelta) ?? reasoning?.text) : reasoning?.text const text = detailDelta?.length ? (detailText(detailDelta) ?? reasoning?.text) : reasoning?.text
if (text !== undefined) lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", text, deltaMetadata) if (!state.lifecycle.text.has("text-0") && text !== undefined)
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", text, deltaMetadata)
else if ( else if (
reasoningDetailsObserved && reasoningDetailsObserved &&
!lifecycle.reasoning.has("reasoning-0") && !lifecycle.reasoning.has("reasoning-0") &&
@@ -749,7 +751,8 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
const fallback = toolDeltas.length > 1 ? position : (latestToolIndex ?? position) const fallback = toolDeltas.length > 1 ? position : (latestToolIndex ?? position)
const fallbackTool = tools[fallback] ?? pendingTools[fallback] const fallbackTool = tools[fallback] ?? pendingTools[fallback]
const index = const index =
tool.index ?? matched ?? (tool.id && fallbackTool?.id && fallbackTool.id !== tool.id ? nextToolIndex : fallback) tool.index ?? matched ??
(tool.id && fallbackTool?.id && fallbackTool.id !== tool.id ? nextToolIndex : fallback)
const current = tools[index] const current = tools[index]
const pending = pendingTools[index] const pending = pendingTools[index]
const id = current?.id ?? pending?.id ?? (tool.id || undefined) const id = current?.id ?? pending?.id ?? (tool.id || undefined)
@@ -812,18 +815,14 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
const finishEvents = (state: ParserState): ReadonlyArray<LLMEvent> => { const finishEvents = (state: ParserState): ReadonlyArray<LLMEvent> => {
const events: LLMEvent[] = [] const events: LLMEvent[] = []
const toolCallEvents = const hasToolCalls = state.toolCallEvents.length > 0
state.finishReason === undefined && Object.keys(state.tools).length > 0
? Effect.runSync(ToolStream.finishAll(ADAPTER, state.tools)).events
: state.toolCallEvents
const hasToolCalls = toolCallEvents.length > 0
const reason = state.finishReason const reason = state.finishReason
? { ? {
...state.finishReason, ...state.finishReason,
normalized: normalized:
state.finishReason.normalized === "stop" && hasToolCalls ? "tool-calls" : state.finishReason.normalized, state.finishReason.normalized === "stop" && hasToolCalls ? "tool-calls" : state.finishReason.normalized,
} }
: { normalized: hasToolCalls ? ("tool-calls" as const) : ("unknown" as const) } : undefined
const metadata = reasoningMetadata( const metadata = reasoningMetadata(
state.reasoningField, state.reasoningField,
state.reasoningDetailsObserved ? state.reasoningDetails : undefined, state.reasoningDetailsObserved ? state.reasoningDetails : undefined,
@@ -833,9 +832,9 @@ const finishEvents = (state: ParserState): ReadonlyArray<LLMEvent> => {
? Lifecycle.reasoningStart(state.lifecycle, events, "reasoning-0", reasoningMetadata(state.reasoningField)) ? Lifecycle.reasoningStart(state.lifecycle, events, "reasoning-0", reasoningMetadata(state.reasoningField))
: state.lifecycle : state.lifecycle
const ended = Lifecycle.reasoningEnd(started, events, "reasoning-0", metadata) const ended = Lifecycle.reasoningEnd(started, events, "reasoning-0", metadata)
const lifecycle = toolCallEvents.length ? Lifecycle.stepStart(ended, events) : ended const lifecycle = state.toolCallEvents.length ? Lifecycle.stepStart(ended, events) : ended
events.push(...toolCallEvents) events.push(...state.toolCallEvents)
Lifecycle.finish(lifecycle, events, { reason, usage: state.usage }) if (reason) Lifecycle.finish(lifecycle, events, { reason, usage: state.usage })
return events return events
} }
+7 -1
View File
@@ -255,7 +255,13 @@ const requireTerminalEvent = (route: string) => (events: Stream.Stream<LLMEvent,
if (LLMEvent.is.finish(event) || LLMEvent.is.providerError(event)) terminal = true if (LLMEvent.is.finish(event) || LLMEvent.is.providerError(event)) terminal = true
return Effect.succeed(event) return Effect.succeed(event)
}), }),
Stream.onEnd(Effect.suspend(() => (terminal ? Effect.void : Effect.fail(incompleteStreamError(route))))), Stream.onEnd(
Effect.suspend(() =>
terminal
? Effect.void
: Effect.fail(incompleteStreamError(route)),
),
),
) )
}) })
+2 -14
View File
@@ -1,4 +1,4 @@
import { Cause, Context, Effect, Layer, Option, Schema } from "effect" import { Cause, Context, Effect, Layer } from "effect"
import { import {
FetchHttpClient, FetchHttpClient,
Headers, Headers,
@@ -198,20 +198,8 @@ const responseBody = (body: string | void, request: HttpClientRequest.HttpClient
return { body: redacted.slice(0, BODY_LIMIT), bodyTruncated: true } return { body: redacted.slice(0, BODY_LIMIT), bodyTruncated: true }
} }
const decodeProviderBody = Schema.decodeUnknownOption(
Schema.fromJsonString(
Schema.Struct({
message: Schema.optionalKey(Schema.String),
error: Schema.optionalKey(Schema.Struct({ message: Schema.optionalKey(Schema.String) })),
}),
),
)
const providerMessage = (status: number, body: { readonly body?: string }) => { const providerMessage = (status: number, body: { readonly body?: string }) => {
if (body.body && body.body.length <= 500) { if (body.body && body.body.length <= 500) return `Provider request failed with HTTP ${status}: ${body.body}`
const decoded = Option.getOrUndefined(decodeProviderBody(body.body))
return `Provider request failed with HTTP ${status}: ${decoded?.error?.message ?? decoded?.message ?? body.body}`
}
return `Provider request failed with HTTP ${status}` return `Provider request failed with HTTP ${status}`
} }
+7 -6
View File
@@ -45,7 +45,8 @@ const isToolResultValue = (value: unknown): value is ToolResultValue =>
(value.type === "text" || value.type === "json" || value.type === "error" || value.type === "content") && (value.type === "text" || value.type === "json" || value.type === "error" || value.type === "content") &&
"value" in value "value" in value
const toolResultValueSchema = Schema.Union([ export const ToolResultValue = Object.assign(
Schema.Union([
Schema.Struct({ Schema.Struct({
type: Schema.Literal("json"), type: Schema.Literal("json"),
value: Schema.Unknown, value: Schema.Unknown,
@@ -62,17 +63,17 @@ const toolResultValueSchema = Schema.Union([
type: Schema.Literal("content"), type: Schema.Literal("content"),
value: Schema.Array(Tool.Content), value: Schema.Array(Tool.Content),
}), }),
]).annotate({ identifier: "LLM.ToolResult" }) ]).annotate({ identifier: "LLM.ToolResult" }),
export type ToolResultValue = Schema.Schema.Type<typeof toolResultValueSchema> {
export const ToolResultValue = Object.assign(toolResultValueSchema, {
is: isToolResultValue, is: isToolResultValue,
make: (value: unknown, type: ToolResultValue["type"] = "json"): ToolResultValue => { make: (value: unknown, type: ToolResultValue["type"] = "json"): ToolResultValue => {
if (isToolResultValue(value)) return value if (isToolResultValue(value)) return value
if (type === "content") return { type, value: Array.isArray(value) ? value : [] } if (type === "content") return { type, value: Array.isArray(value) ? value : [] }
return { type, value } return { type, value }
}, },
}) },
)
export type ToolResultValue = Schema.Schema.Type<typeof ToolResultValue>
export interface ToolOutput { export interface ToolOutput {
readonly structured: unknown readonly structured: unknown
+7 -2
View File
@@ -1,6 +1,10 @@
import { Effect, JsonSchema, Schema } from "effect" import { Effect, JsonSchema, Schema } from "effect"
import { Tool } from "@opencode-ai/schema/tool" import { Tool } from "@opencode-ai/schema/tool"
import type { ToolCallPart, ToolDefinition as ToolDefinitionClass, ToolOutput as ToolOutputType } from "./schema" import type {
ToolCallPart,
ToolDefinition as ToolDefinitionClass,
ToolOutput as ToolOutputType,
} from "./schema"
import { ToolDefinition, ToolFailure, ToolOutput } from "./schema" import { ToolDefinition, ToolFailure, ToolOutput } from "./schema"
/** /**
@@ -240,7 +244,8 @@ const project = (
): ToolOutputType => ): ToolOutputType =>
ToolOutput.make( ToolOutput.make(
toStructuredOutput?.(output) ?? output, toStructuredOutput?.(output) ?? output,
toModelOutput?.({ id, parameters, output }) ?? (typeof output === "string" ? [{ type: "text", text: output }] : []), toModelOutput?.({ id, parameters, output }) ??
(typeof output === "string" ? [{ type: "text", text: output }] : []),
) )
export { ToolFailure } export { ToolFailure }
@@ -1,7 +1,13 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:anthropic-messages-cache", "provider:anthropic", "protocol:anthropic-messages", "cache", "tool"], "tags": [
"prefix:anthropic-messages-cache",
"provider:anthropic",
"protocol:anthropic-messages",
"cache",
"tool"
],
"name": "anthropic-messages-cache/keeps-a-long-tool-turn-inside-the-cache-lookback", "name": "anthropic-messages-cache/keeps-a-long-tool-turn-inside-the-cache-lookback",
"recordedAt": "2026-07-24T16:22:29.494Z" "recordedAt": "2026-07-24T16:22:29.494Z"
}, },
@@ -1,7 +1,12 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:bedrock-converse-cache", "provider:amazon-bedrock", "protocol:bedrock-converse", "cache"], "tags": [
"prefix:bedrock-converse-cache",
"provider:amazon-bedrock",
"protocol:bedrock-converse",
"cache"
],
"name": "bedrock-converse-cache/writes-then-reads-cachepoint-on-identical-second-call", "name": "bedrock-converse-cache/writes-then-reads-cachepoint-on-identical-second-call",
"recordedAt": "2026-07-23T02:29:10.955Z" "recordedAt": "2026-07-23T02:29:10.955Z"
}, },
@@ -1,7 +1,11 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:google-images", "provider:google", "protocol:google-images"], "tags": [
"prefix:google-images",
"provider:google",
"protocol:google-images"
],
"name": "google-images/generates-an-image", "name": "google-images/generates-an-image",
"recordedAt": "2026-07-19T16:05:51.868Z" "recordedAt": "2026-07-19T16:05:51.868Z"
}, },
@@ -1,7 +1,11 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:openai-images", "provider:openai", "protocol:openai-images"], "tags": [
"prefix:openai-images",
"provider:openai",
"protocol:openai-images"
],
"name": "openai-images/generates-an-image", "name": "openai-images/generates-an-image",
"recordedAt": "2026-07-19T14:41:43.188Z" "recordedAt": "2026-07-19T14:41:43.188Z"
}, },
@@ -1,7 +1,11 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:openai-responses-images", "provider:openai", "protocol:openai-responses"], "tags": [
"prefix:openai-responses-images",
"provider:openai",
"protocol:openai-responses"
],
"name": "openai-responses-images/generates-and-edits-an-image-with-the-hosted-tool", "name": "openai-responses-images/generates-and-edits-an-image-with-the-hosted-tool",
"recordedAt": "2026-07-19T14:57:16.284Z" "recordedAt": "2026-07-19T14:57:16.284Z"
}, },
@@ -2,7 +2,12 @@
"version": 1, "version": 1,
"metadata": { "metadata": {
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"tags": ["prefix:openai-compatible-chat", "provider:openrouter", "protocol:openai-chat", "reasoning"], "tags": [
"prefix:openai-compatible-chat",
"provider:openrouter",
"protocol:openai-chat",
"reasoning"
],
"name": "openrouter-reasoning", "name": "openrouter-reasoning",
"recordedAt": "2026-07-18T11:28:39.267Z" "recordedAt": "2026-07-18T11:28:39.267Z"
}, },
@@ -1,7 +1,14 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:anthropic", "protocol:anthropic-messages", "tool", "tool-result"], "tags": [
"prefix:pdf",
"pdf",
"provider:anthropic",
"protocol:anthropic-messages",
"tool",
"tool-result"
],
"name": "pdf/anthropic-tool-result", "name": "pdf/anthropic-tool-result",
"recordedAt": "2026-07-22T18:15:39.002Z" "recordedAt": "2026-07-22T18:15:39.002Z"
}, },
@@ -1,7 +1,13 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:anthropic", "protocol:anthropic-messages", "user-input"], "tags": [
"prefix:pdf",
"pdf",
"provider:anthropic",
"protocol:anthropic-messages",
"user-input"
],
"name": "pdf/anthropic-user-input", "name": "pdf/anthropic-user-input",
"recordedAt": "2026-07-22T18:15:37.979Z" "recordedAt": "2026-07-22T18:15:37.979Z"
}, },
@@ -1,7 +1,14 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:amazon-bedrock", "protocol:bedrock-converse", "tool", "tool-result"], "tags": [
"prefix:pdf",
"pdf",
"provider:amazon-bedrock",
"protocol:bedrock-converse",
"tool",
"tool-result"
],
"name": "pdf/bedrock-tool-result", "name": "pdf/bedrock-tool-result",
"recordedAt": "2026-07-22T18:15:52.400Z" "recordedAt": "2026-07-22T18:15:52.400Z"
}, },
@@ -1,7 +1,13 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:amazon-bedrock", "protocol:bedrock-converse", "user-input"], "tags": [
"prefix:pdf",
"pdf",
"provider:amazon-bedrock",
"protocol:bedrock-converse",
"user-input"
],
"name": "pdf/bedrock-user-input", "name": "pdf/bedrock-user-input",
"recordedAt": "2026-07-22T18:15:48.408Z" "recordedAt": "2026-07-22T18:15:48.408Z"
}, },
@@ -1,7 +1,14 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:google", "protocol:gemini", "tool", "tool-result"], "tags": [
"prefix:pdf",
"pdf",
"provider:google",
"protocol:gemini",
"tool",
"tool-result"
],
"name": "pdf/gemini-tool-result", "name": "pdf/gemini-tool-result",
"recordedAt": "2026-07-22T18:21:59.606Z" "recordedAt": "2026-07-22T18:21:59.606Z"
}, },
@@ -1,7 +1,13 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:google", "protocol:gemini", "user-input"], "tags": [
"prefix:pdf",
"pdf",
"provider:google",
"protocol:gemini",
"user-input"
],
"name": "pdf/gemini-user-input", "name": "pdf/gemini-user-input",
"recordedAt": "2026-07-22T18:20:55.140Z" "recordedAt": "2026-07-22T18:20:55.140Z"
}, },
@@ -1,7 +1,14 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:openai", "protocol:openai-responses", "tool", "tool-result"], "tags": [
"prefix:pdf",
"pdf",
"provider:openai",
"protocol:openai-responses",
"tool",
"tool-result"
],
"name": "pdf/openai-tool-result", "name": "pdf/openai-tool-result",
"recordedAt": "2026-07-22T18:15:36.438Z" "recordedAt": "2026-07-22T18:15:36.438Z"
}, },
@@ -1,7 +1,13 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:openai", "protocol:openai-responses", "user-input"], "tags": [
"prefix:pdf",
"pdf",
"provider:openai",
"protocol:openai-responses",
"user-input"
],
"name": "pdf/openai-user-input", "name": "pdf/openai-user-input",
"recordedAt": "2026-07-22T18:15:34.867Z" "recordedAt": "2026-07-22T18:15:34.867Z"
}, },
@@ -1,7 +1,14 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:xai", "protocol:openai-responses", "tool", "tool-result"], "tags": [
"prefix:pdf",
"pdf",
"provider:xai",
"protocol:openai-responses",
"tool",
"tool-result"
],
"name": "pdf/xai-tool-result", "name": "pdf/xai-tool-result",
"recordedAt": "2026-07-22T18:15:43.608Z" "recordedAt": "2026-07-22T18:15:43.608Z"
}, },
@@ -1,7 +1,13 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:pdf", "pdf", "provider:xai", "protocol:openai-responses", "user-input"], "tags": [
"prefix:pdf",
"pdf",
"provider:xai",
"protocol:openai-responses",
"user-input"
],
"name": "pdf/xai-user-input", "name": "pdf/xai-user-input",
"recordedAt": "2026-07-22T18:15:42.429Z" "recordedAt": "2026-07-22T18:15:42.429Z"
}, },
@@ -2,7 +2,12 @@
"version": 1, "version": 1,
"metadata": { "metadata": {
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"tags": ["prefix:openai-compatible-chat", "provider:vercel-ai-gateway", "protocol:openai-chat", "reasoning"], "tags": [
"prefix:openai-compatible-chat",
"provider:vercel-ai-gateway",
"protocol:openai-chat",
"reasoning"
],
"name": "vercel-ai-gateway-reasoning", "name": "vercel-ai-gateway-reasoning",
"recordedAt": "2026-07-18T11:28:42.077Z" "recordedAt": "2026-07-18T11:28:42.077Z"
}, },
@@ -1,7 +1,11 @@
{ {
"version": 1, "version": 1,
"metadata": { "metadata": {
"tags": ["prefix:xai-images", "provider:xai", "protocol:xai-images"], "tags": [
"prefix:xai-images",
"provider:xai",
"protocol:xai-images"
],
"name": "xai-images/generates-an-image", "name": "xai-images/generates-an-image",
"recordedAt": "2026-07-19T15:56:20.098Z" "recordedAt": "2026-07-19T15:56:20.098Z"
}, },
@@ -519,8 +519,14 @@ describe("Bedrock Converse route", () => {
fixedBytes( fixedBytes(
eventStreamBody( eventStreamBody(
["messageStart", { role: "assistant" }], ["messageStart", { role: "assistant" }],
["contentBlockDelta", { contentBlockIndex: 0, delta: { reasoningContent: { text: "Let me think." } } }], [
["contentBlockDelta", { contentBlockIndex: 0, delta: { reasoningContent: { signature: "sig_1" } } }], "contentBlockDelta",
{ contentBlockIndex: 0, delta: { reasoningContent: { text: "Let me think." } } },
],
[
"contentBlockDelta",
{ contentBlockIndex: 0, delta: { reasoningContent: { signature: "sig_1" } } },
],
["messageStop", { stopReason: "end_turn" }], ["messageStop", { stopReason: "end_turn" }],
), ),
), ),
@@ -555,7 +561,10 @@ describe("Bedrock Converse route", () => {
Effect.gen(function* () { Effect.gen(function* () {
const body = eventStreamBody( const body = eventStreamBody(
["messageStart", { role: "assistant" }], ["messageStart", { role: "assistant" }],
["contentBlockDelta", { contentBlockIndex: 0, delta: { reasoningContent: { signature: "sig_1" } } }], [
"contentBlockDelta",
{ contentBlockIndex: 0, delta: { reasoningContent: { signature: "sig_1" } } },
],
["contentBlockStop", { contentBlockIndex: 0 }], ["contentBlockStop", { contentBlockIndex: 0 }],
["messageStop", { stopReason: "end_turn" }], ["messageStop", { stopReason: "end_turn" }],
) )
+26 -48
View File
@@ -102,24 +102,6 @@ describe("OpenAI Chat route", () => {
}), }),
) )
it.effect("concatenates assistant text parts without adding separators", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model,
messages: [
Message.assistant([
{ type: "text", text: "Hello" },
{ type: "text", text: " world" },
]),
],
}),
)
expect(prepared.body.messages).toEqual([{ role: "assistant", content: "Hello world" }])
}),
)
it.effect("writes reasoning to a configured custom field on every assistant message", () => it.effect("writes reasoning to a configured custom field on every assistant message", () =>
Effect.gen(function* () { Effect.gen(function* () {
const prepared = yield* compileRequest( const prepared = yield* compileRequest(
@@ -596,7 +578,7 @@ describe("OpenAI Chat route", () => {
}), }),
) )
expect(prepared.body.messages).toEqual([{ role: "assistant", content: "", reasoning_content: "hidden" }]) expect(prepared.body.messages).toEqual([{ role: "assistant", content: null, reasoning_content: "hidden" }])
}), }),
) )
@@ -845,7 +827,7 @@ describe("OpenAI Chat route", () => {
}), }),
) )
it.effect("preserves scalar reasoning after content starts", () => it.effect("ignores scalar reasoning after content starts", () =>
Effect.gen(function* () { Effect.gen(function* () {
const details = [{ type: "reasoning.text", text: "detail", format: "unknown", index: 0 }] const details = [{ type: "reasoning.text", text: "detail", format: "unknown", index: 0 }]
const response = yield* LLMClient.generate(request).pipe( const response = yield* LLMClient.generate(request).pipe(
@@ -861,11 +843,11 @@ describe("OpenAI Chat route", () => {
), ),
) )
expect(response.reasoning).toBe("detailscalar") expect(response.reasoning).toBe("detail")
expect(response.events.filter(LLMEvent.is.reasoningStart)).toHaveLength(2) expect(response.events.filter(LLMEvent.is.reasoningStart)).toHaveLength(1)
expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(2) expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(1)
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({ expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
openai: { reasoningField: "reasoning", reasoningDetails: details }, openai: { reasoningDetails: details },
}) })
}), }),
) )
@@ -965,7 +947,7 @@ describe("OpenAI Chat route", () => {
expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(1) expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(1)
const replay = yield* compileRequest(LLM.request({ model, messages: [response.message] })) const replay = yield* compileRequest(LLM.request({ model, messages: [response.message] }))
expect(replay.body.messages).toEqual([{ role: "assistant", content: "", reasoning_details: details }]) expect(replay.body.messages).toEqual([{ role: "assistant", content: null, reasoning_details: details }])
}), }),
) )
@@ -1015,7 +997,7 @@ describe("OpenAI Chat route", () => {
) )
expect(replay.body.messages).toEqual([ expect(replay.body.messages).toEqual([
{ role: "assistant", content: "", reasoning: "firstsecond", reasoning_details: [first, second] }, { role: "assistant", content: null, reasoning: "firstsecond", reasoning_details: [first, second] },
]) ])
}), }),
) )
@@ -1040,7 +1022,7 @@ describe("OpenAI Chat route", () => {
) )
expect(replay.body.messages).toEqual([ expect(replay.body.messages).toEqual([
{ role: "assistant", content: "", reasoning_content: "AB", reasoning_details: [detail] }, { role: "assistant", content: null, reasoning_content: "AB", reasoning_details: [detail] },
]) ])
}), }),
) )
@@ -1062,7 +1044,7 @@ describe("OpenAI Chat route", () => {
) )
expect(replay.body.messages).toEqual([ expect(replay.body.messages).toEqual([
{ role: "assistant", content: "", reasoning_content: "thinking", reasoning_details: details }, { role: "assistant", content: null, reasoning_content: "thinking", reasoning_details: details },
]) ])
}), }),
) )
@@ -1170,7 +1152,7 @@ describe("OpenAI Chat route", () => {
}), }),
) )
it.effect("finalizes a streamed tool call when the provider ends without a finish reason", () => it.effect("fails a streamed tool call when the provider ends without a finish reason", () =>
Effect.gen(function* () { Effect.gen(function* () {
const body = sseEvents( const body = sseEvents(
deltaChunk({ deltaChunk({
@@ -1182,31 +1164,27 @@ describe("OpenAI Chat route", () => {
const input = LLMRequest.update(request, { const input = LLMRequest.update(request, {
tools: [ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })], tools: [ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })],
}) })
const response = yield* LLMClient.generate(input).pipe(Effect.provide(fixedResponse(body))) const events: LLMEvent[] = []
const streamError = yield* LLMClient.stream(input).pipe(
Stream.runForEach((event) => Effect.sync(() => events.push(event))),
Effect.flip,
Effect.provide(fixedResponse(body)),
)
const error = yield* LLMClient.generate(input).pipe(Effect.provide(fixedResponse(body)), Effect.flip)
expect(response.events).toEqual([ expect(events).toEqual([
{ type: "step-start", index: 0 }, { type: "step-start", index: 0 },
{ type: "tool-input-start", id: "call_1", name: "lookup", providerMetadata: undefined }, { type: "tool-input-start", id: "call_1", name: "lookup", providerMetadata: undefined },
{ type: "tool-input-delta", id: "call_1", name: "lookup", text: '{"query"' }, { type: "tool-input-delta", id: "call_1", name: "lookup", text: '{"query"' },
{ type: "tool-input-delta", id: "call_1", name: "lookup", text: ':"weather"}' }, { type: "tool-input-delta", id: "call_1", name: "lookup", text: ':"weather"}' },
{ type: "tool-input-end", id: "call_1", name: "lookup", providerMetadata: undefined },
{
type: "tool-call",
id: "call_1",
name: "lookup",
input: { query: "weather" },
providerExecuted: undefined,
providerMetadata: undefined,
},
{
type: "step-finish",
index: 0,
reason: { normalized: "tool-calls" },
usage: undefined,
providerMetadata: undefined,
},
{ type: "finish", reason: { normalized: "tool-calls" }, usage: undefined },
]) ])
expect(events.filter(LLMEvent.is.toolCall)).toEqual([])
expect(streamError.reason).toMatchObject({
_tag: "InvalidProviderOutput",
classification: "incomplete-stream",
})
expect(streamError.message).toContain("The provider response ended unexpectedly.")
expect(error.message).toContain("The provider response ended unexpectedly.")
}), }),
) )
@@ -300,9 +300,7 @@ describe("OpenAI-compatible Chat route", () => {
Effect.gen(function* () { Effect.gen(function* () {
const response = yield* LLMClient.generate( const response = yield* LLMClient.generate(
LLMRequest.update(request, { LLMRequest.update(request, {
tools: [ tools: [ToolDefinition.make({ name: "weather", description: "Get weather", inputSchema: { type: "object" } })],
ToolDefinition.make({ name: "weather", description: "Get weather", inputSchema: { type: "object" } }),
],
}), }),
).pipe( ).pipe(
Effect.provide( Effect.provide(
@@ -85,7 +85,8 @@ async function mockServers(page: Page, requests: string[]) {
const current = url.origin === serverA ? sessionA : sessionB const current = url.origin === serverA ? sessionA : sessionB
const directory = url.searchParams.get("directory") const directory = url.searchParams.get("directory")
if (directory && directory !== current.directory) return json(route, { name: "InvalidDirectory" }, 500) if (directory && directory !== current.directory) return json(route, { name: "InvalidDirectory" }, 500)
if (url.pathname === "/api/event") return sse(route) if (url.pathname === "/api/event")
return sse(route)
if (url.pathname === "/api/health") return json(route, { pid: 1 }) if (url.pathname === "/api/health") return json(route, { pid: 1 })
if (url.pathname === "/api/session") return json(route, { data: [currentSession(current)], cursor: {} }) if (url.pathname === "/api/session") return json(route, { data: [currentSession(current)], cursor: {} })
if (url.pathname === "/api/session/active") return json(route, { data: {} }) if (url.pathname === "/api/session/active") return json(route, { data: {} })
@@ -181,17 +181,12 @@ async function mockServers(page: Page, permissionRequests: string[], permissionR
return json(route, true) return json(route, true)
} }
if (requestDirectory && requestDirectory !== directory) return json(route, { name: "InvalidDirectory" }, 500) if (requestDirectory && requestDirectory !== directory) return json(route, { name: "InvalidDirectory" }, 500)
if (url.pathname === "/api/event") return sse(route) if (url.pathname === "/api/event")
return sse(route)
if (url.pathname === "/api/provider") if (url.pathname === "/api/provider")
return json(route, { return json(route, {
location: { directory }, location: { directory },
data: [ data: [{ id: remote ? "server-b" : "server-a", name: remote ? "Server B Provider" : "Server A Provider", package: "test" }],
{
id: remote ? "server-b" : "server-a",
name: remote ? "Server B Provider" : "Server A Provider",
package: "test",
},
],
}) })
if (url.pathname === "/api/model") return json(route, { location: { directory }, data: [model(remote)] }) if (url.pathname === "/api/model") return json(route, { location: { directory }, data: [model(remote)] })
if (url.pathname === "/api/model/default") return json(route, { location: { directory }, data: model(remote) }) if (url.pathname === "/api/model/default") return json(route, { location: { directory }, data: model(remote) })
@@ -58,7 +58,8 @@ async function mockServers(page: Page) {
const current = url.origin === serverA ? sessionA : sessionB const current = url.origin === serverA ? sessionA : sessionB
const directory = url.searchParams.get("directory") const directory = url.searchParams.get("directory")
if (directory && directory !== current.directory) return json(route, { name: "InvalidDirectory" }, 500) if (directory && directory !== current.directory) return json(route, { name: "InvalidDirectory" }, 500)
if (url.pathname === "/api/event") return sse(route, url.pathname === "/api/event") if (url.pathname === "/api/event")
return sse(route, url.pathname === "/api/event")
if (url.pathname === "/api/health") return json(route, { pid: 1 }) if (url.pathname === "/api/health") return json(route, { pid: 1 })
if (url.pathname === "/api/session/active") if (url.pathname === "/api/session/active")
return json(route, { data: url.origin === serverB ? { [sessionB.id]: { type: "running" } } : {} }) return json(route, { data: url.origin === serverB ? { [sessionB.id]: { type: "running" } } : {} })
@@ -86,10 +86,15 @@ test.describe("session timeline projection", () => {
], ],
{ summary: { diffs: Array.from({ length: 11 }, (_, index) => summaryDiff(index)) } }, { summary: { diffs: Array.from({ length: 11 }, (_, index) => summaryDiff(index)) } },
) )
const aborted = assistantMessage([{ id: "prt_before_abort", type: "text", text: "Before interruption" }], { const aborted = assistantMessage(
[
{ id: "prt_before_abort", type: "text", text: "Before interruption" },
],
{
id: "msg_1001_assistant_aborted", id: "msg_1001_assistant_aborted",
error: { name: "MessageAbortedError", data: { message: "Stopped" } }, error: { name: "MessageAbortedError", data: { message: "Stopped" } },
}) },
)
const failed = assistantMessage([{ id: "prt_after_abort", type: "text", text: "After interruption" }], { const failed = assistantMessage([{ id: "prt_after_abort", type: "text", text: "After interruption" }], {
id: "msg_1002_assistant_failed", id: "msg_1002_assistant_failed",
error: { error: {
@@ -89,7 +89,8 @@ async function mockServer(page: Page) {
if (url.origin !== server) return route.fallback() if (url.origin !== server) return route.fallback()
if ([`/api/session/${unresolvedSessionID}`, `/session/${unresolvedSessionID}`].includes(url.pathname)) if ([`/api/session/${unresolvedSessionID}`, `/session/${unresolvedSessionID}`].includes(url.pathname))
return new Promise(() => {}) return new Promise(() => {})
if (url.pathname === "/api/event") return sse(route) if (url.pathname === "/api/event")
return sse(route)
if (url.pathname === "/api/session") return json(route, { data: sessions.map(currentSession), cursor: {} }) if (url.pathname === "/api/session") return json(route, { data: sessions.map(currentSession), cursor: {} })
if (url.pathname === "/api/session/active") return json(route, { data: {} }) if (url.pathname === "/api/session/active") return json(route, { data: {} })
const currentSessionInfo = sessions.find((item) => url.pathname === `/api/session/${item.id}`) const currentSessionInfo = sessions.find((item) => url.pathname === `/api/session/${item.id}`)
@@ -300,8 +300,9 @@ export const fixture = {
.filter((message) => message.info.role === "user") .filter((message) => message.info.role === "user")
.map((message) => message.info.id), .map((message) => message.info.id),
targetPartIDs: targetMessages.flatMap(currentPartIDs), targetPartIDs: targetMessages.flatMap(currentPartIDs),
expandedShellPartID: targetMessages.flatMap((message) => message.parts).find((part) => part.tool === "bash")! expandedShellPartID: targetMessages
.callID, .flatMap((message) => message.parts)
.find((part) => part.tool === "bash")!.callID,
}, },
} }
+19 -21
View File
@@ -144,8 +144,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
location: location(config), location: location(config),
data: currentProviders(providerConfig(config)), data: currentProviders(providerConfig(config)),
}) })
if (path === "/api/model") if (path === "/api/model") return json(route, { location: location(config), data: currentModels(providerConfig(config)) })
return json(route, { location: location(config), data: currentModels(providerConfig(config)) })
if (path === "/api/model/default") if (path === "/api/model/default")
return json(route, { location: location(config), data: currentDefaultModel(providerConfig(config)) }) return json(route, { location: location(config), data: currentDefaultModel(providerConfig(config)) })
if (path === "/api/integration") return json(route, { location: location(config), data: [] }) if (path === "/api/integration") return json(route, { location: location(config), data: [] })
@@ -207,8 +206,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
const fileRead = path.match(/^\/api\/fs\/read\/(.+)$/)?.[1] const fileRead = path.match(/^\/api\/fs\/read\/(.+)$/)?.[1]
if (fileRead && config.fileContent) { if (fileRead && config.fileContent) {
const value = await config.fileContent(decodeURIComponent(fileRead)) const value = await config.fileContent(decodeURIComponent(fileRead))
const content = const content = value && typeof value === "object" && "content" in value ? String(value.content) : String(value ?? "")
value && typeof value === "object" && "content" in value ? String(value.content) : String(value ?? "")
return route.fulfill({ status: 200, body: content, headers: { "content-type": "application/octet-stream" } }) return route.fulfill({ status: 200, body: content, headers: { "content-type": "application/octet-stream" } })
} }
if (path === "/api/fs/find" && config.findFiles) { if (path === "/api/fs/find" && config.findFiles) {
@@ -283,8 +281,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
if (/^\/api\/session\/[^/]+\/permission\/[^/]+\/reply$/.test(path) && route.request().method() === "POST") { if (/^\/api\/session\/[^/]+\/permission\/[^/]+\/reply$/.test(path) && route.request().method() === "POST") {
return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } }) return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } })
} }
if (/^\/question\/[^/]+\/(reply|reject)$/.test(path) && route.request().method() === "POST") if (/^\/question\/[^/]+\/(reply|reject)$/.test(path) && route.request().method() === "POST") return json(route, true)
return json(route, true)
if (/^\/session\/[^/]+\/permissions\/[^/]+$/.test(path) && route.request().method() === "POST") if (/^\/session\/[^/]+\/permissions\/[^/]+$/.test(path) && route.request().method() === "POST")
return json(route, true) return json(route, true)
if ( if (
@@ -389,9 +386,7 @@ function providerConfig(config: MockServerConfig) {
function currentProviders(value: unknown) { function currentProviders(value: unknown) {
if (!record(value) || !Array.isArray(value.all)) return Array.isArray(value) ? value : [] if (!record(value) || !Array.isArray(value.all)) return Array.isArray(value) ? value : []
return value.all return value.all.filter(record).flatMap((provider) =>
.filter(record)
.flatMap((provider) =>
typeof provider.id === "string" && typeof provider.name === "string" typeof provider.id === "string" && typeof provider.name === "string"
? [{ id: provider.id, name: provider.name, package: provider.id }] ? [{ id: provider.id, name: provider.name, package: provider.id }]
: [], : [],
@@ -445,7 +440,9 @@ function currentDefaultModel(value: unknown) {
if (!record(value) || !record(value.default)) return null if (!record(value) || !record(value.default)) return null
const selected = value.default const selected = value.default
const models = currentModels(value) const models = currentModels(value)
return models.find((model) => model.providerID === selected.providerID && model.id === selected.modelID) ?? null return models.find(
(model) => model.providerID === selected.providerID && model.id === selected.modelID,
) ?? null
} }
function currentPermission(value: unknown) { function currentPermission(value: unknown) {
@@ -565,16 +562,22 @@ function legacyAgent(part: Record<string, unknown>): PromptAgentAttachment[] {
} }
function mentionFrom(value: Record<string, unknown> | undefined) { function mentionFrom(value: Record<string, unknown> | undefined) {
if (!value || typeof value.value !== "string" || typeof value.start !== "number" || typeof value.end !== "number") if (
!value ||
typeof value.value !== "string" ||
typeof value.start !== "number" ||
typeof value.end !== "number"
)
return return
return { text: value.value, start: value.start, end: value.end } return { text: value.value, start: value.start, end: value.end }
} }
function legacyAssistantContent(part: Record<string, unknown>, created: number): SessionMessageAssistant["content"] { function legacyAssistantContent(
part: Record<string, unknown>,
created: number,
): SessionMessageAssistant["content"] {
if (part.type === "text" && typeof part.text === "string") if (part.type === "text" && typeof part.text === "string")
return [ return [{ type: "text", text: part.text, ...(jsonRecord(part.metadata) ? { state: jsonRecord(part.metadata) } : {}) }]
{ type: "text", text: part.text, ...(jsonRecord(part.metadata) ? { state: jsonRecord(part.metadata) } : {}) },
]
if (part.type === "reasoning" && typeof part.text === "string") { if (part.type === "reasoning" && typeof part.text === "string") {
const time = record(part.time) ? part.time : undefined const time = record(part.time) ? part.time : undefined
return [ return [
@@ -615,12 +618,7 @@ function legacyAssistantContent(part: Record<string, unknown>, created: number):
...(jsonRecord(part.providerResultState) ? { providerResultState: jsonRecord(part.providerResultState) } : {}), ...(jsonRecord(part.providerResultState) ? { providerResultState: jsonRecord(part.providerResultState) } : {}),
} }
if (state.status === "pending") if (state.status === "pending")
return [ return [{ ...base, state: { status: "streaming", input: typeof state.raw === "string" ? state.raw : JSON.stringify(input) } }]
{
...base,
state: { status: "streaming", input: typeof state.raw === "string" ? state.raw : JSON.stringify(input) },
},
]
if (state.status === "completed") if (state.status === "completed")
return [ return [
{ {
+2 -1
View File
@@ -174,7 +174,8 @@ export async function installSseTransport<T>(
const fetch = (input: RequestInfo | URL, init?: RequestInit) => { const fetch = (input: RequestInfo | URL, init?: RequestInit) => {
const request = new Request(input, init) const request = new Request(input, init)
const url = new URL(request.url) const url = new URL(request.url)
if (url.origin !== server || url.pathname !== "/api/event") return originalFetch(request) if (url.origin !== server || url.pathname !== "/api/event")
return originalFetch(request)
const id = ++nextConnectionID const id = ++nextConnectionID
const record = { const record = {
+1 -4
View File
@@ -20,10 +20,7 @@
<meta property="twitter:image" content="/social-share.png" /> <meta property="twitter:image" content="/social-share.png" />
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script> <script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
</head> </head>
<body <body data-new-layout class="antialiased overscroll-none font-(family-name:--font-family-text) text-[13px] font-[440] overflow-hidden bg-v2-background-bg-deep">
data-new-layout
class="antialiased overscroll-none font-(family-name:--font-family-text) text-[13px] font-[440] overflow-hidden bg-v2-background-bg-deep"
>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="flex flex-col h-dvh bg-v2-background-bg-deep p-px"></div> <div id="root" class="flex flex-col h-dvh bg-v2-background-bg-deep p-px"></div>
<script src="/src/entry.tsx" type="module"></script> <script src="/src/entry.tsx" type="module"></script>
+8 -1
View File
@@ -9,7 +9,14 @@ import { Font } from "@opencode-ai/ui/font"
import { Splash } from "@opencode-ai/ui/logo" import { Splash } from "@opencode-ai/ui/logo"
import { ThemeProvider } from "@opencode-ai/ui/theme/context" import { ThemeProvider } from "@opencode-ai/ui/theme/context"
import { MetaProvider } from "@solidjs/meta" import { MetaProvider } from "@solidjs/meta"
import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router" import {
type BaseRouterProps,
Navigate,
Route,
Router,
useParams,
useSearchParams,
} from "@solidjs/router"
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
import { Effect } from "effect" import { Effect } from "effect"
import { import {
@@ -1,4 +1,4 @@
import type { FormAnswer, IntegrationMethod, IntegrationOauthConnectOutput } from "@opencode-ai/client/promise" import type { IntegrationMethod, IntegrationOauthConnectOutput } from "@opencode-ai/client/promise"
import { Button } from "@opencode-ai/ui/button" import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog" import { Dialog } from "@opencode-ai/ui/dialog"
@@ -40,8 +40,6 @@ import { decode64 } from "@/utils/base64"
const CUSTOM_ID = "_custom" const CUSTOM_ID = "_custom"
type ConnectMethod = Extract<IntegrationMethod, { type: "key" | "oauth" }> type ConnectMethod = Extract<IntegrationMethod, { type: "key" | "oauth" }>
type IntegrationForm = NonNullable<ConnectMethod["form"]>[number]
type StringForm = Extract<IntegrationForm, { type: "string" }>
export function useProviderConnectController(options: { onBack?: () => void } = {}) { export function useProviderConnectController(options: { onBack?: () => void } = {}) {
const [store, setStore] = createStore({ selected: undefined as string | undefined }) const [store, setStore] = createStore({ selected: undefined as string | undefined })
@@ -436,16 +434,16 @@ function ProviderConnection(props: {
const [store, setStore] = createStore({ const [store, setStore] = createStore({
methodIndex: undefined as undefined | number, methodIndex: undefined as undefined | number,
authorization: undefined as undefined | IntegrationOauthConnectOutput["data"], authorization: undefined as undefined | IntegrationOauthConnectOutput["data"],
formAnswer: undefined as FormAnswer | undefined, promptInputs: undefined as undefined | Record<string, string>,
state: "pending" as undefined | "pending" | "complete" | "error" | "form", state: "pending" as undefined | "pending" | "complete" | "error" | "prompt",
error: undefined as string | undefined, error: undefined as string | undefined,
}) })
type Action = type Action =
| { type: "method.select"; index: number } | { type: "method.select"; index: number }
| { type: "method.reset" } | { type: "method.reset" }
| { type: "auth.form" } | { type: "auth.prompt" }
| { type: "auth.answer"; answer: FormAnswer | undefined } | { type: "auth.inputs"; inputs: Record<string, string> }
| { type: "auth.pending" } | { type: "auth.pending" }
| { type: "auth.complete"; authorization: IntegrationOauthConnectOutput["data"] } | { type: "auth.complete"; authorization: IntegrationOauthConnectOutput["data"] }
| { type: "auth.error"; error: string } | { type: "auth.error"; error: string }
@@ -456,7 +454,7 @@ function ProviderConnection(props: {
if (action.type === "method.select") { if (action.type === "method.select") {
draft.methodIndex = action.index draft.methodIndex = action.index
draft.authorization = undefined draft.authorization = undefined
draft.formAnswer = undefined draft.promptInputs = undefined
draft.state = undefined draft.state = undefined
draft.error = undefined draft.error = undefined
return return
@@ -464,18 +462,18 @@ function ProviderConnection(props: {
if (action.type === "method.reset") { if (action.type === "method.reset") {
draft.methodIndex = undefined draft.methodIndex = undefined
draft.authorization = undefined draft.authorization = undefined
draft.formAnswer = undefined draft.promptInputs = undefined
draft.state = undefined draft.state = undefined
draft.error = undefined draft.error = undefined
return return
} }
if (action.type === "auth.form") { if (action.type === "auth.prompt") {
draft.state = "form" draft.state = "prompt"
draft.error = undefined draft.error = undefined
return return
} }
if (action.type === "auth.answer") { if (action.type === "auth.inputs") {
draft.formAnswer = action.answer draft.promptInputs = action.inputs
draft.state = undefined draft.state = undefined
draft.error = undefined draft.error = undefined
return return
@@ -533,7 +531,7 @@ function ProviderConnection(props: {
return fallback return fallback
} }
async function selectMethod(index: number, answer?: FormAnswer) { async function selectMethod(index: number, inputs?: Record<string, string>) {
if (timer.current !== undefined) { if (timer.current !== undefined) {
clearTimeout(timer.current) clearTimeout(timer.current)
timer.current = undefined timer.current = undefined
@@ -542,17 +540,9 @@ function ProviderConnection(props: {
const method = methods()[index] const method = methods()[index]
dispatch({ type: "method.select", index }) dispatch({ type: "method.select", index })
if (method.form?.length && !answer) {
dispatch({ type: "auth.form" })
return
}
if (method.type === "key") {
dispatch({ type: "auth.answer", answer })
return
}
if (method.type === "oauth") { if (method.type === "oauth") {
if (method.form?.some((field) => field.type !== "string")) { if (method.prompts?.length && !inputs) {
dispatch({ type: "auth.error", error: "This authentication form contains unsupported fields" }) dispatch({ type: "auth.prompt" })
return return
} }
dispatch({ type: "auth.pending" }) dispatch({ type: "auth.pending" })
@@ -560,7 +550,7 @@ function ProviderConnection(props: {
.api.integration.oauth.connect({ .api.integration.oauth.connect({
integrationID: props.provider, integrationID: props.provider,
methodID: method.id, methodID: method.id,
...(answer ? { answer } : {}), inputs: inputs ?? {},
location: location(), location: location(),
}) })
.then((x) => { .then((x) => {
@@ -574,42 +564,41 @@ function ProviderConnection(props: {
} }
} }
function AuthFormView() { function AuthPromptsView() {
const [formStore, setFormStore] = createStore({ const [formStore, setFormStore] = createStore({
value: {} as Record<string, string>, value: {} as Record<string, string>,
index: 0, index: 0,
}) })
const fields = createMemo<StringForm[]>(() => { const prompts = createMemo(() => {
const value = method() const value = method()
return (value?.form ?? []).flatMap((field) => (field.type === "string" ? [field] : [])) return value?.type === "oauth" ? (value.prompts ?? []) : []
}) })
const matches = (field: StringForm, value: Record<string, string>) => { const matches = (prompt: NonNullable<ReturnType<typeof prompts>[number]>, value: Record<string, string>) => {
return (field.when ?? []).every((condition) => { if (!prompt.when) return true
const actual = value[condition.key] const actual = value[prompt.when.key]
if (actual === undefined) return false if (actual === undefined) return false
return condition.op === "eq" ? actual === condition.value : actual !== condition.value return prompt.when.op === "eq" ? actual === prompt.when.value : actual !== prompt.when.value
})
} }
const current = createMemo(() => { const current = createMemo(() => {
const all = fields() const all = prompts()
const index = all.findIndex((field, index) => index >= formStore.index && matches(field, formStore.value)) const index = all.findIndex((prompt, index) => index >= formStore.index && matches(prompt, formStore.value))
if (index === -1) return if (index === -1) return
return { return {
index, index,
field: all[index], prompt: all[index],
} }
}) })
const valid = createMemo(() => { const valid = createMemo(() => {
const item = current() const item = current()
if (!item || item.field.options) return false if (!item || item.prompt.type !== "text") return false
if (!item.field.required) return true const value = formStore.value[item.prompt.key] ?? ""
return (formStore.value[item.field.key] ?? "").trim().length > 0 return value.trim().length > 0
}) })
async function next(index: number, value: Record<string, string>) { async function next(index: number, value: Record<string, string>) {
if (store.methodIndex === undefined) return if (store.methodIndex === undefined) return
const next = fields().findIndex((field, i) => i > index && matches(field, value)) const next = prompts().findIndex((prompt, i) => i > index && matches(prompt, value))
if (next !== -1) { if (next !== -1) {
setFormStore("index", next) setFormStore("index", next)
return return
@@ -620,60 +609,60 @@ function ProviderConnection(props: {
async function handleSubmit(e: SubmitEvent) { async function handleSubmit(e: SubmitEvent) {
e.preventDefault() e.preventDefault()
const item = current() const item = current()
if (!item || item.field.options) return if (!item || item.prompt.type !== "text") return
if (!valid()) return if (!valid()) return
await next(item.index, formStore.value) await next(item.index, formStore.value)
} }
const item = () => current() const item = () => current()
const text = createMemo(() => { const text = createMemo(() => {
const field = item()?.field const prompt = item()?.prompt
if (!field || field.options) return if (!prompt || prompt.type !== "text") return
return field return prompt
}) })
const select = createMemo(() => { const select = createMemo(() => {
const field = item()?.field const prompt = item()?.prompt
if (!field?.options) return if (!prompt || prompt.type !== "select") return
return field return prompt
}) })
return ( return (
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4"> <form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
<Switch> <Switch>
<Match when={item()?.field.options === undefined}> <Match when={item()?.prompt.type === "text"}>
<TextField <TextField
type="text" type="text"
label={text()?.title ?? ""} label={text()?.message ?? ""}
placeholder={text()?.placeholder} placeholder={text()?.placeholder}
value={text() ? (formStore.value[text()!.key] ?? "") : ""} value={text() ? (formStore.value[text()!.key] ?? "") : ""}
onChange={(value) => { onChange={(value) => {
const field = text() const prompt = text()
if (!field) return if (!prompt) return
setFormStore("value", field.key, value) setFormStore("value", prompt.key, value)
}} }}
/> />
<Button class="w-auto" type="submit" size="large" variant="primary" disabled={!valid()}> <Button class="w-auto" type="submit" size="large" variant="primary" disabled={!valid()}>
{language.t("common.continue")} {language.t("common.continue")}
</Button> </Button>
</Match> </Match>
<Match when={item()?.field.options !== undefined}> <Match when={item()?.prompt.type === "select"}>
<div class="w-full flex flex-col gap-1.5"> <div class="w-full flex flex-col gap-1.5">
<div class="text-14-regular text-text-base">{select()?.title}</div> <div class="text-14-regular text-text-base">{select()?.message}</div>
<div> <div>
<List <List
class="px-3" class="px-3"
items={select()?.options ?? []} items={select()?.options ?? []}
key={(x) => x.value} key={(x) => x.value}
current={select()?.options?.find((x) => x.value === formStore.value[select()!.key])} current={select()?.options.find((x) => x.value === formStore.value[select()!.key])}
onSelect={(value) => { onSelect={(value) => {
if (!value) return if (!value) return
const field = select() const prompt = select()
if (!field) return if (!prompt) return
const nextValue = { const nextValue = {
...formStore.value, ...formStore.value,
[field.key]: value.value, [prompt.key]: value.value,
} }
setFormStore("value", field.key, value.value) setFormStore("value", prompt.key, value.value)
void next(item()!.index, nextValue) void next(item()!.index, nextValue)
}} }}
> >
@@ -683,7 +672,7 @@ function ProviderConnection(props: {
<div class="w-2.5 h-0.5 ml-0 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" /> <div class="w-2.5 h-0.5 ml-0 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
</div> </div>
<span>{option.label}</span> <span>{option.label}</span>
<span class="text-14-regular text-text-weak">{option.description}</span> <span class="text-14-regular text-text-weak">{option.hint}</span>
</div> </div>
)} )}
</List> </List>
@@ -831,7 +820,6 @@ function ProviderConnection(props: {
integrationID: props.provider, integrationID: props.provider,
location: location(), location: location(),
key: apiKey, key: apiKey,
...(store.formAnswer ? { answer: store.formAnswer } : {}),
}) })
await complete() await complete()
} }
@@ -1155,8 +1143,8 @@ function ProviderConnection(props: {
</div> </div>
</div> </div>
</Match> </Match>
<Match when={store.state === "form"}> <Match when={store.state === "prompt"}>
<AuthFormView /> <AuthPromptsView />
</Match> </Match>
<Match when={store.state === "error"}> <Match when={store.state === "error"}>
<div class="text-14-regular text-text-base"> <div class="text-14-regular text-text-base">
@@ -123,7 +123,9 @@ export const SettingsProvidersV2: Component<{
const credentials = integration.data?.connections.filter((item) => item.type === "credential") ?? [] const credentials = integration.data?.connections.filter((item) => item.type === "credential") ?? []
if (credentials.length === 0) throw new Error(`No removable credentials found for ${name}`) if (credentials.length === 0) throw new Error(`No removable credentials found for ${name}`)
await Promise.all( await Promise.all(
credentials.map((credential) => serverSdk().api.credential.remove({ credentialID: credential.id, location })), credentials.map((credential) =>
serverSdk().api.credential.remove({ credentialID: credential.id, location }),
),
) )
showToast({ showToast({
variant: "success", variant: "success",
@@ -5,16 +5,7 @@ import { Switch } from "@opencode-ai/ui/switch"
import { Tabs } from "@opencode-ai/ui/tabs" import { Tabs } from "@opencode-ai/ui/tabs"
import { showToast } from "@/utils/toast" import { showToast } from "@/utils/toast"
import { useNavigate } from "@solidjs/router" import { useNavigate } from "@solidjs/router"
import { import { type Accessor, createEffect, createMemo, createResource, For, type JSXElement, onCleanup, Show } from "solid-js"
type Accessor,
createEffect,
createMemo,
createResource,
For,
type JSXElement,
onCleanup,
Show,
} from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row" import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
@@ -300,10 +291,7 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
const lspCount = createMemo(() => lspItems().length) const lspCount = createMemo(() => lspItems().length)
const [pluginList] = createResource( const [pluginList] = createResource(
() => (props.shown() ? sdk().directory : undefined), () => (props.shown() ? sdk().directory : undefined),
(directory) => (directory) => sdk().api.plugin.list({ location: { directory } }).then((result) => result.data),
sdk()
.api.plugin.list({ location: { directory } })
.then((result) => result.data),
) )
const plugins = createMemo(() => (pluginList.latest ?? []).map((item) => item.id)) const plugins = createMemo(() => (pluginList.latest ?? []).map((item) => item.id))
const pluginCount = createMemo(() => plugins().length) const pluginCount = createMemo(() => plugins().length)
+4 -1
View File
@@ -190,7 +190,10 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
.find((item) => ServerConnection.key(item) === (route.server ?? server.key)) .find((item) => ServerConnection.key(item) === (route.server ?? server.key))
return conn ? { route, sdk: global.ensureServerCtx(conn).sdk } : undefined return conn ? { route, sdk: global.ensureServerCtx(conn).sdk } : undefined
}, },
({ route, sdk }) => sdk.api.session.get({ sessionID: route.sessionId }).catch(() => {}), ({ route, sdk }) =>
sdk.api.session
.get({ sessionID: route.sessionId })
.catch(() => {}),
) )
const matchRoute = (route: LayoutRoute) => { const matchRoute = (route: LayoutRoute) => {
+3 -1
View File
@@ -124,7 +124,9 @@ export const createDirSyncContext = (
const [store, setStore] = current() const [store, setStore] = current()
setStore("limit", (value) => value + count) setStore("limit", (value) => value + count)
const response = await serverSDK.api.session.list({ directory, limit: store.limit, order: "desc" }) const response = await serverSDK.api.session.list({ directory, limit: store.limit, order: "desc" })
const sessions = response.data.sort((a, b) => cmp(a.id, b.id)).slice(0, store.limit) const sessions = response.data
.sort((a, b) => cmp(a.id, b.id))
.slice(0, store.limit)
sessions.forEach(serverSync.session.remember) sessions.forEach(serverSync.session.remember)
setStore("session", reconcile(sessions, { key: "id" })) setStore("session", reconcile(sessions, { key: "id" }))
}, },
@@ -1,4 +1,9 @@
import type { Config, Path, Project, ProviderAuthResponse } from "@/types" import type {
Config,
Path,
Project,
ProviderAuthResponse,
} from "@/types"
import type { import type {
AgentListInput, AgentListInput,
AgentListOutput, AgentListOutput,
@@ -26,7 +31,12 @@ import { batch } from "solid-js"
import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store" import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
import type { State, VcsCache } from "./types" import type { State, VcsCache } from "./types"
import type { ServerSession } from "../server-session" import type { ServerSession } from "../server-session"
import { cmp, normalizeAgentList, normalizeProjectInfo, normalizeProviderList } from "./utils" import {
cmp,
normalizeAgentList,
normalizeProjectInfo,
normalizeProviderList,
} from "./utils"
import { formatServerError } from "@/utils/server-errors" import { formatServerError } from "@/utils/server-errors"
import { QueryClient, queryOptions } from "@tanstack/solid-query" import { QueryClient, queryOptions } from "@tanstack/solid-query"
import { loadMcpQuery, loadMcpResourcesQuery } from "../server-sync" import { loadMcpQuery, loadMcpResourcesQuery } from "../server-sync"
@@ -138,7 +148,10 @@ export async function bootstrapGlobal(input: {
}) { }) {
const slow = [ const slow = [
() => input.queryClient.fetchQuery(loadGlobalConfigQuery(input.scope)), () => input.queryClient.fetchQuery(loadGlobalConfigQuery(input.scope)),
() => input.queryClient.fetchQuery(loadProvidersQuery(input.scope, null, input.serverAPI)), () =>
input.queryClient.fetchQuery(
loadProvidersQuery(input.scope, null, input.serverAPI),
),
() => input.queryClient.fetchQuery(loadPathQuery(input.scope, null, input.serverAPI.location)), () => input.queryClient.fetchQuery(loadPathQuery(input.scope, null, input.serverAPI.location)),
() => () =>
input.queryClient input.queryClient
@@ -198,7 +211,11 @@ function warmSessions(input: {
).then(() => undefined) ).then(() => undefined)
} }
export const loadProvidersQuery = (scope: ServerScope, directory: string | null, sdk: CatalogApi) => export const loadProvidersQuery = (
scope: ServerScope,
directory: string | null,
sdk: CatalogApi,
) =>
queryOptions({ queryOptions({
queryKey: [scope, directory, "providers"], queryKey: [scope, directory, "providers"],
queryFn: () => queryFn: () =>
@@ -225,16 +242,28 @@ type ReferenceListApi = {
readonly list: (input?: ReferenceListInput) => Promise<ReferenceListOutput> readonly list: (input?: ReferenceListInput) => Promise<ReferenceListOutput>
} }
export const loadAgentsQuery = (scope: ServerScope, directory: string, sdk: AgentListApi) => export const loadAgentsQuery = (
scope: ServerScope,
directory: string,
sdk: AgentListApi,
) =>
queryOptions({ queryOptions({
queryKey: [scope, directory, "agents"], queryKey: [scope, directory, "agents"],
queryFn: () => retry(() => sdk.list({ location: { directory } }).then((result) => normalizeAgentList(result.data))), queryFn: () =>
retry(() => sdk.list({ location: { directory } }).then((result) => normalizeAgentList(result.data))),
}) })
export const loadCommands = (directory: string, api: CommandListApi): Promise<CommandInfo[]> => export const loadCommands = (
directory: string,
api: CommandListApi,
): Promise<CommandInfo[]> =>
retry(() => api.list({ location: { directory } }).then((result) => result.data)) retry(() => api.list({ location: { directory } }).then((result) => result.data))
export const loadPathQuery = (scope: ServerScope, directory: string | null, api: LocationApi) => export const loadPathQuery = (
scope: ServerScope,
directory: string | null,
api: LocationApi,
) =>
queryOptions<Path>({ queryOptions<Path>({
queryKey: [scope, directory, "path"], queryKey: [scope, directory, "path"],
queryFn: () => queryFn: () =>
@@ -247,10 +276,15 @@ export const loadPathQuery = (scope: ServerScope, directory: string | null, api:
})), })),
}) })
export const loadReferencesQuery = (scope: ServerScope, directory: string, api: ReferenceListApi) => export const loadReferencesQuery = (
scope: ServerScope,
directory: string,
api: ReferenceListApi,
) =>
queryOptions<ReferenceInfo[]>({ queryOptions<ReferenceInfo[]>({
queryKey: [scope, directory, "references"] as const, queryKey: [scope, directory, "references"] as const,
queryFn: () => retry(() => api.list({ location: { directory } }).then((result) => result.data)).catch(() => []), queryFn: () =>
retry(() => api.list({ location: { directory } }).then((result) => result.data)).catch(() => []),
placeholderData: [], placeholderData: [],
}) })
@@ -319,8 +353,13 @@ export async function bootstrapDirectory(input: {
})), })),
input.mcp && input.mcp &&
(() => (() =>
loadCommands(input.directory, input.api.command).then((commands) => input.setStore("command", commands))), loadCommands(input.directory, input.api.command).then((commands) =>
() => input.queryClient.fetchQuery(loadReferencesQuery(input.scope, input.directory, input.api.reference)), input.setStore("command", commands),
)),
() =>
input.queryClient.fetchQuery(
loadReferencesQuery(input.scope, input.directory, input.api.reference),
),
() => () =>
retry(() => retry(() =>
input.api.permission.request input.api.permission.request
@@ -390,11 +429,20 @@ export async function bootstrapDirectory(input: {
}), }),
), ),
() => Promise.resolve(input.loadSessions(input.directory)), () => Promise.resolve(input.loadSessions(input.directory)),
input.mcp && (() => input.queryClient.fetchQuery(loadMcpQuery(input.scope, input.directory, input.api.mcp))),
input.mcp && input.mcp &&
(() => input.queryClient.fetchQuery(loadMcpResourcesQuery(input.scope, input.directory, input.api.mcp))), (() =>
input.queryClient.fetchQuery(
loadMcpQuery(input.scope, input.directory, input.api.mcp),
)),
input.mcp &&
(() =>
input.queryClient.fetchQuery(
loadMcpResourcesQuery(input.scope, input.directory, input.api.mcp),
)),
() => () =>
input.queryClient.fetchQuery(loadProvidersQuery(input.scope, input.directory, input.api)).catch((err) => { input.queryClient
.fetchQuery(loadProvidersQuery(input.scope, input.directory, input.api))
.catch((err) => {
const project = getFilename(input.directory) const project = getFilename(input.directory)
showToast({ showToast({
variant: "error", variant: "error",
@@ -1,13 +1,12 @@
import { Binary } from "@opencode-ai/core/util/binary" import { Binary } from "@opencode-ai/core/util/binary"
import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store" import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
import type { Message, Part, Project, Todo } from "@/types"
import type { import type {
FileDiffInfo, Message,
PermissionRequest, Part,
QuestionRequest, Project,
SessionInfo, Todo,
SessionStatus, } from "@/types"
} from "@opencode-ai/client/promise" import type { FileDiffInfo, PermissionRequest, QuestionRequest, SessionInfo, SessionStatus } from "@opencode-ai/client/promise"
import type { State, VcsCache } from "./types" import type { State, VcsCache } from "./types"
import { trimSessions } from "./session-trim" import { trimSessions } from "./session-trim"
import { dropSessionCaches } from "./session-cache" import { dropSessionCaches } from "./session-cache"
+10 -1
View File
@@ -1,4 +1,13 @@
import type { Agent, Config, LspStatus, Message, Part, Path, Todo, VcsInfo } from "@/types" import type {
Agent,
Config,
LspStatus,
Message,
Part,
Path,
Todo,
VcsInfo,
} from "@/types"
import type { import type {
FileDiffInfo, FileDiffInfo,
PermissionRequest, PermissionRequest,
+19 -18
View File
@@ -67,8 +67,7 @@ function currentMessages(data: MessageResponse["data"]): SessionMessageInfo[] {
}, },
] ]
} }
return [ return [{
{
id: item.info.id, id: item.info.id,
type: "assistant", type: "assistant",
agent: item.info.agent, agent: item.info.agent,
@@ -85,8 +84,7 @@ function currentMessages(data: MessageResponse["data"]): SessionMessageInfo[] {
] ]
if (part.type !== "tool") return [] if (part.type !== "tool") return []
const state: SessionMessageAssistantTool["state"] = (() => { const state: SessionMessageAssistantTool["state"] = (() => {
if (part.state.status === "pending") if (part.state.status === "pending") return { status: "streaming" as const, input: JSON.stringify(part.state.input) }
return { status: "streaming" as const, input: JSON.stringify(part.state.input) }
if (part.state.status === "running") if (part.state.status === "running")
return { return {
status: "running" as const, status: "running" as const,
@@ -125,16 +123,8 @@ function currentMessages(data: MessageResponse["data"]): SessionMessageInfo[] {
time: item.info.time, time: item.info.time,
cost: item.info.cost, cost: item.info.cost,
tokens: item.info.tokens, tokens: item.info.tokens,
finish: item.info.finish as finish: item.info.finish as "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" | undefined,
| "stop" }]
| "length"
| "tool-calls"
| "content-filter"
| "error"
| "unknown"
| undefined,
},
]
}) })
} }
@@ -442,7 +432,9 @@ describe("server session", () => {
const user = userMessage("message-1") const user = userMessage("message-1")
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
const client = rootMessageClient( const client = rootMessageClient(
[response(assistants.map((info) => ({ info, parts: [] })))], [
response(assistants.map((info) => ({ info, parts: [] }))),
],
[singleResponse(user)], [singleResponse(user)],
) )
const store = createServerSession(client) const store = createServerSession(client)
@@ -503,7 +495,9 @@ describe("server session", () => {
const part = textPart(user.id) const part = textPart(user.id)
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
const client = rootMessageClient( const client = rootMessageClient(
[response(assistants.map((info) => ({ info, parts: [] })))], [
response(assistants.map((info) => ({ info, parts: [] }))),
],
[singleResponse(user)], [singleResponse(user)],
) )
const store = createServerSession(client) const store = createServerSession(client)
@@ -522,7 +516,10 @@ describe("server session", () => {
const user = userMessage("message-1") const user = userMessage("message-1")
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
const client = rootMessageClient( const client = rootMessageClient(
[response([{ info: unrelated, parts: [] }]), response(assistants.map((info) => ({ info, parts: [] })))], [
response([{ info: unrelated, parts: [] }]),
response(assistants.map((info) => ({ info, parts: [] }))),
],
[singleResponse(user)], [singleResponse(user)],
) )
const store = createServerSession(client) const store = createServerSession(client)
@@ -615,7 +612,11 @@ describe("server session", () => {
const live = { ...user, agent: "stale" } const live = { ...user, agent: "stale" }
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
const client = rootMessageClient( const client = rootMessageClient(
[response(assistants.map((info) => ({ info, parts: [] })))], [
response(
assistants.map((info) => ({ info, parts: [] })),
),
],
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
) )
const store = createServerSession(client, { retry: retryImmediately }) const store = createServerSession(client, { retry: retryImmediately })
+12 -6
View File
@@ -1,7 +1,11 @@
import { Binary } from "@opencode-ai/core/util/binary" import { Binary } from "@opencode-ai/core/util/binary"
import { retry } from "@opencode-ai/core/util/retry" import { retry } from "@opencode-ai/core/util/retry"
import type { OpenCodeEvent, SessionApi, SessionInfo, SessionMessageInfo } from "@opencode-ai/client/promise" import type { OpenCodeEvent, SessionApi, SessionInfo, SessionMessageInfo } from "@opencode-ai/client/promise"
import type { Message, Part, Todo } from "@/types" import type {
Message,
Part,
Todo,
} from "@/types"
import type { FileDiffInfo, PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/client/promise" import type { FileDiffInfo, PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/client/promise"
import { batch } from "solid-js" import { batch } from "solid-js"
import { createStore, produce, reconcile } from "solid-js/store" import { createStore, produce, reconcile } from "solid-js/store"
@@ -34,16 +38,14 @@ function projectMessageSource(message: Message): SessionMessageInfo[] {
{ id: message.id, type: "user", text: "", time: message.time }, { id: message.id, type: "user", text: "", time: message.time },
] ]
} }
return [ return [{
{
id: message.id, id: message.id,
type: "assistant", type: "assistant",
agent: message.agent ?? message.mode, agent: message.agent ?? message.mode,
model: { id: message.modelID, providerID: message.providerID, variant: message.variant }, model: { id: message.modelID, providerID: message.providerID, variant: message.variant },
content: [], content: [],
time: message.time, time: message.time,
}, }]
]
} }
function needsOlderTurnRoot(source: readonly SessionMessageInfo[]) { function needsOlderTurnRoot(source: readonly SessionMessageInfo[]) {
@@ -241,7 +243,11 @@ export function createServerSession(
const indexProjectedMessage = (message: Message) => { const indexProjectedMessage = (message: Message) => {
const current = data.session_message[message.sessionID] ?? [] const current = data.session_message[message.sessionID] ?? []
if (current.some((item) => item.id === message.id)) return if (current.some((item) => item.id === message.id)) return
setData("session_message", message.sessionID, reconcile([...current, ...projectMessageSource(message)])) setData(
"session_message",
message.sessionID,
reconcile([...current, ...projectMessageSource(message)]),
)
} }
const remember = (session: SessionInfo) => { const remember = (session: SessionInfo) => {
+14 -4
View File
@@ -1,4 +1,9 @@
import type { Config, Path, Project, ProviderAuthResponse } from "@/types" import type {
Config,
Path,
Project,
ProviderAuthResponse,
} from "@/types"
import { showToast } from "@/utils/toast" import { showToast } from "@/utils/toast"
import { getFilename } from "@opencode-ai/core/util/path" import { getFilename } from "@opencode-ai/core/util/path"
import { type Accessor, batch, createMemo, getOwner, onCleanup, onMount, untrack } from "solid-js" import { type Accessor, batch, createMemo, getOwner, onCleanup, onMount, untrack } from "solid-js"
@@ -159,11 +164,15 @@ export function seedActiveSessionStatuses(
} }
} }
function makeQueryOptionsApi(scope: ServerScope, serverAPI: ServerApi) { function makeQueryOptionsApi(
scope: ServerScope,
serverAPI: ServerApi,
) {
return { return {
globalConfig: () => loadGlobalConfigQuery(scope), globalConfig: () => loadGlobalConfigQuery(scope),
projects: () => loadProjectsQuery(scope, serverAPI.project), projects: () => loadProjectsQuery(scope, serverAPI.project),
providers: (directory: PathKey | null) => loadProvidersQuery(scope, directory, serverAPI), providers: (directory: PathKey | null) =>
loadProvidersQuery(scope, directory, serverAPI),
path: (directory: PathKey | null) => loadPathQuery(scope, directory, serverAPI.location), path: (directory: PathKey | null) => loadPathQuery(scope, directory, serverAPI.location),
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, serverAPI.agent), agents: (directory: PathKey) => loadAgentsQuery(scope, directory, serverAPI.agent),
references: (directory: PathKey) => loadReferencesQuery(scope, directory, serverAPI.reference), references: (directory: PathKey) => loadReferencesQuery(scope, directory, serverAPI.reference),
@@ -662,12 +671,13 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
integrationID: server.integrationID, integrationID: server.integrationID,
location: { directory: key }, location: { directory: key },
}) })
const method = integration.data?.methods.find((item) => item.type === "oauth" && !item.form?.length) const method = integration.data?.methods.find((item) => item.type === "oauth" && !item.prompts?.length)
if (!method || method.type !== "oauth") if (!method || method.type !== "oauth")
throw new Error(`MCP server ${name} requires an interactive authentication form`) throw new Error(`MCP server ${name} requires an interactive authentication form`)
const attempt = await serverSDK.api.integration.oauth.connect({ const attempt = await serverSDK.api.integration.oauth.connect({
integrationID: server.integrationID, integrationID: server.integrationID,
methodID: method.id, methodID: method.id,
inputs: {},
location: { directory: key }, location: { directory: key },
}) })
platform.openLink(attempt.data.url) platform.openLink(attempt.data.url)
+1 -4
View File
@@ -268,10 +268,7 @@ function createWorkspaceTerminalSession(
const index = store.all.findIndex((x) => x.id === id) const index = store.all.findIndex((x) => x.id === id)
const pty = store.all[index] const pty = store.all[index]
if (!pty) return if (!pty) return
const data = await sdk.api.pty const data = await sdk.api.pty.create({ location, title: pty.title }).then((result) => result.data).catch((error: unknown) => {
.create({ location, title: pty.title })
.then((result) => result.data)
.catch((error: unknown) => {
console.error("Failed to clone terminal", error) console.error("Failed to clone terminal", error)
return undefined return undefined
}) })
@@ -45,7 +45,13 @@ import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
import { TextField } from "@opencode-ai/ui/text-field" import { TextField } from "@opencode-ai/ui/text-field"
import { TextReveal } from "@opencode-ai/ui/text-reveal" import { TextReveal } from "@opencode-ai/ui/text-reveal"
import { TextShimmer } from "@opencode-ai/ui/text-shimmer" import { TextShimmer } from "@opencode-ai/ui/text-shimmer"
import type { AssistantMessage, Message as MessageType, Part as PartType, ToolPart, UserMessage } from "@/types" import type {
AssistantMessage,
Message as MessageType,
Part as PartType,
ToolPart,
UserMessage,
} from "@/types"
import { showToast } from "@/utils/toast" import { showToast } from "@/utils/toast"
import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
import { Popover as KobaltePopover } from "@kobalte/core/popover" import { Popover as KobaltePopover } from "@kobalte/core/popover"
@@ -54,9 +54,9 @@ describe("reviewDiffNeedsLoad", () => {
patch: "@@ -0,0 +1 @@\n+value", patch: "@@ -0,0 +1 @@\n+value",
}), }),
).toBe(false) ).toBe(false)
expect(reviewDiffNeedsLoad({ file: "empty.txt", patch: "", additions: 0, deletions: 0, status: "modified" })).toBe( expect(
false, reviewDiffNeedsLoad({ file: "empty.txt", patch: "", additions: 0, deletions: 0, status: "modified" }),
) ).toBe(false)
}) })
}) })
+5 -1
View File
@@ -1,4 +1,8 @@
import type { EventSubscribeOutput, FileDiffInfo, ProjectListOutput } from "@opencode-ai/client/promise" import type {
EventSubscribeOutput,
FileDiffInfo,
ProjectListOutput,
} from "@opencode-ai/client/promise"
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
export type Project = Omit<ProjectListOutput[number], "canonical"> & { worktree: string } export type Project = Omit<ProjectListOutput[number], "canonical"> & { worktree: string }
+2 -12
View File
@@ -119,15 +119,7 @@ function installPackage(name) {
try { try {
const result = childProcess.spawnSync( const result = childProcess.spawnSync(
"npm", "npm",
[ ["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${name}@${dependencies[name]}`],
"install",
"--ignore-scripts",
"--no-save",
"--loglevel=error",
"--prefix",
temp,
`${name}@${dependencies[name]}`,
],
{ stdio: "inherit", windowsHide: true }, { stdio: "inherit", windowsHide: true },
) )
if (result.status !== 0) return false if (result.status !== 0) return false
@@ -158,9 +150,7 @@ function main() {
} }
} }
throw new Error( throw new Error(`Failed to install OpenCode. Try manually installing ${names.map((name) => JSON.stringify(name)).join(" or ")}.`)
`Failed to install OpenCode. Try manually installing ${names.map((name) => JSON.stringify(name)).join(" or ")}.`,
)
} }
try { try {
+13 -11
View File
@@ -40,13 +40,15 @@ try {
const token = encodeURIComponent(credential) const token = encodeURIComponent(credential)
const health = await waitForReady(info.url, headers) const health = await waitForReady(info.url, headers)
if (health.pid !== info.pid) throw new Error("Health process does not match registration") if (health.pid !== info.pid) throw new Error("Health process does not match registration")
const tokenHealth = await fetch(new URL(`/api/health?auth_token=${token}`, info.url), { const tokenHealth = await fetch(
signal: AbortSignal.timeout(5_000), new URL(`/api/health?auth_token=${token}`, info.url),
}) { signal: AbortSignal.timeout(5_000) },
)
if (tokenHealth.status !== 200) throw new Error("Compiled service rejected query authentication") if (tokenHealth.status !== 200) throw new Error("Compiled service rejected query authentication")
const tokenOpenApi = await fetch(new URL(`/openapi.json?auth_token=${token}`, info.url), { const tokenOpenApi = await fetch(
signal: AbortSignal.timeout(5_000), new URL(`/openapi.json?auth_token=${token}`, info.url),
}) { signal: AbortSignal.timeout(5_000) },
)
if (tokenOpenApi.status !== 200) throw new Error("Compiled application rejected query authentication") if (tokenOpenApi.status !== 200) throw new Error("Compiled application rejected query authentication")
if ((await pluginIDs(info.url, headers)).includes("smoke")) throw new Error("Smoke plugin existed before creation") if ((await pluginIDs(info.url, headers)).includes("smoke")) throw new Error("Smoke plugin existed before creation")
const plugin = path.join(root, ".opencode", "plugins", "smoke.ts") const plugin = path.join(root, ".opencode", "plugins", "smoke.ts")
@@ -61,8 +63,7 @@ try {
const unauthorizedOpenApi = await fetch(new URL("/openapi.json", info.url), { const unauthorizedOpenApi = await fetch(new URL("/openapi.json", info.url), {
signal: AbortSignal.timeout(5_000), signal: AbortSignal.timeout(5_000),
}) })
if (unauthorizedOpenApi.status !== 401) if (unauthorizedOpenApi.status !== 401) throw new Error("Compiled service exposed application routes without authentication")
throw new Error("Compiled service exposed application routes without authentication")
const unauthorizedStop = await fetch(new URL("/api/service/stop", info.url), { const unauthorizedStop = await fetch(new URL("/api/service/stop", info.url), {
method: "POST", method: "POST",
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
@@ -93,8 +94,7 @@ try {
} finally { } finally {
processes.forEach((process) => process.kill()) processes.forEach((process) => process.kill())
await Promise.all(processes.map((process) => process.exited)) await Promise.all(processes.map((process) => process.exited))
if (failure) if (failure) errors.push(fs.readFile(path.join(root, "data", "opencode", "log", "opencode.log"), "utf8").catch(() => ""))
errors.push(fs.readFile(path.join(root, "data", "opencode", "log", "opencode.log"), "utf8").catch(() => ""))
} }
const output = await Promise.all(errors) const output = await Promise.all(errors)
@@ -154,7 +154,9 @@ async function pluginIDs(url: string, headers: HeadersInit) {
throw new Error("Compiled service returned an invalid plugin list") throw new Error("Compiled service returned an invalid plugin list")
} }
return body.data.flatMap((plugin) => return body.data.flatMap((plugin) =>
typeof plugin === "object" && plugin !== null && "id" in plugin && typeof plugin.id === "string" ? [plugin.id] : [], typeof plugin === "object" && plugin !== null && "id" in plugin && typeof plugin.id === "string"
? [plugin.id]
: [],
) )
} }
@@ -50,7 +50,7 @@ const login = Effect.fn("cli.console.login.run")(function* (timeline: TimelineHo
{ {
integrationID, integrationID,
methodID: method.id, methodID: method.id,
...(server ? { answer: { server } } : {}), inputs: server ? { server } : {},
location, location,
}, },
{ signal }, { signal },
+4 -5
View File
@@ -20,8 +20,7 @@ export default Runtime.handler(
return response.text() return response.text()
}) })
: Bun.file(input.file).text(), : Bun.file(input.file).text(),
catch: (cause) => catch: (cause) => new Error(`Failed to read session data: ${cause instanceof Error ? cause.message : String(cause)}`),
new Error(`Failed to read session data: ${cause instanceof Error ? cause.message : String(cause)}`),
}) })
const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(SessionTransfer.Data))(text) const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(SessionTransfer.Data))(text)
const encoded = Schema.encodeSync(SessionTransfer.Data)(data) const encoded = Schema.encodeSync(SessionTransfer.Data)(data)
@@ -53,9 +52,9 @@ export default Runtime.handler(
return return
} }
if (!response.ok) yield* Effect.fail(new Error(`Failed to import session: ${response.statusText}`)) if (!response.ok) yield* Effect.fail(new Error(`Failed to import session: ${response.statusText}`))
const imported = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Struct({ data: Session.Info })))( const imported = yield* Schema.decodeUnknownEffect(
yield* Effect.promise(() => response.text()), Schema.fromJsonString(Schema.Struct({ data: Session.Info })),
) )(yield* Effect.promise(() => response.text()))
process.stdout.write(`Imported session: ${imported.data.id}${EOL}`) process.stdout.write(`Imported session: ${imported.data.id}${EOL}`)
}), }),
) )
@@ -32,7 +32,7 @@ export default Runtime.handler(
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`)) return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
const started = yield* Effect.promise(() => const started = yield* Effect.promise(() =>
client.integration.oauth.connect({ integrationID: integration.id, methodID: method.id, location }), client.integration.oauth.connect({ integrationID: integration.id, methodID: method.id, inputs: {}, location }),
) )
const attempt = started.data const attempt = started.data
if (attempt.mode === "code") if (attempt.mode === "code")
+3 -1
View File
@@ -36,6 +36,8 @@ export default Runtime.handler(
const hostname = new URL(endpoint.url).hostname const hostname = new URL(endpoint.url).hostname
if (!["localhost", "127.0.0.1", "[::1]"].includes(hostname)) return if (!["localhost", "127.0.0.1", "[::1]"].includes(hostname)) return
process.stderr.write(` Run \`opencode service set hostname 0.0.0.0\` to access the service remotely.${EOL}${EOL}`) process.stderr.write(
` Run \`opencode service set hostname 0.0.0.0\` to access the service remotely.${EOL}${EOL}`,
)
}), }),
) )
+3 -7
View File
@@ -48,16 +48,12 @@ export function migrateV1(legacy: TuiConfigV1.Info | undefined, kv: Record<strin
const attentionSoundPack = kv.attention_sound_pack const attentionSoundPack = kv.attention_sound_pack
const diffView = kv.diff_viewer_view ?? (legacy?.diff_style === "stacked" ? "unified" : undefined) const diffView = kv.diff_viewer_view ?? (legacy?.diff_style === "stacked" ? "unified" : undefined)
const thinking = const thinking =
kv.thinking_mode ?? (kv.thinking_visibility === undefined ? undefined : kv.thinking_visibility ? "show" : "hide") kv.thinking_mode ??
(kv.thinking_visibility === undefined ? undefined : kv.thinking_visibility ? "show" : "hide")
return { return {
...(themeName !== undefined || themeMode !== undefined ...(themeName !== undefined || themeMode !== undefined
? { ? { theme: { ...(themeName === undefined ? {} : { name: themeName }), ...(themeMode === undefined ? {} : { mode: themeMode }) } }
theme: {
...(themeName === undefined ? {} : { name: themeName }),
...(themeMode === undefined ? {} : { mode: themeMode }),
},
}
: {}), : {}),
...(legacy?.keybinds === undefined ? {} : { keybinds: legacy.keybinds }), ...(legacy?.keybinds === undefined ? {} : { keybinds: legacy.keybinds }),
...(plugins.length ? { plugins } : {}), ...(plugins.length ? { plugins } : {}),
+4 -1
View File
@@ -468,7 +468,10 @@ export async function runNonInteractivePrompt(input: Input) {
continue continue
} }
if (event.type === "session.step.failed") { if (event.type === "session.step.failed") {
if (input.compatibility === "v1" && event.data.error.message === "The provider response ended unexpectedly.") { if (
input.compatibility === "v1" &&
event.data.error.message === "The provider response ended unexpectedly."
) {
pendingStep = undefined pendingStep = undefined
v1InvalidOutput = true v1InvalidOutput = true
continue continue
@@ -62,7 +62,10 @@ function managedService(options: EnsureOptions) {
} }
} }
const resolveManaged = Effect.fnUntraced(function* (options: EnsureOptions, mismatch: NonNullable<Args["mismatch"]>) { const resolveManaged = Effect.fnUntraced(function* (
options: EnsureOptions,
mismatch: NonNullable<Args["mismatch"]>,
) {
if (mismatch === "replace") return yield* Service.ensure(options) if (mismatch === "replace") return yield* Service.ensure(options)
if (mismatch === "ignore") return yield* Service.ensure({ ...options, version: undefined }) if (mismatch === "ignore") return yield* Service.ensure({ ...options, version: undefined })
@@ -413,7 +413,9 @@ function UpdateFooter(props: {
const completion = smoothstep(headerFade.progress()) const completion = smoothstep(headerFade.progress())
return Array.from({ length: width }, (_, index) => { return Array.from({ length: width }, (_, index) => {
const color = const color =
index >= filled ? colors.muted : shade(railRamp, Math.max(0, 1 - Math.abs(index - center) / glowRadius) ** 2) index >= filled
? colors.muted
: shade(railRamp, Math.max(0, 1 - Math.abs(index - center) / glowRadius) ** 2)
return { return {
char: success || index < filled ? "━" : "·", char: success || index < filled ? "━" : "·",
color: success ? blend(color, colors.accent, completion) : color, color: success ? blend(color, colors.accent, completion) : color,
+15 -16
View File
@@ -52,10 +52,9 @@ export const layer = Layer.effect(
const readPolicy = Effect.fnUntraced(function* () { const readPolicy = Effect.fnUntraced(function* () {
const values = yield* Effect.forEach(["config.json", "opencode.json", "opencode.jsonc"], (name) => const values = yield* Effect.forEach(["config.json", "opencode.json", "opencode.jsonc"], (name) =>
fs.readFileString(path.join(global.config, name)).pipe( fs
Effect.map(decodePolicy), .readFileString(path.join(global.config, name))
Effect.catch(() => Effect.succeed(undefined)), .pipe(Effect.map(decodePolicy), Effect.catch(() => Effect.succeed(undefined))),
),
) )
return values.findLast((value) => value !== undefined) ?? true return values.findLast((value) => value !== undefined) ?? true
}) })
@@ -95,10 +94,10 @@ export const layer = Layer.effect(
const latest = Effect.fnUntraced(function* () { const latest = Effect.fnUntraced(function* () {
const response = yield* Effect.tryPromise({ const response = yield* Effect.tryPromise({
try: () => try: () =>
fetch(`https://update.opencode.ai/api/${encodeURIComponent(channel)}/cli/npm`, { fetch(
headers: { "User-Agent": `opencode/${OPENCODE_VERSION}` }, `https://update.opencode.ai/api/${encodeURIComponent(channel)}/cli/npm`,
signal: AbortSignal.timeout(10_000), { headers: { "User-Agent": `opencode/${OPENCODE_VERSION}` }, signal: AbortSignal.timeout(10_000) },
}), ),
catch: (cause) => new Error("Failed to check for updates", { cause }), catch: (cause) => new Error("Failed to check for updates", { cause }),
}) })
if (!response.ok) return yield* Effect.fail(new Error(`Update check failed with status ${response.status}`)) if (!response.ok) return yield* Effect.fail(new Error(`Update check failed with status ${response.status}`))
@@ -119,7 +118,7 @@ export const layer = Layer.effect(
pnpm: ["pnpm", "install", "--global", target], pnpm: ["pnpm", "install", "--global", target],
yarn: ["yarn", "global", "add", target], yarn: ["yarn", "global", "add", target],
} }
const result = yield* method === "bun" const result = yield* (method === "bun"
? Effect.scoped( ? Effect.scoped(
Effect.gen(function* () { Effect.gen(function* () {
// Bun does not prune old versions from its shared package cache. // Bun does not prune old versions from its shared package cache.
@@ -128,14 +127,16 @@ export const layer = Layer.effect(
return yield* run(["bun", "install", "--global", "--cache-dir", cache, target], "5 minutes") return yield* run(["bun", "install", "--global", "--cache-dir", cache, target], "5 minutes")
}), }),
) )
: run(commands[method], "5 minutes") : run(commands[method], "5 minutes"))
if (result.code === 0) return if (result.code === 0) return
return yield* Effect.fail(new Error(result.stderr.trim() || `Failed to update with ${method}`)) return yield* Effect.fail(new Error(result.stderr.trim() || `Failed to update with ${method}`))
}) })
const check = Effect.fn("cli.updater.check")( const check = Effect.fn("cli.updater.check")(function* () {
function* () { if (
if (OPENCODE_LOCAL || ["1", "true"].includes(process.env.OPENCODE_DISABLE_AUTOUPDATE?.toLowerCase() ?? "")) OPENCODE_LOCAL ||
["1", "true"].includes(process.env.OPENCODE_DISABLE_AUTOUPDATE?.toLowerCase() ?? "")
)
return yield* Effect.logInfo("update check skipped", { return yield* Effect.logInfo("update check skipped", {
reason: OPENCODE_LOCAL ? "local-install" : "disabled", reason: OPENCODE_LOCAL ? "local-install" : "disabled",
version: OPENCODE_VERSION, version: OPENCODE_VERSION,
@@ -157,9 +158,7 @@ export const layer = Layer.effect(
yield* upgrade(detected, version) yield* upgrade(detected, version)
yield* Effect.logInfo("updated OpenCode", { from: OPENCODE_VERSION, to: version, method: detected }) yield* Effect.logInfo("updated OpenCode", { from: OPENCODE_VERSION, to: version, method: detected })
}) })
}, }, Effect.catchCause((cause) => Effect.logWarning("automatic update failed", { cause })))
Effect.catchCause((cause) => Effect.logWarning("automatic update failed", { cause })),
)
return Service.of({ check }) return Service.of({ check })
}), }),
+4 -2
View File
@@ -102,7 +102,9 @@ test("migrates before the first update and does not remigrate afterward", async
draft.animations = false draft.animations = false
draft.mouse = false draft.mouse = false
}) })
yield* Effect.promise(() => Bun.write(path.join(directory, "tui.json"), JSON.stringify({ theme: "changed" }))) yield* Effect.promise(() =>
Bun.write(path.join(directory, "tui.json"), JSON.stringify({ theme: "changed" })),
)
return yield* service.get() return yield* service.get()
}), }),
) )
@@ -120,7 +122,7 @@ test("migrates before the first update and does not remigrate afterward", async
test("updates a config draft while preserving JSONC comments", async () => { test("updates a config draft while preserving JSONC comments", async () => {
const directory = await Bun.$`mktemp -d`.text().then((value) => value.trim()) const directory = await Bun.$`mktemp -d`.text().then((value) => value.trim())
await Bun.write(path.join(directory, "cli.json"), '{\n // Keep this comment\n "animations": true\n}\n') await Bun.write(path.join(directory, "cli.json"), "{\n // Keep this comment\n \"animations\": true\n}\n")
try { try {
const config = await run( const config = await run(
@@ -17,9 +17,7 @@ export default defineScript({
const session = `mini-stage2-${process.pid}` const session = `mini-stage2-${process.pid}`
const snapshots = path.join(artifacts, "mini-stage2") const snapshots = path.join(artifacts, "mini-stage2")
const explicitDirectory = path.join(artifacts, "explicit-model") const explicitDirectory = path.join(artifacts, "explicit-model")
yield* Effect.promise(() => yield* Effect.promise(() => Promise.all([snapshots, explicitDirectory].map((dir) => mkdir(dir, { recursive: true }))))
Promise.all([snapshots, explicitDirectory].map((dir) => mkdir(dir, { recursive: true }))),
)
/** @param {string} directory @param {string | undefined} model */ /** @param {string} directory @param {string | undefined} model */
const mini = (directory, model) => [ const mini = (directory, model) => [
"env", "env",
@@ -171,7 +169,14 @@ export default defineScript({
yield* Effect.promise(() => tmux(["clear-history", "-t", session])) yield* Effect.promise(() => tmux(["clear-history", "-t", session]))
yield* Effect.promise(() => yield* Effect.promise(() =>
tmux(["respawn-pane", "-k", "-t", session, "--", ...mini(explicitDirectory, "simulation/gpt-sim-model")]), tmux([
"respawn-pane",
"-k",
"-t",
session,
"--",
...mini(explicitDirectory, "simulation/gpt-sim-model"),
]),
) )
const explicitModel = yield* Effect.promise(() => waitForPane(session, "Simulated Model", 15_000)) const explicitModel = yield* Effect.promise(() => waitForPane(session, "Simulated Model", 15_000))
yield* Effect.promise(() => Bun.write(path.join(snapshots, "07-explicit-model.txt"), explicitModel)) yield* Effect.promise(() => Bun.write(path.join(snapshots, "07-explicit-model.txt"), explicitModel))
+8 -1
View File
@@ -159,7 +159,14 @@ test("import validates a file and sends it to the resolved location", async () =
}) })
try { try {
const [stdout, , exitCode] = await run(["import", file, "--directory", root, "--server", server.url.toString()]) const [stdout, , exitCode] = await run([
"import",
file,
"--directory",
root,
"--server",
server.url.toString(),
])
expect(exitCode).toBe(0) expect(exitCode).toBe(0)
expect(stdout).toBe(`Imported session: ${info.id}${os.EOL}`) expect(stdout).toBe(`Imported session: ${info.id}${os.EOL}`)
+7 -1
View File
@@ -3,7 +3,13 @@ import { readFile } from "node:fs/promises"
import { createRequire } from "node:module" import { createRequire } from "node:module"
import { defineConfig, type Plugin, type UserConfig } from "vite" import { defineConfig, type Plugin, type UserConfig } from "vite"
import solid from "vite-plugin-solid" import solid from "vite-plugin-solid"
import { nodeExecArgv, nodeTarget, type NodeTarget, photonWasmAsset, shellParserWasmAssets } from "./src/node/target" import {
nodeExecArgv,
nodeTarget,
type NodeTarget,
photonWasmAsset,
shellParserWasmAssets,
} from "./src/node/target"
const dir = import.meta.dirname const dir = import.meta.dirname
+6 -1
View File
@@ -1,6 +1,11 @@
import { NodeFileSystem } from "@effect/platform-node" import { NodeFileSystem } from "@effect/platform-node"
import { compile, emitEffectImported, emitEffectShape, emitPromise, write } from "@opencode-ai/httpapi-codegen" import { compile, emitEffectImported, emitEffectShape, emitPromise, write } from "@opencode-ai/httpapi-codegen"
import { ClientApi, effectOmitEndpoints, groupNames, promiseOmitEndpoints } from "@opencode-ai/protocol/client" import {
ClientApi,
effectOmitEndpoints,
groupNames,
promiseOmitEndpoints,
} from "@opencode-ai/protocol/client"
import { Agent } from "@opencode-ai/schema/agent" import { Agent } from "@opencode-ai/schema/agent"
import { Command } from "@opencode-ai/schema/command" import { Command } from "@opencode-ai/schema/command"
import { Config } from "@opencode-ai/schema/config" import { Config } from "@opencode-ai/schema/config"
+6 -1
View File
@@ -1 +1,6 @@
export { ClientApi, effectOmitEndpoints, groupNames, promiseOmitEndpoints } from "@opencode-ai/protocol/client" export {
ClientApi,
effectOmitEndpoints,
groupNames,
promiseOmitEndpoints,
} from "@opencode-ai/protocol/client"
@@ -1 +1,3 @@
["api.ts"] [
"api.ts"
]
+2 -5
View File
@@ -23,9 +23,9 @@ import type { Shell } from "@opencode-ai/schema/shell"
import type { DateTime } from "effect" import type { DateTime } from "effect"
import type { Provider } from "@opencode-ai/schema/provider" import type { Provider } from "@opencode-ai/schema/provider"
import type { Integration } from "@opencode-ai/schema/integration" import type { Integration } from "@opencode-ai/schema/integration"
import type { Form } from "@opencode-ai/schema/form"
import type { Mcp } from "@opencode-ai/schema/mcp" import type { Mcp } from "@opencode-ai/schema/mcp"
import type { Credential } from "@opencode-ai/schema/credential" import type { Credential } from "@opencode-ai/schema/credential"
import type { Form } from "@opencode-ai/schema/form"
import type { Permission } from "@opencode-ai/schema/permission" import type { Permission } from "@opencode-ai/schema/permission"
import type { PermissionSaved } from "@opencode-ai/schema/permission-saved" import type { PermissionSaved } from "@opencode-ai/schema/permission-saved"
import type { FileSystem } from "@opencode-ai/schema/filesystem" import type { FileSystem } from "@opencode-ai/schema/filesystem"
@@ -180,7 +180,6 @@ export type Endpoint5_12Input = {
readonly text: string readonly text: string
readonly files?: ReadonlyArray<PromptInput.FileAttachment> | undefined readonly files?: ReadonlyArray<PromptInput.FileAttachment> | undefined
readonly agents?: ReadonlyArray<AgentAttachment> | undefined readonly agents?: ReadonlyArray<AgentAttachment> | undefined
readonly skills?: ReadonlyArray<PromptInput.SkillAttachment> | undefined
readonly metadata?: { readonly [x: string]: unknown } | undefined readonly metadata?: { readonly [x: string]: unknown } | undefined
readonly delivery?: "steer" | "queue" | undefined readonly delivery?: "steer" | "queue" | undefined
readonly resume?: boolean | undefined readonly resume?: boolean | undefined
@@ -197,7 +196,6 @@ export type Endpoint5_13Input = {
readonly model?: Model.Ref | undefined readonly model?: Model.Ref | undefined
readonly files?: ReadonlyArray<PromptInput.FileAttachment> | undefined readonly files?: ReadonlyArray<PromptInput.FileAttachment> | undefined
readonly agents?: ReadonlyArray<AgentAttachment> | undefined readonly agents?: ReadonlyArray<AgentAttachment> | undefined
readonly skills?: ReadonlyArray<PromptInput.SkillAttachment> | undefined
readonly delivery?: "steer" | "queue" | undefined readonly delivery?: "steer" | "queue" | undefined
readonly resume?: boolean | undefined readonly resume?: boolean | undefined
} }
@@ -1054,7 +1052,6 @@ export type Endpoint10_3Input = {
readonly integrationID: Integration.ID readonly integrationID: Integration.ID
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly key: string readonly key: string
readonly answer?: Form.Answer | undefined
readonly label?: string | undefined readonly label?: string | undefined
} }
export type Endpoint10_3Output = void export type Endpoint10_3Output = void
@@ -1066,7 +1063,7 @@ export type Endpoint10_4Input = {
readonly integrationID: Integration.ID readonly integrationID: Integration.ID
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly methodID: Integration.MethodID readonly methodID: Integration.MethodID
readonly answer?: Form.Answer | undefined readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined readonly label?: string | undefined
} }
export type Endpoint10_4Output = { readonly location: Location.Info; readonly data: Integration.Attempt } export type Endpoint10_4Output = { readonly location: Location.Info; readonly data: Integration.Attempt }
@@ -1 +1,5 @@
["client-error.ts", "client.ts", "index.ts"] [
"client-error.ts",
"client.ts",
"index.ts"
]
@@ -412,7 +412,6 @@ const Endpoint5_12 = (raw: RawClient["server.session"]) => (input: Endpoint5_12I
text: input["text"], text: input["text"],
files: input["files"], files: input["files"],
agents: input["agents"], agents: input["agents"],
skills: input["skills"],
metadata: input["metadata"], metadata: input["metadata"],
delivery: input["delivery"], delivery: input["delivery"],
resume: input["resume"], resume: input["resume"],
@@ -435,7 +434,6 @@ const Endpoint5_13 = (raw: RawClient["server.session"]) => (input: Endpoint5_13I
model: input["model"], model: input["model"],
files: input["files"], files: input["files"],
agents: input["agents"], agents: input["agents"],
skills: input["skills"],
delivery: input["delivery"], delivery: input["delivery"],
resume: input["resume"], resume: input["resume"],
}, },
@@ -717,7 +715,7 @@ const Endpoint10_3 = (raw: RawClient["server.integration"]) => (input: Endpoint1
raw["integration.connect.key"]({ raw["integration.connect.key"]({
params: { integrationID: input["integrationID"] }, params: { integrationID: input["integrationID"] },
query: { location: input["location"] }, query: { location: input["location"] },
payload: { key: input["key"], answer: input["answer"], label: input["label"] }, payload: { key: input["key"], label: input["label"] },
}).pipe(Effect.mapError(mapClientError)), }).pipe(Effect.mapError(mapClientError)),
) )
@@ -726,7 +724,7 @@ const Endpoint10_4 = (raw: RawClient["server.integration"]) => (input: Endpoint1
raw["integration.oauth.connect"]({ raw["integration.oauth.connect"]({
params: { integrationID: input["integrationID"] }, params: { integrationID: input["integrationID"] },
query: { location: input["location"] }, query: { location: input["location"] },
payload: { methodID: input["methodID"], answer: input["answer"], label: input["label"] }, payload: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
}).pipe(Effect.mapError(mapClientError)), }).pipe(Effect.mapError(mapClientError)),
) )
@@ -1 +1,6 @@
["client-error.ts", "client.ts", "index.ts", "types.ts"] [
"client-error.ts",
"client.ts",
"index.ts",
"types.ts"
]
@@ -615,7 +615,6 @@ export function make(options: ClientOptions) {
text: input["text"], text: input["text"],
files: input["files"], files: input["files"],
agents: input["agents"], agents: input["agents"],
skills: input["skills"],
metadata: input["metadata"], metadata: input["metadata"],
delivery: input["delivery"], delivery: input["delivery"],
resume: input["resume"], resume: input["resume"],
@@ -639,7 +638,6 @@ export function make(options: ClientOptions) {
model: input["model"], model: input["model"],
files: input["files"], files: input["files"],
agents: input["agents"], agents: input["agents"],
skills: input["skills"],
delivery: input["delivery"], delivery: input["delivery"],
resume: input["resume"], resume: input["resume"],
}, },
@@ -1032,7 +1030,7 @@ export function make(options: ClientOptions) {
method: "POST", method: "POST",
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`, path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`,
query: { location: input["location"] }, query: { location: input["location"] },
body: { key: input["key"], answer: input["answer"], label: input["label"] }, body: { key: input["key"], label: input["label"] },
successStatus: 204, successStatus: 204,
declaredStatuses: [400, 401], declaredStatuses: [400, 401],
empty: true, empty: true,
@@ -1047,7 +1045,7 @@ export function make(options: ClientOptions) {
method: "POST", method: "POST",
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`, path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`,
query: { location: input["location"] }, query: { location: input["location"] },
body: { methodID: input["methodID"], answer: input["answer"], label: input["label"] }, body: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
successStatus: 200, successStatus: 200,
declaredStatuses: [400, 401], declaredStatuses: [400, 401],
empty: false, empty: false,
+80 -201
View File
@@ -195,18 +195,12 @@ export type ProviderInfo = {
body?: { [x: string]: any } body?: { [x: string]: any }
} }
export type FormWhen = { export type IntegrationWhen = { key: string; op: "eq" | "neq"; value: string }
key: string
op: "eq" | "neq"
value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}
export type FormOption = { value: string; label: string; description?: string }
export type FormExternalField = { key: string; type: "external"; url: string; title?: string; description?: string }
export type IntegrationCommandMethod = { id: string; type: "command"; label: string; command: Array<string> } export type IntegrationCommandMethod = { id: string; type: "command"; label: string; command: Array<string> }
export type IntegrationKeyMethod = { type: "key"; label?: string }
export type IntegrationEnvMethod = { type: "env"; names: Array<string> } export type IntegrationEnvMethod = { type: "env"; names: Array<string> }
export type ConnectionCredentialInfo = { type: "credential"; id: string; label: string } export type ConnectionCredentialInfo = { type: "credential"; id: string; label: string }
@@ -291,6 +285,16 @@ export type ProjectDirectory = { directory: string; strategy?: string }
export type FormMetadata = { [x: string]: JsonValue } export type FormMetadata = { [x: string]: JsonValue }
export type FormWhen = {
key: string
op: "eq" | "neq"
value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}
export type FormOption = { value: string; label: string; description?: string }
export type FormExternalField = { key: string; type: "external"; url: string; title?: string; description?: string }
export type FormValue = string | number | boolean | Array<string> export type FormValue = string | number | boolean | Array<string>
export type PermissionSource = { type: "tool"; messageID: string; id: string } export type PermissionSource = { type: "tool"; messageID: string; id: string }
@@ -1076,8 +1080,6 @@ export type PromptFileAttachment = {
export type PromptAgentAttachment = { name: string; mention?: PromptMention } export type PromptAgentAttachment = { name: string; mention?: PromptMention }
export type PromptSkillAttachment = { id: string; name: string; text: string; mention?: PromptMention }
export type SessionMessageAssistantText = { type: "text"; text: string; state?: SessionMessageProviderState } export type SessionMessageAssistantText = { type: "text"; text: string; state?: SessionMessageProviderState }
export type SessionMessageAssistantReasoning = { export type SessionMessageAssistantReasoning = {
@@ -1273,6 +1275,45 @@ export type ModelCost = {
cache: { read: MoneyUSDPerMillionTokens; write: MoneyUSDPerMillionTokens } cache: { read: MoneyUSDPerMillionTokens; write: MoneyUSDPerMillionTokens }
} }
export type IntegrationTextPrompt = {
type: "text"
key: string
message: string
placeholder?: string
when?: IntegrationWhen
}
export type IntegrationSelectPrompt = {
type: "select"
key: string
message: string
options: Array<{ label: string; value: string; hint?: string }>
when?: IntegrationWhen
}
export type ConnectionInfo = ConnectionCredentialInfo | ConnectionEnvInfo
export type McpServer = {
name: string
status: McpStatusConnected | McpStatusPending | McpStatusDisabled | McpStatusFailed | McpStatusNeedsAuth
integrationID?: string
}
export type McpResourceCatalog = { resources: Array<McpResource>; templates: Array<McpResourceTemplate> }
export type Project = {
id: string
canonical: string
vcs?: ProjectVcs
name?: string
icon?: ProjectIcon
commands?: ProjectCommands
time: ProjectTime
sandboxes: Array<string>
}
export type ProjectDirectories = Array<ProjectDirectory>
export type FormNumberField = { export type FormNumberField = {
key: string key: string
title?: string title?: string
@@ -1338,29 +1379,6 @@ export type FormMultiselectField = {
default?: Array<string> default?: Array<string>
} }
export type ConnectionInfo = ConnectionCredentialInfo | ConnectionEnvInfo
export type McpServer = {
name: string
status: McpStatusConnected | McpStatusPending | McpStatusDisabled | McpStatusFailed | McpStatusNeedsAuth
integrationID?: string
}
export type McpResourceCatalog = { resources: Array<McpResource>; templates: Array<McpResourceTemplate> }
export type Project = {
id: string
canonical: string
vcs?: ProjectVcs
name?: string
icon?: ProjectIcon
commands?: ProjectCommands
time: ProjectTime
sandboxes: Array<string>
}
export type ProjectDirectories = Array<ProjectDirectory>
export type FormAnswer = { [x: string]: FormValue } export type FormAnswer = { [x: string]: FormValue }
export type PermissionRequest = { export type PermissionRequest = {
@@ -1547,7 +1565,6 @@ export type SessionMessageUser = {
text: string text: string
files?: Array<PromptFileAttachment> files?: Array<PromptFileAttachment>
agents?: Array<PromptAgentAttachment> agents?: Array<PromptAgentAttachment>
skills?: Array<PromptSkillAttachment>
type: "user" type: "user"
} }
@@ -1555,7 +1572,6 @@ export type SessionPendingUserData = {
text: string text: string
files?: Array<PromptFileAttachment> files?: Array<PromptFileAttachment>
agents?: Array<PromptAgentAttachment> agents?: Array<PromptAgentAttachment>
skills?: Array<PromptSkillAttachment>
metadata?: { [x: string]: JsonValue } metadata?: { [x: string]: JsonValue }
} }
@@ -1563,7 +1579,6 @@ export type SessionPendingUserData1 = {
text: string text: string
files?: Array<PromptFileAttachment> files?: Array<PromptFileAttachment>
agents?: Array<PromptAgentAttachment> agents?: Array<PromptAgentAttachment>
skills?: Array<PromptSkillAttachment>
metadata?: { [x: string]: any } metadata?: { [x: string]: any }
} }
@@ -1644,6 +1659,13 @@ export type ModelInfo = {
limit: { context: number; input?: number; output: number } limit: { context: number; input?: number; output: number }
} }
export type IntegrationOAuthMethod = {
id: string
type: "oauth"
label: string
prompts?: Array<IntegrationTextPrompt | IntegrationSelectPrompt>
}
export type FormField = export type FormField =
| FormStringField | FormStringField
| FormNumberField | FormNumberField
@@ -1897,9 +1919,15 @@ export type SessionMessageAssistantTool = {
time: { created: number; ran?: number; completed?: number } time: { created: number; ran?: number; completed?: number }
} }
export type IntegrationMethod =
| IntegrationOAuthMethod
| IntegrationCommandMethod
| IntegrationKeyMethod
| IntegrationEnvMethod
export type FormFields = [FormField, ...Array<FormField>] export type FormFields = [FormField, ...Array<FormField>]
export type FormFields3 = [FormField1, ...Array<FormField1>] export type FormFields1 = [FormField1, ...Array<FormField1>]
export type SessionPendingInfo = SessionPendingUser | SessionPendingSynthetic | SessionPendingCompaction export type SessionPendingInfo = SessionPendingUser | SessionPendingSynthetic | SessionPendingCompaction
@@ -1921,13 +1949,16 @@ export type SessionMessageAssistant = {
retry?: SessionMessageAssistantRetry retry?: SessionMessageAssistantRetry
} }
export type IntegrationOAuthMethod = { id: string; type: "oauth"; label: string; form?: FormFields } export type IntegrationInfo = {
id: string
export type IntegrationKeyMethod = { type: "key"; label?: string; form?: FormFields } name: string
methods: Array<IntegrationMethod>
connections: Array<ConnectionInfo>
}
export type FormInfo = { id: string; sessionID: string; title: string; metadata?: FormMetadata; fields: FormFields } export type FormInfo = { id: string; sessionID: string; title: string; metadata?: FormMetadata; fields: FormFields }
export type FormInfo1 = { id: string; sessionID: string; title: string; metadata?: FormMetadata1; fields: FormFields3 } export type FormInfo1 = { id: string; sessionID: string; title: string; metadata?: FormMetadata1; fields: FormFields1 }
export type SessionInputAdmitted = { export type SessionInputAdmitted = {
id: string id: string
@@ -1950,12 +1981,6 @@ export type SessionMessageInfo =
| SessionMessageAssistant | SessionMessageAssistant
| SessionMessageCompaction | SessionMessageCompaction
export type IntegrationMethod =
| IntegrationOAuthMethod
| IntegrationCommandMethod
| IntegrationKeyMethod
| IntegrationEnvMethod
export type FormCreated = { export type FormCreated = {
id: string id: string
created: number created: number
@@ -2016,13 +2041,6 @@ export type SessionMessagesResponse = {
cursor: { previous?: string | null; next?: string | null } cursor: { previous?: string | null; next?: string | null }
} }
export type IntegrationInfo = {
id: string
name: string
methods: Array<IntegrationMethod>
connections: Array<ConnectionInfo>
}
export type V2Event = export type V2Event =
| ModelsDevRefreshed | ModelsDevRefreshed
| IntegrationUpdated | IntegrationUpdated
@@ -2561,12 +2579,6 @@ export type SessionImportInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly name: string
readonly text: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user" readonly type: "user"
} }
| { | {
@@ -2812,12 +2824,6 @@ export type SessionImportInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly name: string
readonly text: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user" readonly type: "user"
} }
| { | {
@@ -3063,12 +3069,6 @@ export type SessionImportInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly name: string
readonly text: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user" readonly type: "user"
} }
| { | {
@@ -3320,10 +3320,6 @@ export type SessionPromptInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
@@ -3341,10 +3337,6 @@ export type SessionPromptInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
@@ -3362,10 +3354,6 @@ export type SessionPromptInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
@@ -3383,35 +3371,10 @@ export type SessionPromptInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["agents"] }["agents"]
readonly skills?: {
readonly id?: string | null
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["skills"]
readonly metadata?: { readonly metadata?: {
readonly id?: string | null readonly id?: string | null
readonly text: string readonly text: string
@@ -3425,10 +3388,6 @@ export type SessionPromptInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
@@ -3446,10 +3405,6 @@ export type SessionPromptInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
@@ -3467,10 +3422,6 @@ export type SessionPromptInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly metadata?: { readonly [x: string]: JsonValue } readonly metadata?: { readonly [x: string]: JsonValue }
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
@@ -3497,10 +3448,6 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["id"] }["id"]
@@ -3520,10 +3467,6 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["command"] }["command"]
@@ -3543,10 +3486,6 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["arguments"] }["arguments"]
@@ -3566,10 +3505,6 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["agent"] }["agent"]
@@ -3589,10 +3524,6 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["model"] }["model"]
@@ -3612,10 +3543,6 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["files"] }["files"]
@@ -3635,36 +3562,9 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["agents"] }["agents"]
readonly skills?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["skills"]
readonly delivery?: { readonly delivery?: {
readonly id?: string | null readonly id?: string | null
readonly command: string readonly command: string
@@ -3681,10 +3581,6 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["delivery"] }["delivery"]
@@ -3704,10 +3600,6 @@ export type SessionCommandInput = {
readonly name: string readonly name: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string } readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}> }>
readonly skills?: ReadonlyArray<{
readonly id: string
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null readonly resume?: boolean | null
}["resume"] }["resume"]
@@ -4022,21 +3914,8 @@ export type IntegrationConnectKeyInput = {
readonly location?: { readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"] }["location"]
readonly key: { readonly key: { readonly key: string; readonly label?: string | undefined }["key"]
readonly key: string readonly label?: { readonly key: string; readonly label?: string | undefined }["label"]
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined
readonly label?: string | undefined
}["key"]
readonly answer?: {
readonly key: string
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined
readonly label?: string | undefined
}["answer"]
readonly label?: {
readonly key: string
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined
readonly label?: string | undefined
}["label"]
} }
export type IntegrationConnectKeyOutput = void export type IntegrationConnectKeyOutput = void
@@ -4048,17 +3927,17 @@ export type IntegrationOauthConnectInput = {
}["location"] }["location"]
readonly methodID: { readonly methodID: {
readonly methodID: string readonly methodID: string
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined readonly label?: string | undefined
}["methodID"] }["methodID"]
readonly answer?: { readonly inputs: {
readonly methodID: string readonly methodID: string
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined readonly label?: string | undefined
}["answer"] }["inputs"]
readonly label?: { readonly label?: {
readonly methodID: string readonly methodID: string
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined readonly label?: string | undefined
}["label"] }["label"]
} }
+4 -2
View File
@@ -66,7 +66,8 @@ export async function ensure(options: EnsureOptions = {}): Promise<Endpoint> {
if (registration.timedOut && registration.info !== undefined) { if (registration.timedOut && registration.info !== undefined) {
timeouts = { timeouts = {
info: registration.info, info: registration.info,
count: timeouts !== undefined && same(timeouts.info, registration.info) ? timeouts.count + 1 : 1, count:
timeouts !== undefined && same(timeouts.info, registration.info) ? timeouts.count + 1 : 1,
} }
if (timeouts.count >= 3) { if (timeouts.count >= 3) {
announce("missing") announce("missing")
@@ -187,7 +188,8 @@ async function probeResult(info: Info, allowLegacy = false) {
const body = result.value.body const body = result.value.body
if (body !== undefined && "version" in body && "pid" in body) { if (body !== undefined && "version" in body && "pid" in body) {
if (body.pid !== info.pid) return { service: undefined, timedOut: false } if (body.pid !== info.pid) return { service: undefined, timedOut: false }
if (info.version !== undefined && body.version !== info.version) return { service: undefined, timedOut: false } if (info.version !== undefined && body.version !== info.version)
return { service: undefined, timedOut: false }
return { return {
service: { service: {
info, info,
+8 -2
View File
@@ -9,7 +9,12 @@ if (mode === "record-start") {
} }
if (mode === "signal") process.kill(process.pid, process.platform === "win32" ? "SIGTERM" : "SIGKILL") if (mode === "signal") process.kill(process.pid, process.platform === "win32" ? "SIGTERM" : "SIGKILL")
if (mode === "delayed" || mode === "delayed-failed" || mode === "coordinated" || mode === "coordinated-failed-loser") { if (
mode === "delayed" ||
mode === "delayed-failed" ||
mode === "coordinated" ||
mode === "coordinated-failed-loser"
) {
await appendFile(registration + ".starts", process.pid + "\n") await appendFile(registration + ".starts", process.pid + "\n")
const owner = await writeFile(registration + ".owner", String(process.pid), { flag: "wx" }) const owner = await writeFile(registration + ".owner", String(process.pid), { flag: "wx" })
.then(() => true) .then(() => true)
@@ -54,7 +59,8 @@ const server = Bun.serve({
if (mode === "legacy") return Response.json({ healthy: true }) if (mode === "legacy") return Response.json({ healthy: true })
if (mode === "starting" && !(await Bun.file(registration + ".release").exists())) if (mode === "starting" && !(await Bun.file(registration + ".release").exists()))
return Response.json({ healthy: true, version, pid: process.pid }, { status: 503 }) return Response.json({ healthy: true, version, pid: process.pid }, { status: 503 })
if (mode === "failed-owner") return Response.json({ healthy: true, version, pid: process.pid }, { status: 500 }) if (mode === "failed-owner")
return Response.json({ healthy: true, version, pid: process.pid }, { status: 500 })
if (mode === "starting" || mode === "graceful" || mode === "reject-stop") if (mode === "starting" || mode === "graceful" || mode === "reject-stop")
return Response.json({ healthy: true, version, pid: process.pid }) return Response.json({ healthy: true, version, pid: process.pid })
return Response.json({ healthy: true, version, pid: process.pid }) return Response.json({ healthy: true, version, pid: process.pid })
-43
View File
@@ -148,49 +148,6 @@ test("experimental wellknown integration add uses the public HTTP contract", asy
expect(await request?.json()).toEqual({ url: "https://example.com" }) expect(await request?.json()).toEqual({ url: "https://example.com" })
}) })
test("integration connections optionally submit a form answer", async () => {
const requests: Request[] = []
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input, init) => {
const request = input instanceof Request ? input : new Request(input, init)
requests.push(request)
if (request.url.endsWith("/connect/key")) return new Response(null, { status: 204 })
return Response.json({
location: { directory: "/tmp/project", project: { id: "proj_test", directory: "/tmp/project" } },
data: {
attemptID: "con_test",
url: "https://example.com/authorize",
instructions: "Authorize",
mode: "auto",
time: { created: 1, expires: 2 },
},
})
},
})
await client.integration.connect.key({
integrationID: "cloudflare-workers-ai",
key: "secret",
answer: { accountId: "account" },
})
await client.integration.oauth.connect({
integrationID: "github-copilot",
methodID: "device",
answer: { deploymentType: "enterprise", enabled: true, scopes: ["read:user"] },
})
await client.integration.connect.key({ integrationID: "openai", key: "secret" })
await client.integration.oauth.connect({ integrationID: "openai", methodID: "device" })
expect(await requests[0].json()).toEqual({ key: "secret", answer: { accountId: "account" } })
expect(await requests[1].json()).toEqual({
methodID: "device",
answer: { deploymentType: "enterprise", enabled: true, scopes: ["read:user"] },
})
expect(await requests[2].json()).toEqual({ key: "secret" })
expect(await requests[3].json()).toEqual({ methodID: "device" })
})
test("health.stop sends exact replacement identity", async () => { test("health.stop sends exact replacement identity", async () => {
let request: Request | undefined let request: Request | undefined
const client = OpenCode.make({ const client = OpenCode.make({
+20 -5
View File
@@ -93,10 +93,16 @@
}, },
"role": { "role": {
"type": "string", "type": "string",
"enum": ["admin", "member"] "enum": [
"admin",
"member"
]
} }
}, },
"required": ["name", "email"], "required": [
"name",
"email"
],
"additionalProperties": false "additionalProperties": false
} }
} }
@@ -137,7 +143,9 @@
"type": "integer" "type": "integer"
} }
}, },
"required": ["query"], "required": [
"query"
],
"additionalProperties": false "additionalProperties": false
} }
}, },
@@ -208,10 +216,17 @@
}, },
"role": { "role": {
"type": "string", "type": "string",
"enum": ["admin", "member"] "enum": [
"admin",
"member"
]
} }
}, },
"required": ["id", "name", "email"], "required": [
"id",
"name",
"email"
],
"additionalProperties": false "additionalProperties": false
} }
}, },
File diff suppressed because it is too large Load Diff
@@ -258,8 +258,7 @@ add("test/built-ins/String/prototype/toUpperCase/supplementary_plane.js", "toUpp
), ),
]) ])
const whitespace = const whitespace = "\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
const lineTerminators = "\u000A\u000D\u2028\u2029" const lineTerminators = "\u000A\u000D\u2028\u2029"
const trim = (file: string, input: string, expected: string) => const trim = (file: string, input: string, expected: string) =>
add(`test/built-ins/String/prototype/trim/${file}`, "trim", [assertion("upstream assertion", input, expected)]) add(`test/built-ins/String/prototype/trim/${file}`, "trim", [assertion("upstream assertion", input, expected)])
@@ -335,35 +334,18 @@ add("test/built-ins/String/prototype/trim/u180e.js", "trim", [
assertion("leading U+180E", "\u180E_", "\u180E_"), assertion("leading U+180E", "\u180E_", "\u180E_"),
]) ])
const directionalWhitespace = const directionalWhitespace = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF"
"\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF"
add("test/built-ins/String/prototype/trimStart/this-value-whitespace.js", "trimStart", [ add("test/built-ins/String/prototype/trimStart/this-value-whitespace.js", "trimStart", [
assertion( assertion("all whitespace", directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace, "a" + directionalWhitespace + "b" + directionalWhitespace),
"all whitespace",
directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace,
"a" + directionalWhitespace + "b" + directionalWhitespace,
),
]) ])
add("test/built-ins/String/prototype/trimStart/this-value-line-terminator.js", "trimStart", [ add("test/built-ins/String/prototype/trimStart/this-value-line-terminator.js", "trimStart", [
assertion( assertion("all line terminators", lineTerminators + "a" + lineTerminators + "b" + lineTerminators, "a" + lineTerminators + "b" + lineTerminators),
"all line terminators",
lineTerminators + "a" + lineTerminators + "b" + lineTerminators,
"a" + lineTerminators + "b" + lineTerminators,
),
]) ])
add("test/built-ins/String/prototype/trimEnd/this-value-whitespace.js", "trimEnd", [ add("test/built-ins/String/prototype/trimEnd/this-value-whitespace.js", "trimEnd", [
assertion( assertion("all whitespace", directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace, directionalWhitespace + "a" + directionalWhitespace + "b"),
"all whitespace",
directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace,
directionalWhitespace + "a" + directionalWhitespace + "b",
),
]) ])
add("test/built-ins/String/prototype/trimEnd/this-value-line-terminator.js", "trimEnd", [ add("test/built-ins/String/prototype/trimEnd/this-value-line-terminator.js", "trimEnd", [
assertion( assertion("all line terminators", lineTerminators + "a" + lineTerminators + "b" + lineTerminators, lineTerminators + "a" + lineTerminators + "b"),
"all line terminators",
lineTerminators + "a" + lineTerminators + "b" + lineTerminators,
lineTerminators + "a" + lineTerminators + "b",
),
]) ])
add("test/built-ins/String/prototype/repeat/repeat-string-n-times.js", "repeat", [ add("test/built-ins/String/prototype/repeat/repeat-string-n-times.js", "repeat", [
assertion("repeat once", "abc", "abc", [1]), assertion("repeat once", "abc", "abc", [1]),
@@ -381,9 +363,7 @@ add("test/built-ins/String/prototype/repeat/count-is-zero-returns-empty-string.j
add("test/built-ins/String/prototype/repeat/count-coerced-to-zero-returns-empty-string.js", "repeat", [ add("test/built-ins/String/prototype/repeat/count-coerced-to-zero-returns-empty-string.js", "repeat", [
assertion("fraction truncates to zero", "abc", "", [0.9]), assertion("fraction truncates to zero", "abc", "", [0.9]),
]) ])
add("test/built-ins/String/prototype/padStart/fill-string-empty.js", "padStart", [ add("test/built-ins/String/prototype/padStart/fill-string-empty.js", "padStart", [assertion("empty fill", "abc", "abc", [5, ""])])
assertion("empty fill", "abc", "abc", [5, ""]),
])
add("test/built-ins/String/prototype/padStart/normal-operation.js", "padStart", [ add("test/built-ins/String/prototype/padStart/normal-operation.js", "padStart", [
assertion("truncated multi-character fill", "abc", "defdabc", [7, "def"]), assertion("truncated multi-character fill", "abc", "defdabc", [7, "def"]),
assertion("single-character fill", "abc", "**abc", [5, "*"]), assertion("single-character fill", "abc", "**abc", [5, "*"]),
@@ -401,9 +381,7 @@ add("test/built-ins/String/prototype/padStart/max-length-not-greater-than-string
assertion("equal length", "abc", "abc", [3, "def"]), assertion("equal length", "abc", "abc", [3, "def"]),
assertion("fraction truncates", "abc", "abc", [3.9999, "def"]), assertion("fraction truncates", "abc", "abc", [3.9999, "def"]),
]) ])
add("test/built-ins/String/prototype/padEnd/fill-string-empty.js", "padEnd", [ add("test/built-ins/String/prototype/padEnd/fill-string-empty.js", "padEnd", [assertion("empty fill", "abc", "abc", [5, ""])])
assertion("empty fill", "abc", "abc", [5, ""]),
])
add("test/built-ins/String/prototype/padEnd/normal-operation.js", "padEnd", [ add("test/built-ins/String/prototype/padEnd/normal-operation.js", "padEnd", [
assertion("truncated multi-character fill", "abc", "abcdefd", [7, "def"]), assertion("truncated multi-character fill", "abc", "abcdefd", [7, "def"]),
assertion("single-character fill", "abc", "abc**", [5, "*"]), assertion("single-character fill", "abc", "abc**", [5, "*"]),
@@ -423,29 +401,11 @@ add("test/built-ins/String/prototype/padEnd/max-length-not-greater-than-string.j
]) ])
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T4.js", "charAt", [assertion("omitted position", "lego", "l")]) add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T4.js", "charAt", [assertion("omitted position", "lego", "l")])
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T7.js", "charAt", [ add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T7.js", "charAt", [assertion("undefined position", "lego", "l", [undefined])])
assertion("undefined position", "lego", "l", [undefined]), add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T8.js", "charAt", [assertion("undefined position", "42", "4", [undefined])])
]) add("test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T1.js", "charAt", ["A", "B", "C", "A", "B", "C"].map((expected, position) => assertion(`position ${position}`, "ABCABC", expected, [position])))
add("test/built-ins/String/prototype/charAt/S15.5.4.4_A1_T8.js", "charAt", [ add("test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T2.js", "charAt", [-2, -1].map((position) => assertion(`position ${position}`, "ABCABC", "", [position])))
assertion("undefined position", "42", "4", [undefined]), add("test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T3.js", "charAt", [6, 7].map((position) => assertion(`position ${position}`, "ABCABC", "", [position])))
])
add(
"test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T1.js",
"charAt",
["A", "B", "C", "A", "B", "C"].map((expected, position) =>
assertion(`position ${position}`, "ABCABC", expected, [position]),
),
)
add(
"test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T2.js",
"charAt",
[-2, -1].map((position) => assertion(`position ${position}`, "ABCABC", "", [position])),
)
add(
"test/built-ins/String/prototype/charAt/S15.5.4.4_A4_T3.js",
"charAt",
[6, 7].map((position) => assertion(`position ${position}`, "ABCABC", "", [position])),
)
add("test/built-ins/String/prototype/charAt/S9.4_A1.js", "charAt", [assertion("NaN position", "abc", "a", [NaN])]) add("test/built-ins/String/prototype/charAt/S9.4_A1.js", "charAt", [assertion("NaN position", "abc", "a", [NaN])])
add("test/built-ins/String/prototype/charAt/S9.4_A2.js", "charAt", [ add("test/built-ins/String/prototype/charAt/S9.4_A2.js", "charAt", [
assertion("positive zero", "abc", "a", [0]), assertion("positive zero", "abc", "a", [0]),
@@ -460,15 +420,9 @@ add("test/built-ins/String/prototype/charAt/pos-rounding.js", "charAt", [
assertion("1.99999", "abc", "b", [1.99999]), assertion("1.99999", "abc", "b", [1.99999]),
]) ])
add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T4.js", "charCodeAt", [ add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T4.js", "charCodeAt", [assertion("omitted position", "smart", 0x73)])
assertion("omitted position", "smart", 0x73), add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T7.js", "charCodeAt", [assertion("undefined position", "lego", 0x6c, [undefined])])
]) add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T8.js", "charCodeAt", [assertion("undefined position", "42", 0x34, [undefined])])
add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T7.js", "charCodeAt", [
assertion("undefined position", "lego", 0x6c, [undefined]),
])
add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T8.js", "charCodeAt", [
assertion("undefined position", "42", 0x34, [undefined]),
])
add("test/built-ins/String/prototype/charCodeAt/pos-rounding.js", "charCodeAt", [ add("test/built-ins/String/prototype/charCodeAt/pos-rounding.js", "charCodeAt", [
assertion("-0.99999", "abc", 0x61, [-0.99999]), assertion("-0.99999", "abc", 0x61, [-0.99999]),
assertion("-0.00001", "abc", 0x61, [-0.00001]), assertion("-0.00001", "abc", 0x61, [-0.00001]),
@@ -479,91 +433,55 @@ add("test/built-ins/String/prototype/charCodeAt/pos-rounding.js", "charCodeAt",
]) ])
add("test/built-ins/String/prototype/codePointAt/return-single-code-unit.js", "codePointAt", [ add("test/built-ins/String/prototype/codePointAt/return-single-code-unit.js", "codePointAt", [
assertion("a", "abc", 97, [0]), assertion("a", "abc", 97, [0]), assertion("b", "abc", 98, [1]), assertion("c", "abc", 99, [2]),
assertion("b", "abc", 98, [1]), assertion("ordinary BMP", "\uAAAA\uBBBB", 0xaaaa, [0]), assertion("before high-surrogate range", "\uD7FF\uAAAA", 0xd7ff, [0]),
assertion("c", "abc", 99, [2]), assertion("low surrogate", "\uDC00\uAAAA", 0xdc00, [0]), assertion("trailing D800", "123\uD800", 0xd800, [3]),
assertion("ordinary BMP", "\uAAAA\uBBBB", 0xaaaa, [0]), assertion("trailing DAAA", "123\uDAAA", 0xdaaa, [3]), assertion("trailing DBFF", "123\uDBFF", 0xdbff, [3]),
assertion("before high-surrogate range", "\uD7FF\uAAAA", 0xd7ff, [0]),
assertion("low surrogate", "\uDC00\uAAAA", 0xdc00, [0]),
assertion("trailing D800", "123\uD800", 0xd800, [3]),
assertion("trailing DAAA", "123\uDAAA", 0xdaaa, [3]),
assertion("trailing DBFF", "123\uDBFF", 0xdbff, [3]),
]) ])
add("test/built-ins/String/prototype/codePointAt/return-first-code-unit.js", "codePointAt", [ add("test/built-ins/String/prototype/codePointAt/return-first-code-unit.js", "codePointAt", [
assertion("D800 before DBFF", "\uD800\uDBFF", 0xd800, [0]), assertion("D800 before DBFF", "\uD800\uDBFF", 0xd800, [0]), assertion("D800 before E000", "\uD800\uE000", 0xd800, [0]),
assertion("D800 before E000", "\uD800\uE000", 0xd800, [0]), assertion("DAAA before DBFF", "\uDAAA\uDBFF", 0xdaaa, [0]), assertion("DAAA before E000", "\uDAAA\uE000", 0xdaaa, [0]),
assertion("DAAA before DBFF", "\uDAAA\uDBFF", 0xdaaa, [0]), assertion("DBFF before DBFF", "\uDBFF\uDBFF", 0xdbff, [0]), assertion("DBFF before E000", "\uDBFF\uE000", 0xdbff, [0]),
assertion("DAAA before E000", "\uDAAA\uE000", 0xdaaa, [0]), assertion("D800 before NUL", "\uD800\u0000", 0xd800, [0]), assertion("D800 before FFFF", "\uD800\uFFFF", 0xd800, [0]),
assertion("DBFF before DBFF", "\uDBFF\uDBFF", 0xdbff, [0]), assertion("DAAA before NUL", "\uDAAA\u0000", 0xdaaa, [0]), assertion("DAAA before FFFF", "\uDAAA\uFFFF", 0xdaaa, [0]),
assertion("DBFF before E000", "\uDBFF\uE000", 0xdbff, [0]),
assertion("D800 before NUL", "\uD800\u0000", 0xd800, [0]),
assertion("D800 before FFFF", "\uD800\uFFFF", 0xd800, [0]),
assertion("DAAA before NUL", "\uDAAA\u0000", 0xdaaa, [0]),
assertion("DAAA before FFFF", "\uDAAA\uFFFF", 0xdaaa, [0]),
assertion("DBFF before FFFF", "\uDBFF\uFFFF", 0xdbff, [0]), assertion("DBFF before FFFF", "\uDBFF\uFFFF", 0xdbff, [0]),
]) ])
add("test/built-ins/String/prototype/codePointAt/return-utf16-decode.js", "codePointAt", [ add("test/built-ins/String/prototype/codePointAt/return-utf16-decode.js", "codePointAt", [
assertion("U+10000", "\uD800\uDC00", 65536, [0]), assertion("U+10000", "\uD800\uDC00", 65536, [0]), assertion("U+101D0", "\uD800\uDDD0", 66000, [0]),
assertion("U+101D0", "\uD800\uDDD0", 66000, [0]), assertion("U+103FF", "\uD800\uDFFF", 66559, [0]), assertion("U+BA800", "\uDAAA\uDC00", 763904, [0]),
assertion("U+103FF", "\uD800\uDFFF", 66559, [0]), assertion("U+BA9D0", "\uDAAA\uDDD0", 764368, [0]), assertion("U+BABFF", "\uDAAA\uDFFF", 764927, [0]),
assertion("U+BA800", "\uDAAA\uDC00", 763904, [0]), assertion("U+10FC00", "\uDBFF\uDC00", 1113088, [0]), assertion("U+10FDD0", "\uDBFF\uDDD0", 1113552, [0]),
assertion("U+BA9D0", "\uDAAA\uDDD0", 764368, [0]),
assertion("U+BABFF", "\uDAAA\uDFFF", 764927, [0]),
assertion("U+10FC00", "\uDBFF\uDC00", 1113088, [0]),
assertion("U+10FDD0", "\uDBFF\uDDD0", 1113552, [0]),
assertion("U+10FFFF", "\uDBFF\uDFFF", 1114111, [0]), assertion("U+10FFFF", "\uDBFF\uDFFF", 1114111, [0]),
]) ])
add("test/built-ins/String/prototype/codePointAt/return-code-unit-coerced-position.js", "codePointAt", [ add("test/built-ins/String/prototype/codePointAt/return-code-unit-coerced-position.js", "codePointAt", [
assertion("NaN", "\uD800\uDC00", 65536, [NaN]), assertion("NaN", "\uD800\uDC00", 65536, [NaN]), assertion("undefined", "\uD800\uDC00", 65536, [undefined]),
assertion("undefined", "\uD800\uDC00", 65536, [undefined]),
]) ])
add("test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-less-than-zero.js", "codePointAt", [ add("test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-less-than-zero.js", "codePointAt", [
{ label: "negative one", input: "abc", args: [-1], outcome: "undefined" }, { label: "negative one", input: "abc", args: [-1], outcome: "undefined" },
{ label: "negative infinity", input: "abc", args: [-Infinity], outcome: "undefined" }, { label: "negative infinity", input: "abc", args: [-Infinity], outcome: "undefined" },
]) ])
add( add("test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-equal-or-more-than-size.js", "codePointAt", [
"test/built-ins/String/prototype/codePointAt/returns-undefined-on-position-equal-or-more-than-size.js",
"codePointAt",
[
{ label: "equal to size", input: "abc", args: [3], outcome: "undefined" }, { label: "equal to size", input: "abc", args: [3], outcome: "undefined" },
{ label: "greater than size", input: "abc", args: [4], outcome: "undefined" }, { label: "greater than size", input: "abc", args: [4], outcome: "undefined" },
{ label: "positive infinity", input: "abc", args: [Infinity], outcome: "undefined" }, { label: "positive infinity", input: "abc", args: [Infinity], outcome: "undefined" },
], ])
)
add("test/built-ins/String/prototype/at/returns-code-unit.js", "at", [ add("test/built-ins/String/prototype/at/returns-code-unit.js", "at", [
assertion("position 0", "12\uD80034", "1", [0]), assertion("position 0", "12\uD80034", "1", [0]), assertion("position 1", "12\uD80034", "2", [1]),
assertion("position 1", "12\uD80034", "2", [1]), assertion("unpaired surrogate", "12\uD80034", "\uD800", [2]), assertion("position 3", "12\uD80034", "3", [3]),
assertion("unpaired surrogate", "12\uD80034", "\uD800", [2]),
assertion("position 3", "12\uD80034", "3", [3]),
assertion("position 4", "12\uD80034", "4", [4]), assertion("position 4", "12\uD80034", "4", [4]),
]) ])
add( add("test/built-ins/String/prototype/at/returns-item.js", "at", ["1", "2", "3", "4", "5"].map((expected, position) => assertion(`position ${position}`, "12345", expected, [position])))
"test/built-ins/String/prototype/at/returns-item.js",
"at",
["1", "2", "3", "4", "5"].map((expected, position) =>
assertion(`position ${position}`, "12345", expected, [position]),
),
)
add("test/built-ins/String/prototype/at/returns-item-relative-index.js", "at", [ add("test/built-ins/String/prototype/at/returns-item-relative-index.js", "at", [
assertion("zero", "12345", "1", [0]), assertion("zero", "12345", "1", [0]), assertion("negative one", "12345", "5", [-1]),
assertion("negative one", "12345", "5", [-1]), assertion("negative three", "12345", "3", [-3]), assertion("negative four", "12345", "2", [-4]),
assertion("negative three", "12345", "3", [-3]),
assertion("negative four", "12345", "2", [-4]),
])
add(
"test/built-ins/String/prototype/at/returns-undefined-for-out-of-range-index.js",
"at",
[-2, 0, 1].map((position) => ({ label: `position ${position}`, input: "", args: [position], outcome: "undefined" })),
)
add("test/built-ins/String/prototype/at/index-non-numeric-argument-tointeger.js", "at", [
assertion("undefined", "01", "0", [undefined]),
]) ])
add("test/built-ins/String/prototype/at/returns-undefined-for-out-of-range-index.js", "at", [-2, 0, 1].map((position) => ({ label: `position ${position}`, input: "", args: [position], outcome: "undefined" })))
add("test/built-ins/String/prototype/at/index-non-numeric-argument-tointeger.js", "at", [assertion("undefined", "01", "0", [undefined])])
add("test/built-ins/String/prototype/concat/S15.5.4.6_A1_T4.js", "concat", [assertion("no arguments", "lego", "lego")]) add("test/built-ins/String/prototype/concat/S15.5.4.6_A1_T4.js", "concat", [assertion("no arguments", "lego", "lego")])
add("test/built-ins/String/prototype/toString/string-primitive.js", "toString", [ add("test/built-ins/String/prototype/toString/string-primitive.js", "toString", [
assertion("empty string", "", ""), assertion("empty string", "", ""), assertion("non-empty string", "str", "str"),
assertion("non-empty string", "str", "str"),
]) ])
add("test/built-ins/String/prototype/normalize/return-normalized-string.js", "normalize", [ add("test/built-ins/String/prototype/normalize/return-normalized-string.js", "normalize", [
@@ -574,15 +492,11 @@ add("test/built-ins/String/prototype/normalize/return-normalized-string.js", "no
assertion("NFC long", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFC"]), assertion("NFC long", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFC"]),
assertion("NFD long", "\u00C5\u2ADC\u0958\u2126\u0344", "A\u030A\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFD"]), assertion("NFD long", "\u00C5\u2ADC\u0958\u2126\u0344", "A\u030A\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFD"]),
assertion("NFKC long", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFKC"]), assertion("NFKC long", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFKC"]),
assertion("NFKD long", "\u00C5\u2ADC\u0958\u2126\u0344", "A\u030A\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", [ assertion("NFKD long", "\u00C5\u2ADC\u0958\u2126\u0344", "A\u030A\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFKD"]),
"NFKD",
]),
]) ])
add("test/built-ins/String/prototype/normalize/return-normalized-string-using-default-parameter.js", "normalize", [ add("test/built-ins/String/prototype/normalize/return-normalized-string-using-default-parameter.js", "normalize", [
assertion("omitted", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301"), assertion("omitted", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301"),
assertion("undefined", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", [ assertion("undefined", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", [undefined]),
undefined,
]),
]) ])
add("test/built-ins/String/prototype/normalize/form-is-not-valid-throws.js", "normalize", [ add("test/built-ins/String/prototype/normalize/form-is-not-valid-throws.js", "normalize", [
{ label: "bar", input: "foo", args: ["bar"], outcome: "RangeError" }, { label: "bar", input: "foo", args: ["bar"], outcome: "RangeError" },
@@ -590,141 +504,51 @@ add("test/built-ins/String/prototype/normalize/form-is-not-valid-throws.js", "no
]) ])
add("test/built-ins/String/prototype/localeCompare/15.5.4.9_CE.js", "localeCompare", [ add("test/built-ins/String/prototype/localeCompare/15.5.4.9_CE.js", "localeCompare", [
assertion("D70", "o\u0308", 0, ["ö"]), assertion("D70", "o\u0308", 0, ["ö"]), assertion("reordered diaeresis", "ä\u0323", 0, ["a\u0323\u0308"]),
assertion("reordered diaeresis", "ä\u0323", 0, ["a\u0323\u0308"]), assertion("reordered marks", "a\u0308\u0323", 0, ["a\u0323\u0308"]), assertion("precomposed dot below", "ạ\u0308", 0, ["a\u0323\u0308"]),
assertion("reordered marks", "a\u0308\u0323", 0, ["a\u0323\u0308"]), assertion("breve after diaeresis", "ä\u0306", 0, ["a\u0308\u0306"]), assertion("diaeresis after breve", "ă\u0308", 0, ["a\u0306\u0308"]),
assertion("precomposed dot below", "ạ\u0308", 0, ["a\u0323\u0308"]), assertion("Hangul", "\u1111\u1171\u11B6", 0, ["퓛"]), assertion("angstrom compatibility", "", 0, ["Å"]),
assertion("breve after diaeresis", "ä\u0306", 0, ["a\u0308\u0306"]), assertion("angstrom decomposed", "Å", 0, ["A\u030A"]), assertion("reordered horn and dot", "x\u031B\u0323", 0, ["x\u0323\u031B"]),
assertion("diaeresis after breve", "ă\u0308", 0, ["a\u0306\u0308"]), assertion("Vietnamese precomposed 1", "ự", 0, ["ụ\u031B"]), assertion("Vietnamese decomposed", "ự", 0, ["u\u031B\u0323"]),
assertion("Hangul", "\u1111\u1171\u11B6", 0, [""]), assertion("Vietnamese precomposed 2", "ự", 0, ["ư\u0323"]), assertion("Vietnamese reordered", "ự", 0, ["u\u0323\u031B"]),
assertion("angstrom compatibility", "", 0, ["Å"]), assertion("cedilla", "Ç", 0, ["C\u0327"]), assertion("q reordered", "q\u0307\u0323", 0, ["q\u0323\u0307"]),
assertion("angstrom decomposed", "", 0, ["A\u030A"]), assertion("Hangul syllable", "", 0, ["\u1100\u1161"]), assertion("ohm", "Ω", 0, ["]),
assertion("reordered horn and dot", "x\u031B\u0323", 0, ["x\u0323\u031B"]), assertion("angstrom", "Å", 0, ["A\u030A"]), assertion("circumflex", ", 0, ["o\u0302"]),
assertion("Vietnamese precomposed 1", "", 0, ["\u031B"]), assertion("s with marks", "ṩ", 0, ["s\u0323\u0307"]), assertion("d composed plus dot", "ḋ\u0323", 0, ["d\u0323\u0307"]),
assertion("Vietnamese decomposed", "ự", 0, ["u\u031B\u0323"]),
assertion("Vietnamese precomposed 2", "ự", 0, ["ư\u0323"]),
assertion("Vietnamese reordered", "ự", 0, ["u\u0323\u031B"]),
assertion("cedilla", "Ç", 0, ["C\u0327"]),
assertion("q reordered", "q\u0307\u0323", 0, ["q\u0323\u0307"]),
assertion("Hangul syllable", "가", 0, ["\u1100\u1161"]),
assertion("ohm", "Ω", 0, ["Ω"]),
assertion("angstrom", "Å", 0, ["A\u030A"]),
assertion("circumflex", "ô", 0, ["o\u0302"]),
assertion("s with marks", "ṩ", 0, ["s\u0323\u0307"]),
assertion("d composed plus dot", "ḋ\u0323", 0, ["d\u0323\u0307"]),
assertion("d two precompositions", "ḋ\u0323", 0, ["ḍ\u0307"]), assertion("d two precompositions", "ḋ\u0323", 0, ["ḍ\u0307"]),
]) ])
add( add("test/built-ins/String/fromCharCode/S15.5.3.2_A2.js", "fromCharCode", [{ label: "no arguments", expected: "" }], true)
"test/built-ins/String/fromCharCode/S15.5.3.2_A2.js", add("test/built-ins/String/fromCharCode/S15.5.3.2_A3_T1.js", "fromCharCode", [{ label: "ABBA", args: [65, 66, 66, 65], expected: "ABBA" }], true)
"fromCharCode", add("test/built-ins/String/fromCharCode/S9.7_A1.js", "fromCharCode", [
[{ label: "no arguments", expected: "" }], { label: "NaN", args: [NaN], expected: 0 }, { label: "zero", args: [0], expected: 0 }, { label: "negative zero", args: [-0], expected: 0 },
true, { label: "positive infinity", args: [Infinity], expected: 0 }, { label: "negative infinity", args: [-Infinity], expected: 0 },
) ], true)
add( add("test/built-ins/String/fromCharCode/S9.7_A2.1.js", "fromCharCode", [
"test/built-ins/String/fromCharCode/S15.5.3.2_A3_T1.js", [0, 0], [1, 1], [-1, 65535], [65535, 65535], [65534, 65534], [65536, 0], [4294967295, 65535], [4294967294, 65534], [4294967296, 0],
"fromCharCode", ].map(([input, expected]) => ({ label: String(input), args: [input!], expected })), true)
[{ label: "ABBA", args: [65, 66, 66, 65], expected: "ABBA" }], add("test/built-ins/String/fromCharCode/S9.7_A2.2.js", "fromCharCode", [
true, [-32767, 32769], [-32768, 32768], [-32769, 32767], [-65535, 1], [-65536, 0], [-65537, 65535], [65535, 65535], [65536, 0], [65537, 1], [131071, 65535], [131072, 0], [131073, 1],
) ].map(([input, expected]) => ({ label: String(input), args: [input!], expected })), true)
add( add("test/built-ins/String/fromCharCode/S9.7_A3.2_T1.js", "fromCharCode", [
"test/built-ins/String/fromCharCode/S9.7_A1.js", { label: "positive fraction", args: [1.2345], expected: 1 }, { label: "negative fraction", args: [-5.4321], expected: 65531 },
"fromCharCode", ], true)
[
{ label: "NaN", args: [NaN], expected: 0 },
{ label: "zero", args: [0], expected: 0 },
{ label: "negative zero", args: [-0], expected: 0 },
{ label: "positive infinity", args: [Infinity], expected: 0 },
{ label: "negative infinity", args: [-Infinity], expected: 0 },
],
true,
)
add(
"test/built-ins/String/fromCharCode/S9.7_A2.1.js",
"fromCharCode",
[
[0, 0],
[1, 1],
[-1, 65535],
[65535, 65535],
[65534, 65534],
[65536, 0],
[4294967295, 65535],
[4294967294, 65534],
[4294967296, 0],
].map(([input, expected]) => ({ label: String(input), args: [input!], expected })),
true,
)
add(
"test/built-ins/String/fromCharCode/S9.7_A2.2.js",
"fromCharCode",
[
[-32767, 32769],
[-32768, 32768],
[-32769, 32767],
[-65535, 1],
[-65536, 0],
[-65537, 65535],
[65535, 65535],
[65536, 0],
[65537, 1],
[131071, 65535],
[131072, 0],
[131073, 1],
].map(([input, expected]) => ({ label: String(input), args: [input!], expected })),
true,
)
add(
"test/built-ins/String/fromCharCode/S9.7_A3.2_T1.js",
"fromCharCode",
[
{ label: "positive fraction", args: [1.2345], expected: 1 },
{ label: "negative fraction", args: [-5.4321], expected: 65531 },
],
true,
)
add( add("test/built-ins/String/fromCodePoint/arguments-is-empty.js", "fromCodePoint", [{ label: "no arguments", expected: "" }], true)
"test/built-ins/String/fromCodePoint/arguments-is-empty.js", add("test/built-ins/String/fromCodePoint/return-string-value.js", "fromCodePoint", [
"fromCodePoint", { label: "NUL", args: [0], expected: "\x00" }, { label: "asterisk", args: [42], expected: "*" },
[{ label: "no arguments", expected: "" }], { label: "AZ", args: [65, 90], expected: "AZ" }, { label: "Cyrillic", args: [0x404], expected: "\u0404" },
true, { label: "hex supplementary", args: [0x2f804], expected: "\uD87E\uDC04" }, { label: "decimal supplementary", args: [194564], expected: "\uD87E\uDC04" },
)
add(
"test/built-ins/String/fromCodePoint/return-string-value.js",
"fromCodePoint",
[
{ label: "NUL", args: [0], expected: "\x00" },
{ label: "asterisk", args: [42], expected: "*" },
{ label: "AZ", args: [65, 90], expected: "AZ" },
{ label: "Cyrillic", args: [0x404], expected: "\u0404" },
{ label: "hex supplementary", args: [0x2f804], expected: "\uD87E\uDC04" },
{ label: "decimal supplementary", args: [194564], expected: "\uD87E\uDC04" },
{ label: "mixed supplementary", args: [0x1d306, 0x61, 0x1d307], expected: "\uD834\uDF06a\uD834\uDF07" }, { label: "mixed supplementary", args: [0x1d306, 0x61, 0x1d307], expected: "\uD834\uDF06a\uD834\uDF07" },
{ label: "maximum code point", args: [1114111], expected: "\uDBFF\uDFFF" }, { label: "maximum code point", args: [1114111], expected: "\uDBFF\uDFFF" },
], ], true)
true, add("test/built-ins/String/fromCodePoint/argument-is-not-integer.js", "fromCodePoint", [
) { label: "fraction", args: [3.14], outcome: "RangeError" }, { label: "fraction after valid", args: [42, 3.14], outcome: "RangeError" },
add( ], true)
"test/built-ins/String/fromCodePoint/argument-is-not-integer.js", add("test/built-ins/String/fromCodePoint/number-is-out-of-range.js", "fromCodePoint", [
"fromCodePoint", { label: "negative one", args: [-1], outcome: "RangeError" }, { label: "negative after valid", args: [1, -1], outcome: "RangeError" },
[ { label: "above maximum", args: [1114112], outcome: "RangeError" }, { label: "infinity", args: [Infinity], outcome: "RangeError" },
{ label: "fraction", args: [3.14], outcome: "RangeError" }, ], true)
{ label: "fraction after valid", args: [42, 3.14], outcome: "RangeError" },
],
true,
)
add(
"test/built-ins/String/fromCodePoint/number-is-out-of-range.js",
"fromCodePoint",
[
{ label: "negative one", args: [-1], outcome: "RangeError" },
{ label: "negative after valid", args: [1, -1], outcome: "RangeError" },
{ label: "above maximum", args: [1114112], outcome: "RangeError" },
{ label: "infinity", args: [Infinity], outcome: "RangeError" },
],
true,
)
describe("Test262-adapted core String behavior", () => { describe("Test262-adapted core String behavior", () => {
for (const vector of vectors) { for (const vector of vectors) {
@@ -734,12 +558,10 @@ describe("Test262-adapted core String behavior", () => {
const expression = vector.static const expression = vector.static
? `String.${vector.method}(${args})` ? `String.${vector.method}(${args})`
: `${JSON.stringify(item.input)}.${vector.method}(${args})` : `${JSON.stringify(item.input)}.${vector.method}(${args})`
const observed = const observed = vector.static && vector.method === "fromCharCode" && typeof item.expected === "number"
vector.static && vector.method === "fromCharCode" && typeof item.expected === "number"
? `${expression}.charCodeAt(0)` ? `${expression}.charCodeAt(0)`
: expression : expression
const checked = const checked = item.outcome === "undefined"
item.outcome === "undefined"
? `${observed} === undefined` ? `${observed} === undefined`
: item.outcome === "length" : item.outcome === "length"
? `${observed}.length` ? `${observed}.length`
@@ -121,35 +121,9 @@ run("Test262-adapted regexp split behavior", [
] ]
`, `,
expected: [ expected: [
["x"], ["x"], ["x"], ["", ""], ["", ""], ["", ""], ["x"], ["", ""], ["", ""], ["", ""],
["x"], ["x"], ["", ""], ["x"], ["x"], ["x"], ["x"], ["", ""], ["x"], ["x"], ["x"],
["", ""], ["x"], ["x"], ["", ""], ["x"], ["x"], ["x"], ["", ""], ["x"], ["", ""], ["x"],
["", ""],
["", ""],
["x"],
["", ""],
["", ""],
["", ""],
["x"],
["", ""],
["x"],
["x"],
["x"],
["x"],
["", ""],
["x"],
["x"],
["x"],
["x"],
["x"],
["", ""],
["x"],
["x"],
["x"],
["", ""],
["x"],
["", ""],
["x"],
], ],
}, },
{ {
@@ -279,71 +253,19 @@ run("Test262-adapted replace behavior", [
return replacements.flatMap((replacement) => patterns.map((pattern) => str.replace(pattern, replacement))) return replacements.flatMap((replacement) => patterns.map((pattern) => str.replace(pattern, replacement)))
`, `,
expected: [ expected: [
"foo-|$0|-bar", "foo-|$0|-bar", "foo-|$0|-bar", "foo-|$0|-bar", "foo-|$0|-bar", "foo-|$0|-bar",
"foo-|$0|-bar", "foo-|$00|-bar", "foo-|$00|-bar", "foo-|$00|-bar", "foo-|$00|-bar", "foo-|$00|-bar",
"foo-|$0|-bar", "foo-|$000|-bar", "foo-|$000|-bar", "foo-|$000|-bar", "foo-|$000|-bar", "foo-|$000|-bar",
"foo-|$0|-bar", "foo-|$1|-bar", "foo-|$1|-bar", "foo-|x|-bar", "foo-|x|-bar", "foo-|x|-bar",
"foo-|$0|-bar", "foo-|$01|-bar", "foo-|$01|-bar", "foo-|x|-bar", "foo-|x|-bar", "foo-|x|-bar",
"foo-|$00|-bar", "foo-|$010|-bar", "foo-|$010|-bar", "foo-|x0|-bar", "foo-|x0|-bar", "foo-|x0|-bar",
"foo-|$00|-bar", "foo-|$2|-bar", "foo-|$2|-bar", "foo-|$2|-bar", "foo-||-bar", "foo-|x|-bar",
"foo-|$00|-bar", "foo-|$02|-bar", "foo-|$02|-bar", "foo-|$02|-bar", "foo-||-bar", "foo-|x|-bar",
"foo-|$00|-bar", "foo-|$020|-bar", "foo-|$020|-bar", "foo-|$020|-bar", "foo-|0|-bar", "foo-|x0|-bar",
"foo-|$00|-bar", "foo-|$10|-bar", "foo-|$10|-bar", "foo-|x0|-bar", "foo-|x0|-bar", "foo-|x|-bar",
"foo-|$000|-bar", "foo-|$100|-bar", "foo-|$100|-bar", "foo-|x00|-bar", "foo-|x00|-bar", "foo-|x0|-bar",
"foo-|$000|-bar", "foo-|$20|-bar", "foo-|$20|-bar", "foo-|$20|-bar", "foo-|0|-bar", "foo-|x0|-bar",
"foo-|$000|-bar", "foo-|$200|-bar", "foo-|$200|-bar", "foo-|$200|-bar", "foo-|00|-bar", "foo-|x00|-bar",
"foo-|$000|-bar",
"foo-|$000|-bar",
"foo-|$1|-bar",
"foo-|$1|-bar",
"foo-|x|-bar",
"foo-|x|-bar",
"foo-|x|-bar",
"foo-|$01|-bar",
"foo-|$01|-bar",
"foo-|x|-bar",
"foo-|x|-bar",
"foo-|x|-bar",
"foo-|$010|-bar",
"foo-|$010|-bar",
"foo-|x0|-bar",
"foo-|x0|-bar",
"foo-|x0|-bar",
"foo-|$2|-bar",
"foo-|$2|-bar",
"foo-|$2|-bar",
"foo-||-bar",
"foo-|x|-bar",
"foo-|$02|-bar",
"foo-|$02|-bar",
"foo-|$02|-bar",
"foo-||-bar",
"foo-|x|-bar",
"foo-|$020|-bar",
"foo-|$020|-bar",
"foo-|$020|-bar",
"foo-|0|-bar",
"foo-|x0|-bar",
"foo-|$10|-bar",
"foo-|$10|-bar",
"foo-|x0|-bar",
"foo-|x0|-bar",
"foo-|x|-bar",
"foo-|$100|-bar",
"foo-|$100|-bar",
"foo-|x00|-bar",
"foo-|x00|-bar",
"foo-|x0|-bar",
"foo-|$20|-bar",
"foo-|$20|-bar",
"foo-|$20|-bar",
"foo-|0|-bar",
"foo-|x0|-bar",
"foo-|$200|-bar",
"foo-|$200|-bar",
"foo-|$200|-bar",
"foo-|00|-bar",
"foo-|x00|-bar",
], ],
}, },
{ {
@@ -455,30 +377,12 @@ run("Test262-adapted replaceAll behavior", [
] ]
`, `,
expected: [ expected: [
"azc azc azc", "azc azc azc", "abc abc abc", "abc abc abc", "o ppercase!", "o Uppercase?", " UPPERCASE!",
"abc abc abc", "ca-bbcca-bbc", "abb$3cabb$3cabb$3cabb$3c",
"abc abc abc",
"o ppercase!",
"o Uppercase?",
" UPPERCASE!",
"ca-bbcca-bbc",
"abb$3cabb$3cabb$3cabb$3c",
"(aabaca)-(aaba)-(aabacadaea)f(agahai)-(agah)-(agahaiajak)(alaman)-(alam)-(alamano a )azaya", "(aabaca)-(aaba)-(aabacadaea)f(agahai)-(agah)-(agahaiajak)(alaman)-(alam)-(alamano a )azaya",
"acbacaa", "acbacaa", "aacabca", "a(b)c(b)a", "a($<named)c($<named)a", "a()c()a", "($)bc($)bc",
"aacabca", "($)bc($)bc", "($$)bc($$)bc", "($$)bc($$)bc", "($&)bc($&)bc", "($1)bc($1)bc",
"a(b)c(b)a", "($`)bc($`)bc", "($')bc($')bc", "($<z>)bc($<z>)bc", "(abca)bc(abca)bc",
"a($<named)c($<named)a",
"a()c()a",
"($)bc($)bc",
"($)bc($)bc",
"($$)bc($$)bc",
"($$)bc($$)bc",
"($&)bc($&)bc",
"($1)bc($1)bc",
"($`)bc($`)bc",
"($')bc($')bc",
"($<z>)bc($<z>)bc",
"(abca)bc(abca)bc",
], ],
}, },
{ {
@@ -568,17 +472,7 @@ run("Test262-adapted replaceAll behavior", [
const str = "ABC AAA ABC AAA" const str = "ABC AAA ABC AAA"
return ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9"].map((replacement) => str.replaceAll("ABC", replacement)) return ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9"].map((replacement) => str.replaceAll("ABC", replacement))
`, `,
expected: [ expected: ["$1 AAA $1 AAA", "$2 AAA $2 AAA", "$3 AAA $3 AAA", "$4 AAA $4 AAA", "$5 AAA $5 AAA", "$6 AAA $6 AAA", "$7 AAA $7 AAA", "$8 AAA $8 AAA", "$9 AAA $9 AAA"],
"$1 AAA $1 AAA",
"$2 AAA $2 AAA",
"$3 AAA $3 AAA",
"$4 AAA $4 AAA",
"$5 AAA $5 AAA",
"$6 AAA $6 AAA",
"$7 AAA $7 AAA",
"$8 AAA $8 AAA",
"$9 AAA $9 AAA",
],
}, },
{ {
path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024NN.js", path: "test/built-ins/String/prototype/replaceAll/getSubstitution-0x0024NN.js",
@@ -248,12 +248,8 @@ const cases = [
code: `const result = "one two three four five".split(" "); return [result.length, ...result]`, code: `const result = "one two three four five".split(" "); return [result.length, ...result]`,
expected: [5, "one", "two", "three", "four", "five"], expected: [5, "one", "two", "three", "four", "five"],
labels: [ labels: [
"The value of __split.length is 5", "The value of __split.length is 5", 'The value of __split[0] is "one"', 'The value of __split[1] is "two"',
'The value of __split[0] is "one"', 'The value of __split[2] is "three"', 'The value of __split[3] is "four"', 'The value of __split[4] is "five"',
'The value of __split[1] is "two"',
'The value of __split[2] is "three"',
'The value of __split[3] is "four"',
'The value of __split[4] is "five"',
], ],
}, },
{ {
@@ -261,10 +257,8 @@ const cases = [
code: `const result = "one two three".split(""); return [result[0], result[1], result[11], result[12]]`, code: `const result = "one two three".split(""); return [result[0], result[1], result[11], result[12]]`,
expected: ["o", "n", "e", "e"], expected: ["o", "n", "e", "e"],
labels: [ labels: [
'The value of __split[0] is "o"', 'The value of __split[0] is "o"', 'The value of __split[1] is "n"',
'The value of __split[1] is "n"', 'The value of __split[11] is "e"', 'The value of __split[12] is "e"',
'The value of __split[11] is "e"',
'The value of __split[12] is "e"',
], ],
}, },
{ {
@@ -320,11 +314,7 @@ const cases = [
path: "test/built-ins/String/prototype/split/separator-undef.js", path: "test/built-ins/String/prototype/split/separator-undef.js",
code: `const result = "undefined is not a function".split(); return [Array.isArray(result), result.length, result[0]]`, code: `const result = "undefined is not a function".split(); return [Array.isArray(result), result.length, result[0]]`,
expected: [true, 1, "undefined is not a function"], expected: [true, 1, "undefined is not a function"],
labels: [ labels: ["implicit separator, result is array", "implicit separator, result.length", "implicit separator, [0] is the same string"],
"implicit separator, result is array",
"implicit separator, result.length",
"implicit separator, [0] is the same string",
],
}, },
{ {
path: "test/built-ins/String/prototype/slice/S15.5.4.13_A1_T6.js", path: "test/built-ins/String/prototype/slice/S15.5.4.13_A1_T6.js",
@@ -466,33 +456,23 @@ const cases = [
}, },
{ {
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailMissingLetter.js", path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailMissingLetter.js",
code: `return ["word".includes("a", 0)]`, code: `return ["word".includes("a", 0)]`, expected: [false], labels: ['"word".includes("a", 0)'],
expected: [false],
labels: ['"word".includes("a", 0)'],
}, },
{ {
path: "test/built-ins/String/prototype/includes/String.prototype.includes_SuccessNoLocation.js", path: "test/built-ins/String/prototype/includes/String.prototype.includes_SuccessNoLocation.js",
code: `return ["word".includes("w")]`, code: `return ["word".includes("w")]`, expected: [true], labels: ['"word".includes("w")'],
expected: [true],
labels: ['"word".includes("w")'],
}, },
{ {
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailBadLocation.js", path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailBadLocation.js",
code: `return ["word".includes("w", 5)]`, code: `return ["word".includes("w", 5)]`, expected: [false], labels: ['"word".includes("w", 5)'],
expected: [false],
labels: ['"word".includes("w", 5)'],
}, },
{ {
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailLocation.js", path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailLocation.js",
code: `return ["word".includes("o", 3)]`, code: `return ["word".includes("o", 3)]`, expected: [false], labels: ['"word".includes("o", 3)'],
expected: [false],
labels: ['"word".includes("o", 3)'],
}, },
{ {
path: "test/built-ins/String/prototype/includes/String.prototype.includes_Success.js", path: "test/built-ins/String/prototype/includes/String.prototype.includes_Success.js",
code: `return ["word".includes("w", 0)]`, code: `return ["word".includes("w", 0)]`, expected: [true], labels: ['"word".includes("w", 0)'],
expected: [true],
labels: ['"word".includes("w", 0)'],
}, },
{ {
path: "test/built-ins/String/prototype/includes/searchstring-found-with-position.js", path: "test/built-ins/String/prototype/includes/searchstring-found-with-position.js",
@@ -523,8 +503,7 @@ const cases = [
{ {
path: "test/built-ins/String/prototype/includes/searchstring-not-found-without-position.js", path: "test/built-ins/String/prototype/includes/searchstring-not-found-without-position.js",
code: `const text = "The future is cool!"; return [text.includes("Flash"), text.includes("FUTURE")]`, code: `const text = "The future is cool!"; return [text.includes("Flash"), text.includes("FUTURE")]`,
expected: [false, false], expected: [false, false], labels: ["Flash if not included", "includes is case sensitive"],
labels: ["Flash if not included", "includes is case sensitive"],
}, },
{ {
path: "test/built-ins/String/prototype/includes/return-false-with-out-of-bounds-position.js", path: "test/built-ins/String/prototype/includes/return-false-with-out-of-bounds-position.js",
@@ -533,21 +512,15 @@ const cases = [
]`, ]`,
expected: [false, false, false, false], expected: [false, false, false, false],
labels: [ labels: [
'str.includes("!", str.length + 1) returns false', 'str.includes("!", str.length + 1) returns false', 'str.includes("!", 100) returns false',
'str.includes("!", 100) returns false', 'str.includes("!", Infinity) returns false', 'str.includes("!", str.length) returns false',
'str.includes("!", Infinity) returns false',
'str.includes("!", str.length) returns false',
], ],
}, },
{ {
path: "test/built-ins/String/prototype/includes/return-true-if-searchstring-is-empty.js", path: "test/built-ins/String/prototype/includes/return-true-if-searchstring-is-empty.js",
code: `const text = "The future is cool!"; return [text.includes("", text.length), text.includes(""), text.includes("", Infinity)]`, code: `const text = "The future is cool!"; return [text.includes("", text.length), text.includes(""), text.includes("", Infinity)]`,
expected: [true, true, true], expected: [true, true, true],
labels: [ labels: ['str.includes("", str.length) returns true', 'str.includes("") returns true', 'str.includes("", Infinity) returns true'],
'str.includes("", str.length) returns true',
'str.includes("") returns true',
'str.includes("", Infinity) returns true',
],
}, },
{ {
path: "test/built-ins/String/prototype/includes/coerced-values-of-position.js", path: "test/built-ins/String/prototype/includes/coerced-values-of-position.js",
@@ -563,8 +536,7 @@ const cases = [
code: `const text = "The future is cool!"; return [text.startsWith("The future", 0), text.startsWith("future", 4), text.startsWith(" is cool!", 10)]`, code: `const text = "The future is cool!"; return [text.startsWith("The future", 0), text.startsWith("future", 4), text.startsWith(" is cool!", 10)]`,
expected: [true, true, true], expected: [true, true, true],
labels: [ labels: [
'str.startsWith("The future", 0) === true', 'str.startsWith("The future", 0) === true', 'str.startsWith("future", 4) === true',
'str.startsWith("future", 4) === true',
'str.startsWith(" is cool!", 10) === true', 'str.startsWith(" is cool!", 10) === true',
], ],
}, },
@@ -572,11 +544,7 @@ const cases = [
path: "test/built-ins/String/prototype/startsWith/searchstring-found-without-position.js", path: "test/built-ins/String/prototype/startsWith/searchstring-found-without-position.js",
code: `const text = "The future is cool!"; return [text.startsWith("The "), text.startsWith("The future"), text.startsWith(text)]`, code: `const text = "The future is cool!"; return [text.startsWith("The "), text.startsWith("The future"), text.startsWith(text)]`,
expected: [true, true, true], expected: [true, true, true],
labels: [ labels: ['str.startsWith("The ") === true', 'str.startsWith("The future") === true', "str.startsWith(str) === true"],
'str.startsWith("The ") === true',
'str.startsWith("The future") === true',
"str.startsWith(str) === true",
],
}, },
{ {
path: "test/built-ins/String/prototype/startsWith/searchstring-not-found-with-position.js", path: "test/built-ins/String/prototype/startsWith/searchstring-not-found-with-position.js",
@@ -588,11 +556,7 @@ const cases = [
path: "test/built-ins/String/prototype/startsWith/searchstring-not-found-without-position.js", path: "test/built-ins/String/prototype/startsWith/searchstring-not-found-without-position.js",
code: `const text = "The future is cool!"; return [text.startsWith("Flash"), text.startsWith("THE FUTURE"), text.startsWith("future is cool!")]`, code: `const text = "The future is cool!"; return [text.startsWith("Flash"), text.startsWith("THE FUTURE"), text.startsWith("future is cool!")]`,
expected: [false, false, false], expected: [false, false, false],
labels: [ labels: ['str.startsWith("Flash") === false', "startsWith is case sensitive", 'str.startsWith("future is cool!") === false'],
'str.startsWith("Flash") === false',
"startsWith is case sensitive",
'str.startsWith("future is cool!") === false',
],
}, },
{ {
path: "test/built-ins/String/prototype/startsWith/out-of-bounds-position.js", path: "test/built-ins/String/prototype/startsWith/out-of-bounds-position.js",
@@ -602,10 +566,8 @@ const cases = [
]`, ]`,
expected: [false, false, false, true, true], expected: [false, false, false, true, true],
labels: [ labels: [
'str.startsWith("!", str.length) returns false', 'str.startsWith("!", str.length) returns false', 'str.startsWith("!", 100) returns false',
'str.startsWith("!", 100) returns false', 'str.startsWith("!", Infinity) returns false', "position argument < 0 will search from the start of the string (-1)",
'str.startsWith("!", Infinity) returns false',
"position argument < 0 will search from the start of the string (-1)",
"position argument < 0 will search from the start of the string (-Infinity)", "position argument < 0 will search from the start of the string (-Infinity)",
], ],
}, },
@@ -613,11 +575,7 @@ const cases = [
path: "test/built-ins/String/prototype/startsWith/return-true-if-searchstring-is-empty.js", path: "test/built-ins/String/prototype/startsWith/return-true-if-searchstring-is-empty.js",
code: `const text = "The future is cool!"; return [text.startsWith(""), text.startsWith("", text.length), text.startsWith("", Infinity)]`, code: `const text = "The future is cool!"; return [text.startsWith(""), text.startsWith("", text.length), text.startsWith("", Infinity)]`,
expected: [true, true, true], expected: [true, true, true],
labels: [ labels: ['str.startsWith("") returns true', 'str.startsWith("", str.length) returns true', 'str.startsWith("", Infinity) returns true'],
'str.startsWith("") returns true',
'str.startsWith("", str.length) returns true',
'str.startsWith("", Infinity) returns true',
],
}, },
{ {
path: "test/built-ins/String/prototype/startsWith/coerced-values-of-position.js", path: "test/built-ins/String/prototype/startsWith/coerced-values-of-position.js",
@@ -630,47 +588,34 @@ const cases = [
}, },
{ {
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success.js", path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success.js",
code: `return ["word".endsWith("d")]`, code: `return ["word".endsWith("d")]`, expected: [true], labels: ['"word".endsWith("d")'],
expected: [true],
labels: ['"word".endsWith("d")'],
}, },
{ {
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_2.js", path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_2.js",
code: `return ["word".endsWith("d", 4)]`, code: `return ["word".endsWith("d", 4)]`, expected: [true], labels: ['"word".endsWith("d", 4)'],
expected: [true],
labels: ['"word".endsWith("d", 4)'],
}, },
{ {
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_3.js", path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_3.js",
code: `return ["word".endsWith("d", 25)]`, code: `return ["word".endsWith("d", 25)]`, expected: [true], labels: ['"word".endsWith("d", 25)'],
expected: [true],
labels: ['"word".endsWith("d", 25)'],
}, },
{ {
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_4.js", path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_4.js",
code: `return ["word".endsWith("r", 3)]`, code: `return ["word".endsWith("r", 3)]`, expected: [true], labels: ['"word".endsWith("r", 3)'],
expected: [true],
labels: ['"word".endsWith("r", 3)'],
}, },
{ {
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail.js", path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail.js",
code: `return ["word".endsWith("r")]`, code: `return ["word".endsWith("r")]`, expected: [false], labels: ['"word".endsWith("r")'],
expected: [false],
labels: ['"word".endsWith("r")'],
}, },
{ {
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail_2.js", path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail_2.js",
code: `return ["word".endsWith("d", 3)]`, code: `return ["word".endsWith("d", 3)]`, expected: [false], labels: ['"word".endsWith("d", 3)'],
expected: [false],
labels: ['"word".endsWith("d", 3)'],
}, },
{ {
path: "test/built-ins/String/prototype/endsWith/searchstring-found-with-position.js", path: "test/built-ins/String/prototype/endsWith/searchstring-found-with-position.js",
code: `const text = "The future is cool!"; return [text.endsWith("The future", 10), text.endsWith("future", 10), text.endsWith(" is cool!", text.length)]`, code: `const text = "The future is cool!"; return [text.endsWith("The future", 10), text.endsWith("future", 10), text.endsWith(" is cool!", text.length)]`,
expected: [true, true, true], expected: [true, true, true],
labels: [ labels: [
'str.endsWith("The future", 10) === true', 'str.endsWith("The future", 10) === true', 'str.endsWith("future", 10) === true',
'str.endsWith("future", 10) === true',
'str.endsWith(" is cool!", str.length) === true', 'str.endsWith(" is cool!", str.length) === true',
], ],
}, },
@@ -690,11 +635,7 @@ const cases = [
path: "test/built-ins/String/prototype/endsWith/searchstring-not-found-without-position.js", path: "test/built-ins/String/prototype/endsWith/searchstring-not-found-without-position.js",
code: `const text = "The future is cool!"; return [text.endsWith("is Flash!"), text.endsWith("IS COOL!"), text.endsWith("The future")]`, code: `const text = "The future is cool!"; return [text.endsWith("is Flash!"), text.endsWith("IS COOL!"), text.endsWith("The future")]`,
expected: [false, false, false], expected: [false, false, false],
labels: [ labels: ['str.endsWith("is Flash!") === false', "endsWith is case sensitive", 'str.endsWith("The future") === false'],
'str.endsWith("is Flash!") === false',
"endsWith is case sensitive",
'str.endsWith("The future") === false',
],
}, },
{ {
path: "test/built-ins/String/prototype/endsWith/return-false-if-search-start-is-less-than-zero.js", path: "test/built-ins/String/prototype/endsWith/return-false-if-search-start-is-less-than-zero.js",
@@ -710,11 +651,8 @@ const cases = [
]`, ]`,
expected: [true, true, true, true, true], expected: [true, true, true, true, true],
labels: [ labels: [
'str.endsWith("") returns true', 'str.endsWith("") returns true', 'str.endsWith("", str.length) returns true', 'str.endsWith("", Infinity) returns true',
'str.endsWith("", str.length) returns true', 'str.endsWith("", -1) returns true', 'str.endsWith("", -Infinity) returns true',
'str.endsWith("", Infinity) returns true',
'str.endsWith("", -1) returns true',
'str.endsWith("", -Infinity) returns true',
], ],
}, },
{ {
@@ -727,39 +665,27 @@ const cases = [
}, },
{ {
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T1.js", path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T1.js",
code: `return ["abcd".indexOf("abcdab")]`, code: `return ["abcd".indexOf("abcdab")]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab")===-1'],
expected: [-1],
labels: ['#1: "abcd".indexOf("abcdab")===-1'],
}, },
{ {
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T2.js", path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T2.js",
code: `return ["abcd".indexOf("abcdab", 0)]`, code: `return ["abcd".indexOf("abcdab", 0)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",0)===-1'],
expected: [-1],
labels: ['#1: "abcd".indexOf("abcdab",0)===-1'],
}, },
{ {
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T3.js", path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T3.js",
code: `return ["abcd".indexOf("abcdab", 99)]`, code: `return ["abcd".indexOf("abcdab", 99)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",99)===-1'],
expected: [-1],
labels: ['#1: "abcd".indexOf("abcdab",99)===-1'],
}, },
{ {
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T4.js", path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T4.js",
code: `return ["abcd".indexOf("abcdab", NaN)]`, code: `return ["abcd".indexOf("abcdab", NaN)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",NaN)===-1'],
expected: [-1],
labels: ['#1: "abcd".indexOf("abcdab",NaN)===-1'],
}, },
{ {
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T1.js", path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T1.js",
code: `return ["$$abcdabcd".indexOf("ab", NaN)]`, code: `return ["$$abcdabcd".indexOf("ab", NaN)]`, expected: [2], labels: ['#1: "$$abcdabcd".indexOf("ab",NaN)===2'],
expected: [2],
labels: ['#1: "$$abcdabcd".indexOf("ab",NaN)===2'],
}, },
{ {
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T3.js", path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T3.js",
code: `return ["$$abcdabcd".indexOf("ab", -Infinity)]`, code: `return ["$$abcdabcd".indexOf("ab", -Infinity)]`, expected: [2], labels: ['#1: "$$abcdabcd".indexOf("ab", function(){return -Infinity;}())===2'],
expected: [2],
labels: ['#1: "$$abcdabcd".indexOf("ab", function(){return -Infinity;}())===2'],
}, },
{ {
path: "test/built-ins/String/prototype/indexOf/position-tointeger.js", path: "test/built-ins/String/prototype/indexOf/position-tointeger.js",
@@ -771,31 +697,21 @@ const cases = [
]`, ]`,
expected: [0, 1, 0, 0, 1, 0, -1, 0, 2, 2], expected: [0, 1, 0, 0, 1, 0, -1, 0, 2, 2],
labels: [ labels: [
"position 0", "position 0", "position 1", "ToInteger: truncate towards 0 (-0.9)", "ToInteger: truncate towards 0 (0.9)",
"position 1", "ToInteger: truncate towards 0 (1.9)", "ToInteger: NaN => 0", "position Infinity",
"ToInteger: truncate towards 0 (-0.9)", "ToInteger: undefined => NaN => 0", "position 2", "ToInteger: truncate towards 0 (2.9)",
"ToInteger: truncate towards 0 (0.9)",
"ToInteger: truncate towards 0 (1.9)",
"ToInteger: NaN => 0",
"position Infinity",
"ToInteger: undefined => NaN => 0",
"position 2",
"ToInteger: truncate towards 0 (2.9)",
], ],
}, },
{ {
path: "test/built-ins/String/prototype/indexOf/searchstring-tostring.js", path: "test/built-ins/String/prototype/indexOf/searchstring-tostring.js",
code: `return ["foo".indexOf(""), "__foo__".indexOf("foo")]`, code: `return ["foo".indexOf(""), "__foo__".indexOf("foo")]`,
expected: [0, 2], expected: [0, 2], labels: ['"foo".indexOf("")', '"__foo__".indexOf("foo")'],
labels: ['"foo".indexOf("")', '"__foo__".indexOf("foo")'],
}, },
{ {
path: "test/built-ins/String/prototype/lastIndexOf/not-a-substring.js", path: "test/built-ins/String/prototype/lastIndexOf/not-a-substring.js",
code: `return ["abc".lastIndexOf("d")]`, code: `return ["abc".lastIndexOf("d")]`,
expected: [-1], expected: [-1],
labels: [ labels: ["String.prototype.lastIndexOf returns -1 when searchString is shorter than this and searchString is not a substring of this."],
"String.prototype.lastIndexOf returns -1 when searchString is shorter than this and searchString is not a substring of this.",
],
}, },
] as const ] as const
@@ -1,21 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { Workspace } from "@opencode-ai/console-core/workspace.js"
import { safeEqual } from "@opencode-ai/console-core/util/crypto.js"
import { Resource } from "@opencode-ai/console-resource"
import z from "zod"
const Body = z.object({ workspaceID: z.string().startsWith("wrk_") })
export async function POST(event: APIEvent) {
if (!safeEqual(event.request.headers.get("authorization") ?? "", `Bearer ${Resource.SUPPORT_API_KEY.value}`)) {
return Response.json({ error: "Unauthorized" }, { status: 401 })
}
const body = Body.safeParse(await event.request.json().catch(() => undefined))
if (!body.success) {
return Response.json({ error: "Invalid request", issues: body.error.issues }, { status: 400 })
}
return Workspace.unblock(body.data.workspaceID)
.then(() => Response.json({ success: true, message: "Workspace unblocked" }))
.catch((error) => Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 400 }))
}
-12
View File
@@ -103,16 +103,4 @@ export namespace Workspace {
.where(eq(WorkspaceTable.id, Actor.workspace())), .where(eq(WorkspaceTable.id, Actor.workspace())),
) )
}) })
export const unblock = fn(z.string().startsWith("wrk_"), async (workspaceID) => {
await Database.transaction(async (tx) => {
const workspace = await tx
.select({ id: WorkspaceTable.id })
.from(WorkspaceTable)
.where(eq(WorkspaceTable.id, workspaceID))
.then((rows) => rows[0])
if (!workspace) throw new Error("Workspace not found")
await tx.update(WorkspaceTable).set({ is_blocked: false }).where(eq(WorkspaceTable.id, workspaceID))
})
})
} }
-1
View File
@@ -18,7 +18,6 @@
}, },
"exports": { "exports": {
"./environment": "./src/environment/index.ts", "./environment": "./src/environment/index.ts",
"./testing/environment-conformance": "./test/lib/environment-conformance.ts",
"./session/runner": "./src/session/runner/index.ts", "./session/runner": "./src/session/runner/index.ts",
"./instructions": "./src/instructions/index.ts", "./instructions": "./src/instructions/index.ts",
"./*": "./src/*.ts" "./*": "./src/*.ts"
+212 -92
View File
@@ -1,9 +1,15 @@
{ {
"version": "7", "version": "7",
"dialect": "sqlite", "dialect": "sqlite",
"id": "15060ec5-05f7-4b86-b2a5-9108609432b3", "id": "2d214a71-3b0a-48c1-a667-741952c4e188",
"prevIds": ["1551a157-8959-4ba9-a52b-4ea3b7b28cae"], "prevIds": [
"f14a9b18-8207-487e-a3d3-227e629ba9ad"
],
"ddl": [ "ddl": [
{
"name": "workspace",
"entityType": "tables"
},
{ {
"name": "account_state", "name": "account_state",
"entityType": "tables" "entityType": "tables"
@@ -69,8 +75,84 @@
"entityType": "tables" "entityType": "tables"
}, },
{ {
"name": "workspace", "type": "text",
"entityType": "tables" "notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "type",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": "''",
"generated": null,
"name": "name",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "branch",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "directory",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "extra",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "project_id",
"entityType": "columns",
"table": "workspace"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_used",
"entityType": "columns",
"table": "workspace"
}, },
{ {
"type": "integer", "type": "integer",
@@ -1303,59 +1385,28 @@
"table": "session_v2" "table": "session_v2"
}, },
{ {
"type": "text", "columns": [
"notNull": false, "project_id"
"autoincrement": false, ],
"default": null, "tableTo": "project",
"generated": null, "columnsTo": [
"name": "id", "id"
"entityType": "columns", ],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_workspace_project_id_project_id_fk",
"entityType": "fks",
"table": "workspace" "table": "workspace"
}, },
{ {
"type": "text", "columns": [
"notNull": true, "active_account_id"
"autoincrement": false, ],
"default": null,
"generated": null,
"name": "provider",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "binding",
"entityType": "columns",
"table": "workspace"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "created_at",
"entityType": "columns",
"table": "workspace"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "last_used_at",
"entityType": "columns",
"table": "workspace"
},
{
"columns": ["active_account_id"],
"tableTo": "account", "tableTo": "account",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "SET NULL", "onDelete": "SET NULL",
"nameExplicit": false, "nameExplicit": false,
@@ -1364,9 +1415,13 @@
"table": "account_state" "table": "account_state"
}, },
{ {
"columns": ["aggregate_id"], "columns": [
"aggregate_id"
],
"tableTo": "event_sequence", "tableTo": "event_sequence",
"columnsTo": ["aggregate_id"], "columnsTo": [
"aggregate_id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@@ -1375,9 +1430,13 @@
"table": "event" "table": "event"
}, },
{ {
"columns": ["project_id"], "columns": [
"project_id"
],
"tableTo": "project", "tableTo": "project",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@@ -1386,9 +1445,13 @@
"table": "permission" "table": "permission"
}, },
{ {
"columns": ["project_id"], "columns": [
"project_id"
],
"tableTo": "project", "tableTo": "project",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@@ -1397,9 +1460,13 @@
"table": "project_directory" "table": "project_directory"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session_v2", "tableTo": "session_v2",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@@ -1408,9 +1475,13 @@
"table": "instruction_entry" "table": "instruction_entry"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session_v2", "tableTo": "session_v2",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@@ -1419,9 +1490,13 @@
"table": "instruction_state" "table": "instruction_state"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session_v2", "tableTo": "session_v2",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@@ -1430,9 +1505,13 @@
"table": "session_message" "table": "session_message"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"tableTo": "session_v2", "tableTo": "session_v2",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@@ -1441,9 +1520,13 @@
"table": "session_pending" "table": "session_pending"
}, },
{ {
"columns": ["project_id"], "columns": [
"project_id"
],
"tableTo": "project", "tableTo": "project",
"columnsTo": ["id"], "columnsTo": [
"id"
],
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"onDelete": "CASCADE", "onDelete": "CASCADE",
"nameExplicit": false, "nameExplicit": false,
@@ -1452,124 +1535,161 @@
"table": "session_v2" "table": "session_v2"
}, },
{ {
"columns": ["email", "url"], "columns": [
"email",
"url"
],
"nameExplicit": false, "nameExplicit": false,
"name": "control_account_pk", "name": "control_account_pk",
"entityType": "pks", "entityType": "pks",
"table": "control_account" "table": "control_account"
}, },
{ {
"columns": ["project_id", "directory"], "columns": [
"project_id",
"directory"
],
"nameExplicit": false, "nameExplicit": false,
"name": "project_directory_pk", "name": "project_directory_pk",
"entityType": "pks", "entityType": "pks",
"table": "project_directory" "table": "project_directory"
}, },
{ {
"columns": ["session_id", "key"], "columns": [
"session_id",
"key"
],
"nameExplicit": false, "nameExplicit": false,
"name": "instruction_entry_pk", "name": "instruction_entry_pk",
"entityType": "pks", "entityType": "pks",
"table": "instruction_entry" "table": "instruction_entry"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false,
"name": "workspace_pk",
"table": "workspace",
"entityType": "pks"
},
{
"columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "account_state_pk", "name": "account_state_pk",
"table": "account_state", "table": "account_state",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "account_pk", "name": "account_pk",
"table": "account", "table": "account",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "credential_pk", "name": "credential_pk",
"table": "credential", "table": "credential",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["aggregate_id"], "columns": [
"aggregate_id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "event_sequence_pk", "name": "event_sequence_pk",
"table": "event_sequence", "table": "event_sequence",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "event_pk", "name": "event_pk",
"table": "event", "table": "event",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["key"], "columns": [
"key"
],
"nameExplicit": false, "nameExplicit": false,
"name": "kv_pk", "name": "kv_pk",
"table": "kv", "table": "kv",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "permission_pk", "name": "permission_pk",
"table": "permission", "table": "permission",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "project_pk", "name": "project_pk",
"table": "project", "table": "project",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["hash"], "columns": [
"hash"
],
"nameExplicit": false, "nameExplicit": false,
"name": "instruction_blob_pk", "name": "instruction_blob_pk",
"table": "instruction_blob", "table": "instruction_blob",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["session_id"], "columns": [
"session_id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "instruction_state_pk", "name": "instruction_state_pk",
"table": "instruction_state", "table": "instruction_state",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "session_message_pk", "name": "session_message_pk",
"table": "session_message", "table": "session_message",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "session_pending_pk", "name": "session_pending_pk",
"table": "session_pending", "table": "session_pending",
"entityType": "pks" "entityType": "pks"
}, },
{ {
"columns": ["id"], "columns": [
"id"
],
"nameExplicit": false, "nameExplicit": false,
"name": "session_v2_pk", "name": "session_v2_pk",
"table": "session_v2", "table": "session_v2",
"entityType": "pks" "entityType": "pks"
}, },
{
"columns": ["id"],
"nameExplicit": false,
"name": "workspace_pk",
"table": "workspace",
"entityType": "pks"
},
{ {
"columns": [ "columns": [
{ {
+3 -1
View File
@@ -20,7 +20,9 @@ if (!Number.isInteger(iterations) || iterations < 1) {
} }
const ref = Location.Ref.make({ directory: AbsolutePath.make(path.resolve(directory)) }) const ref = Location.Ref.make({ directory: AbsolutePath.make(path.resolve(directory)) })
const layer = AppNodeBuilder.build(LayerNode.group([Database.node, Bus.node, SdkPlugins.node, LocationServiceMap.node])) const layer = AppNodeBuilder.build(
LayerNode.group([Database.node, Bus.node, SdkPlugins.node, LocationServiceMap.node]),
)
const measure = <A, E, R>(effect: Effect.Effect<A, E, R>) => const measure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
Effect.gen(function* () { Effect.gen(function* () {
+101
View File
@@ -0,0 +1,101 @@
export * as Account from "./account"
import { Schema } from "effect"
import type { HttpClientError } from "effect/unstable/http"
export const ID = Schema.String.pipe(Schema.brand("AccountID"))
export type ID = Schema.Schema.Type<typeof ID>
export const OrgID = Schema.String.pipe(Schema.brand("OrgID"))
export type OrgID = Schema.Schema.Type<typeof OrgID>
export const AccessToken = Schema.String.pipe(Schema.brand("AccessToken"))
export type AccessToken = Schema.Schema.Type<typeof AccessToken>
export const RefreshToken = Schema.String.pipe(Schema.brand("RefreshToken"))
export type RefreshToken = Schema.Schema.Type<typeof RefreshToken>
export const DeviceCode = Schema.String.pipe(Schema.brand("DeviceCode"))
export type DeviceCode = Schema.Schema.Type<typeof DeviceCode>
export const UserCode = Schema.String.pipe(Schema.brand("UserCode"))
export type UserCode = Schema.Schema.Type<typeof UserCode>
export class Info extends Schema.Class<Info>("Account")({
id: ID,
email: Schema.String,
url: Schema.String,
active_org_id: Schema.NullOr(OrgID),
}) {}
export class Org extends Schema.Class<Org>("Org")({
id: OrgID,
name: Schema.String,
}) {}
export class AccountRepoError extends Schema.TaggedErrorClass<AccountRepoError>()("AccountRepoError", {
message: Schema.String,
cause: Schema.optional(Schema.Defect()),
}) {}
export class AccountServiceError extends Schema.TaggedErrorClass<AccountServiceError>()("AccountServiceError", {
message: Schema.String,
cause: Schema.optional(Schema.Defect()),
}) {}
export class AccountTransportError extends Schema.TaggedErrorClass<AccountTransportError>()("AccountTransportError", {
method: Schema.String,
url: Schema.String,
description: Schema.optional(Schema.String),
cause: Schema.optional(Schema.Defect()),
}) {
static fromHttpClientError(error: HttpClientError.TransportError): AccountTransportError {
return new AccountTransportError({
method: error.request.method,
url: error.request.url,
description: error.description,
cause: error.cause,
})
}
override get message(): string {
return [
`Could not reach ${this.method} ${this.url}.`,
`This failed before the server returned an HTTP response.`,
this.description,
`Check your network, proxy, or VPN configuration and try again.`,
]
.filter(Boolean)
.join("\n")
}
}
export type AccountError = AccountRepoError | AccountServiceError | AccountTransportError
export class Login extends Schema.Class<Login>("Login")({
code: DeviceCode,
user: UserCode,
url: Schema.String,
server: Schema.String,
expiry: Schema.Duration,
interval: Schema.Duration,
}) {}
export class PollSuccess extends Schema.TaggedClass<PollSuccess>()("PollSuccess", {
email: Schema.String,
}) {}
export class PollPending extends Schema.TaggedClass<PollPending>()("PollPending", {}) {}
export class PollSlow extends Schema.TaggedClass<PollSlow>()("PollSlow", {}) {}
export class PollExpired extends Schema.TaggedClass<PollExpired>()("PollExpired", {}) {}
export class PollDenied extends Schema.TaggedClass<PollDenied>()("PollDenied", {}) {}
export class PollError extends Schema.TaggedClass<PollError>()("PollError", {
cause: Schema.Defect(),
}) {}
export const PollResult = Schema.Union([PollSuccess, PollPending, PollSlow, PollExpired, PollDenied, PollError])
export type PollResult = Schema.Schema.Type<typeof PollResult>
+10 -7
View File
@@ -1,21 +1,24 @@
import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core" import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core"
import { Account } from "../account"
import { Timestamps } from "../database/schema.sql" import { Timestamps } from "../database/schema.sql"
export const AccountTable = sqliteTable("account", { export const AccountTable = sqliteTable("account", {
id: text().primaryKey(), id: text().$type<Account.ID>().primaryKey(),
email: text().notNull(), email: text().notNull(),
url: text().notNull(), url: text().notNull(),
access_token: text().notNull(), access_token: text().$type<Account.AccessToken>().notNull(),
refresh_token: text().notNull(), refresh_token: text().$type<Account.RefreshToken>().notNull(),
token_expiry: integer(), token_expiry: integer(),
...Timestamps, ...Timestamps,
}) })
export const AccountStateTable = sqliteTable("account_state", { export const AccountStateTable = sqliteTable("account_state", {
id: integer().primaryKey(), id: integer().primaryKey(),
active_account_id: text().references(() => AccountTable.id, { onDelete: "set null" }), active_account_id: text()
active_org_id: text(), .$type<Account.ID>()
.references(() => AccountTable.id, { onDelete: "set null" }),
active_org_id: text().$type<Account.OrgID>(),
}) })
// LEGACY // LEGACY
@@ -24,8 +27,8 @@ export const ControlAccountTable = sqliteTable(
{ {
email: text().notNull(), email: text().notNull(),
url: text().notNull(), url: text().notNull(),
access_token: text().notNull(), access_token: text().$type<Account.AccessToken>().notNull(),
refresh_token: text().notNull(), refresh_token: text().$type<Account.RefreshToken>().notNull(),
token_expiry: integer(), token_expiry: integer(),
active: integer({ mode: "boolean" }) active: integer({ mode: "boolean" })
.notNull() .notNull()
+7 -18
View File
@@ -110,13 +110,10 @@ export interface Subscribe {
*/ */
(): Stream.Stream<Event.Payload> (): Stream.Stream<Event.Payload>
<D extends Event.Definition>(definition: D): Stream.Stream<Event.Payload<D>> <D extends Event.Definition>(definition: D): Stream.Stream<Event.Payload<D>>
<const D extends readonly [Event.Definition, ...Event.Definition[]]>( <const D extends readonly [Event.Definition, ...Event.Definition[]]>(definitions: D): Stream.Stream<SubscribePayload<D>>
definitions: D,
): Stream.Stream<SubscribePayload<D>>
} }
const isDefinition = (input: Event.Definition | readonly Event.Definition[]): input is Event.Definition => const isDefinition = (input: Event.Definition | readonly Event.Definition[]): input is Event.Definition => !Array.isArray(input)
!Array.isArray(input)
export interface Interface { export interface Interface {
readonly publish: <D extends Event.Definition>( readonly publish: <D extends Event.Definition>(
@@ -152,6 +149,7 @@ export interface Interface {
readonly claim: (aggregateID: string, ownerID: string) => Effect.Effect<void> readonly claim: (aggregateID: string, ownerID: string) => Effect.Effect<void>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/Bus") {} export class Service extends Context.Service<Service, Interface>()("@opencode/Bus") {}
interface Options { interface Options {
@@ -166,9 +164,7 @@ export function configured(options?: Options) {
return makeGlobalNode({ return makeGlobalNode({
service: Service, service: Service,
deps: [Database.node], deps: [Database.node],
layer: Layer.effect( layer: Layer.effect(Service, Effect.gen(function* () {
Service,
Effect.gen(function* () {
const pubsub = { const pubsub = {
live: yield* PubSub.unbounded<Event.Payload>(), live: yield* PubSub.unbounded<Event.Payload>(),
durable: new Map<string, Set<PubSub.PubSub<void>>>(), durable: new Map<string, Set<PubSub.PubSub<void>>>(),
@@ -371,11 +367,7 @@ export function configured(options?: Options) {
}) })
} }
function publishEvent<D extends Event.Definition>( function publishEvent<D extends Event.Definition>(definition: D, event: Event.Payload<D>, commit?: PublishOptions["commit"]) {
definition: D,
event: Event.Payload<D>,
commit?: PublishOptions["commit"],
) {
return Effect.gen(function* () { return Effect.gen(function* () {
if (!definition?.durable && commit) if (!definition?.durable && commit)
return yield* Effect.die( return yield* Effect.die(
@@ -682,9 +674,7 @@ export function configured(options?: Options) {
const project = <D extends Event.Definition>(definition: D, projector: Subscriber<D>): Effect.Effect<void> => const project = <D extends Event.Definition>(definition: D, projector: Subscriber<D>): Effect.Effect<void> =>
Effect.sync(() => { Effect.sync(() => {
const key = definition.durable const key = definition.durable ? versionedType(definition.type, definition.durable.version) : definition.type
? versionedType(definition.type, definition.durable.version)
: definition.type
const list = projectors.get(key) ?? [] const list = projectors.get(key) ?? []
list.push((event) => projector(event as Event.Payload<D>)) list.push((event) => projector(event as Event.Payload<D>))
projectors.set(key, list) projectors.set(key, list)
@@ -701,8 +691,7 @@ export function configured(options?: Options) {
remove, remove,
claim, claim,
}) })
}), })),
),
}) })
} }
+4 -2
View File
@@ -115,7 +115,8 @@ const layer = Layer.effect(
} }
draft.providers.set(providerID, record) draft.providers.set(providerID, record)
} }
const model = record.models.get(modelID) ?? (Model.Info.default(providerID, modelID) as Model.MutableInfo) const model =
record.models.get(modelID) ?? (Model.Info.default(providerID, modelID) as Model.MutableInfo)
if (!record.models.has(modelID)) record.models.set(modelID, model) if (!record.models.has(modelID)) record.models.set(modelID, model)
fn(model) fn(model)
model.id = modelID model.id = modelID
@@ -248,7 +249,8 @@ const layer = Layer.effect(
items, items,
Order.mapInput( Order.mapInput(
Order.Number, Order.Number,
(item: (typeof candidates)[number]) => (item.cost / maxCost) * 0.8 + (item.age / maxAge) * 0.2, (item: (typeof candidates)[number]) =>
(item.cost / maxCost) * 0.8 + (item.age / maxAge) * 0.2,
), ),
) )
return projectModel(selected.model, provider) return projectModel(selected.model, provider)
+3 -1
View File
@@ -46,7 +46,9 @@ export function summarize(entries: ReadonlyArray<Entry>, budget = INLINE_BUDGET)
.map((entry) => { .map((entry) => {
const firstLine = entry.description.split("\n", 1)[0]?.trim() ?? "" const firstLine = entry.description.split("\n", 1)[0]?.trim() ?? ""
const description = const description =
firstLine.length > DESCRIPTION_LIMIT ? firstLine.slice(0, DESCRIPTION_LIMIT - 3) + "..." : firstLine firstLine.length > DESCRIPTION_LIMIT
? firstLine.slice(0, DESCRIPTION_LIMIT - 3) + "..."
: firstLine
const suffix = description.length === 0 ? "" : ` // ${description}` const suffix = description.length === 0 ? "" : ` // ${description}`
return { path: entry.path, line: ` - ${entry.signature}${suffix}` } return { path: entry.path, line: ` - ${entry.signature}${suffix}` }
}) })
+9 -4
View File
@@ -43,9 +43,14 @@ const description = [
export const create = ( export const create = (
registrations: ReadonlyMap<string, Info>, registrations: ReadonlyMap<string, Info>,
executeTool: (name: string, tool: Info, input: unknown, context: Context) => Effect.Effect<Result, Error>, executeTool: (
name: string,
tool: Info,
input: unknown,
context: Context,
) => Effect.Effect<Result, Error>,
) => { ) => {
return { return ({
name: "execute", name: "execute",
description, description,
input: CodeMode.Input, input: CodeMode.Input,
@@ -71,7 +76,7 @@ export const create = (
const content = const content =
typeof executed.content === "string" typeof executed.content === "string"
? [{ type: "text" as const, text: executed.content }] ? [{ type: "text" as const, text: executed.content }]
: (executed.content ?? []) : executed.content ?? []
const outputFileParts = outputFiles(content) const outputFileParts = outputFiles(content)
if (outputFileParts.length > 0) if (outputFileParts.length > 0)
yield* Ref.update(files, (items) => [...items, { index, files: outputFileParts }]) yield* Ref.update(files, (items) => [...items, { index, files: outputFileParts }])
@@ -129,7 +134,7 @@ export const create = (
metadata, metadata,
} }
}), }),
} satisfies Info }) satisfies Info
} }
export const catalog = (registrations: ReadonlyMap<string, Info>) => { export const catalog = (registrations: ReadonlyMap<string, Info>) => {
+16 -31
View File
@@ -52,8 +52,7 @@ export interface Interface extends State.Transformable<Draft> {
export class Service extends Context.Service<Service, Interface>()("@opencode/Command") {} export class Service extends Context.Service<Service, Interface>()("@opencode/Command") {}
export const layer = (options?: ShellSelect.Options) => export const layer = (options?: ShellSelect.Options) => Layer.effect(
Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const mcp = yield* MCP.Service const mcp = yield* MCP.Service
@@ -101,23 +100,22 @@ export const layer = (options?: ShellSelect.Options) =>
list: Effect.fn("Command.list")(function* () { list: Effect.fn("Command.list")(function* () {
const commands = Array.from(state.get().commands.values()) as Info[] const commands = Array.from(state.get().commands.values()) as Info[]
const names = new Set(commands.map((command) => command.name)) const names = new Set(commands.map((command) => command.name))
return [...commands, ...(yield* mcpCommands()).filter((command) => !names.has(command.name))] return [
...commands,
...(yield* mcpCommands()).filter((command) => !names.has(command.name)),
]
}), }),
evaluate: Effect.fn("Command.evaluate")(function* (input) { evaluate: Effect.fn("Command.evaluate")(function* (input) {
const command = staticCommand(input.name) const command = staticCommand(input.name)
if (command) if (command) return yield* evaluateTemplate(input.name, command.template, input.arguments ?? "", {
return yield* evaluateTemplate(input.name, command.template, input.arguments ?? "", {
config, config,
location, location,
processes, processes,
shell: options, shell: options,
}) })
const prompt = (yield* mcp.prompts()).find( const prompt = (yield* mcp.prompts()).find((prompt) => mcpCommandName(prompt.server, prompt.name) === input.name)
(prompt) => mcpCommandName(prompt.server, prompt.name) === input.name, if (!prompt) return yield* new NotFoundError({ command: input.name, message: `Command not found: ${input.name}` })
)
if (!prompt)
return yield* new NotFoundError({ command: input.name, message: `Command not found: ${input.name}` })
const result = yield* mcp const result = yield* mcp
.prompt({ .prompt({
server: prompt.server, server: prompt.server,
@@ -130,7 +128,9 @@ export const layer = (options?: ShellSelect.Options) =>
), ),
}) })
.pipe( .pipe(
Effect.catchTag("MCP.NotFoundError", () => Effect.catchTag(
"MCP.NotFoundError",
() =>
Effect.fail( Effect.fail(
new EvaluationError({ new EvaluationError({
command: input.name, command: input.name,
@@ -144,12 +144,7 @@ export const layer = (options?: ShellSelect.Options) =>
command: input.name, command: input.name,
message: `MCP prompt could not be evaluated: ${prompt.server}:${prompt.name}`, message: `MCP prompt could not be evaluated: ${prompt.server}:${prompt.name}`,
}) })
return { return { text: result.messages.map((message) => promptMessageText(message.content)).join("\n").trim() }
text: result.messages
.map((message) => promptMessageText(message.content))
.join("\n")
.trim(),
}
}), }),
}) })
}), }),
@@ -184,8 +179,7 @@ function evaluateArguments(template: string, input: string) {
return args[argIndex] return args[argIndex]
}) })
const withArguments = expanded.replaceAll("$ARGUMENTS", input) const withArguments = expanded.replaceAll("$ARGUMENTS", input)
if (placeholders.length === 0 && !template.includes("$ARGUMENTS") && input.trim()) if (placeholders.length === 0 && !template.includes("$ARGUMENTS") && input.trim()) return `${withArguments}\n\n${input}`.trim()
return `${withArguments}\n\n${input}`.trim()
return withArguments.trim() return withArguments.trim()
} }
@@ -207,23 +201,14 @@ const evaluateShell = Effect.fnUntraced(function* (
(match) => { (match) => {
const source = match[1] ?? "" const source = match[1] ?? ""
return services.processes return services.processes
.run( .run(ChildProcess.make(shell, ShellSelect.args(shell, source), { cwd: services.location.directory, stdin: "ignore" }), {
ChildProcess.make(shell, ShellSelect.args(shell, source), {
cwd: services.location.directory,
stdin: "ignore",
}),
{
combineOutput: true, combineOutput: true,
}, })
)
.pipe( .pipe(
Effect.map((result) => (result.output ?? Buffer.concat([result.stdout, result.stderr])).toString("utf8")), Effect.map((result) => (result.output ?? Buffer.concat([result.stdout, result.stderr])).toString("utf8")),
Effect.mapError( Effect.mapError(
(error) => (error) =>
new EvaluationError({ new EvaluationError({ command, message: `Shell interpolation failed for ${JSON.stringify(source)}: ${error.message}` }),
command,
message: `Shell interpolation failed for ${JSON.stringify(source)}: ${error.message}`,
}),
), ),
) )
}, },
+4 -9
View File
@@ -78,8 +78,7 @@ export const testLayer = (initial: Entry[] = []) =>
}), }),
) )
export const layer = (options?: Options) => export const layer = (options?: Options) => Layer.effect(
Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const fs = yield* FSUtil.Service const fs = yield* FSUtil.Service
@@ -182,8 +181,7 @@ export const layer = (options?: Options) =>
const globalAgentsDirectory = AbsolutePath.make(path.join(global.home, ".agents")) const globalAgentsDirectory = AbsolutePath.make(path.join(global.home, ".agents"))
const globalClaudeDirectory = AbsolutePath.make(path.join(global.home, ".claude")) const globalClaudeDirectory = AbsolutePath.make(path.join(global.home, ".claude"))
const locationIsGlobal = path.resolve(location.directory) === path.resolve(global.config) const locationIsGlobal = path.resolve(location.directory) === path.resolve(global.config)
const discovered = const discovered = locationIsGlobal || options?.project === false
locationIsGlobal || options?.project === false
? [] ? []
: yield* fs : yield* fs
.up({ .up({
@@ -238,8 +236,7 @@ export const layer = (options?: Options) =>
Effect.orDie, Effect.orDie,
) )
: [] : []
const content = const content = options?.content !== undefined
options?.content !== undefined
? yield* ConfigVariable.substitute({ ? yield* ConfigVariable.substitute({
type: "virtual", type: "virtual",
source: "OPENCODE_CONFIG_CONTENT", source: "OPENCODE_CONFIG_CONTENT",
@@ -340,9 +337,7 @@ export const layer = (options?: Options) =>
Effect.suspend(() => { Effect.suspend(() => {
if (!wellknown.snapshot().length) return Effect.void if (!wellknown.snapshot().length) return Effect.void
return Effect.gen(function* () { return Effect.gen(function* () {
const changed = yield* wellknown const changed = yield* wellknown.refresh().pipe(
.refresh()
.pipe(
Effect.catch((error) => Effect.catch((error) =>
Effect.logWarning("failed to refresh wellknown manifests", { error }).pipe(Effect.as(false)), Effect.logWarning("failed to refresh wellknown manifests", { error }).pipe(Effect.as(false)),
), ),

Some files were not shown because too many files have changed in this diff Show More