mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-09 02:49:57 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ebc6d2146 |
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- v2
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
|
||||
@@ -251,3 +251,4 @@ opencode-drive stop --name demo
|
||||
```bash
|
||||
opencode-drive dir --name demo
|
||||
```
|
||||
|
||||
|
||||
@@ -571,20 +571,6 @@
|
||||
"@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": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.18.8",
|
||||
@@ -693,7 +679,6 @@
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"modal": "0.9.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "catalog:",
|
||||
@@ -896,7 +881,6 @@
|
||||
"dependencies": {
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/merman": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/schema": "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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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/merman": ["@opencode-ai/merman@workspace:packages/merman"],
|
||||
|
||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"],
|
||||
|
||||
"@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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
|
||||
|
||||
"lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-error": ["ts-error@1.0.6", "", {}, "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA=="],
|
||||
|
||||
"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=="],
|
||||
@@ -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=="],
|
||||
|
||||
"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/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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -133,8 +133,7 @@ const countHints = (request: LLMRequest) =>
|
||||
|
||||
export const applyCachePolicy = (request: LLMRequest): LLMRequest => {
|
||||
if (!RESPECTS_INLINE_HINTS.has(request.model.route.id)) return request
|
||||
if (request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto"))
|
||||
return request
|
||||
if (request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto")) return request
|
||||
const policy = resolve(request.cache)
|
||||
if (!policy.tools && !policy.system && !policy.messages) return request
|
||||
|
||||
|
||||
@@ -371,13 +371,12 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
|
||||
return text
|
||||
})()
|
||||
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 = {
|
||||
role: "assistant" as const,
|
||||
content: content.length > 0 ? content.map((part) => part.text).join("") : toolCalls.length > 0 ? null : "",
|
||||
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
||||
...(details !== undefined ? { reasoning_details: details } : {}),
|
||||
...(cacheControl !== undefined ? { cache_control: cacheControl } : {}),
|
||||
content: content.length === 0 ? null : ProviderShared.joinText(content),
|
||||
tool_calls: toolCalls.length === 0 ? undefined : toolCalls,
|
||||
reasoning_details: details,
|
||||
cache_control: options.cacheControl?.(cached && "cache" in cached ? cached.cache : undefined),
|
||||
}
|
||||
if (field === undefined || reasoningText === undefined) return result
|
||||
return { ...result, [field]: reasoningText }
|
||||
@@ -710,20 +709,23 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
||||
Boolean(delta?.content) ||
|
||||
reasoning !== undefined ||
|
||||
(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 (hasLateContent)
|
||||
return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason")
|
||||
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
|
||||
if (detailDelta !== undefined) appendReasoningDetails(state.reasoningDetails, detailDelta)
|
||||
const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined
|
||||
const deltaMetadata = reasoningMetadata(reasoningField)
|
||||
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 (
|
||||
reasoningDetailsObserved &&
|
||||
!lifecycle.reasoning.has("reasoning-0") &&
|
||||
@@ -749,7 +751,8 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
||||
const fallback = toolDeltas.length > 1 ? position : (latestToolIndex ?? position)
|
||||
const fallbackTool = tools[fallback] ?? pendingTools[fallback]
|
||||
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 pending = pendingTools[index]
|
||||
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 events: LLMEvent[] = []
|
||||
const toolCallEvents =
|
||||
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 hasToolCalls = state.toolCallEvents.length > 0
|
||||
const reason = state.finishReason
|
||||
? {
|
||||
...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(
|
||||
state.reasoningField,
|
||||
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))
|
||||
: state.lifecycle
|
||||
const ended = Lifecycle.reasoningEnd(started, events, "reasoning-0", metadata)
|
||||
const lifecycle = toolCallEvents.length ? Lifecycle.stepStart(ended, events) : ended
|
||||
events.push(...toolCallEvents)
|
||||
Lifecycle.finish(lifecycle, events, { reason, usage: state.usage })
|
||||
const lifecycle = state.toolCallEvents.length ? Lifecycle.stepStart(ended, events) : ended
|
||||
events.push(...state.toolCallEvents)
|
||||
if (reason) Lifecycle.finish(lifecycle, events, { reason, usage: state.usage })
|
||||
return events
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,7 @@ import { Headers } from "effect/unstable/http"
|
||||
import { Auth, type AuthInput } from "../../route/auth"
|
||||
import { ProviderShared } from "../shared"
|
||||
|
||||
/**
|
||||
* AWS credentials for SigV4 signing. Bedrock also supports Bearer API key auth,
|
||||
* which provider facades configure as route auth instead of SigV4. STS-vended
|
||||
* credentials should be refreshed by the consumer (rebuild the model) before
|
||||
* they expire; the route does not refresh.
|
||||
*/
|
||||
/** AWS credentials for SigV4 signing. */
|
||||
export interface Credentials {
|
||||
readonly region: string
|
||||
readonly accessKeyId: string
|
||||
@@ -17,6 +12,8 @@ export interface Credentials {
|
||||
readonly sessionToken?: string
|
||||
}
|
||||
|
||||
export type CredentialProvider = () => Promise<Credentials>
|
||||
|
||||
const signRequest = (input: {
|
||||
readonly url: string
|
||||
readonly body: string
|
||||
@@ -48,7 +45,7 @@ const signRequest = (input: {
|
||||
|
||||
/** Sign the exact JSON bytes with SigV4 using credentials configured on the route. */
|
||||
export const sigV4 = (
|
||||
credentials: Credentials | undefined,
|
||||
credentials: Credentials | CredentialProvider | undefined,
|
||||
options: { readonly service?: string; readonly name?: string } = {},
|
||||
) =>
|
||||
Auth.custom((input: AuthInput) => {
|
||||
@@ -58,12 +55,22 @@ export const sigV4 = (
|
||||
`${options.name ?? "Bedrock Converse"} requires either route bearer auth or AWS credentials configured on the route`,
|
||||
)
|
||||
}
|
||||
const resolved =
|
||||
typeof credentials === "function"
|
||||
? yield* Effect.tryPromise({
|
||||
try: credentials,
|
||||
catch: (error) =>
|
||||
ProviderShared.invalidRequest(
|
||||
`${options.name ?? "Bedrock Converse"} credential resolution failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
),
|
||||
})
|
||||
: credentials
|
||||
const headersForSigning = Headers.set(input.headers, "content-type", "application/json")
|
||||
const signed = yield* signRequest({
|
||||
url: input.url,
|
||||
body: input.body,
|
||||
headers: headersForSigning,
|
||||
credentials,
|
||||
credentials: resolved,
|
||||
service: options.service ?? "bedrock",
|
||||
name: options.name ?? "Bedrock Converse",
|
||||
})
|
||||
|
||||
@@ -4,13 +4,14 @@ import type { ProviderPackage } from "../provider-package"
|
||||
import { ProviderID, type ModelID } from "../schema"
|
||||
import * as BedrockConverse from "../protocols/bedrock-converse"
|
||||
import type { BedrockCredentials } from "../protocols/bedrock-converse"
|
||||
import type { CredentialProvider } from "../protocols/utils/bedrock-auth"
|
||||
|
||||
export const id = ProviderID.make("amazon-bedrock")
|
||||
|
||||
export type Config = RouteDefaultsInput & {
|
||||
readonly apiKey?: string
|
||||
readonly headers?: Record<string, string>
|
||||
readonly credentials?: BedrockCredentials
|
||||
readonly credentials?: BedrockCredentials | CredentialProvider
|
||||
/** AWS region. Defaults to `us-east-1` when neither this nor `credentials.region` is set. */
|
||||
readonly region?: string
|
||||
/** Override the computed `https://bedrock-runtime.<region>.amazonaws.com` URL. */
|
||||
@@ -21,7 +22,7 @@ export interface Settings extends ProviderPackage.Settings {
|
||||
readonly apiKey?: string
|
||||
readonly auth?: "bearer" | "sigv4"
|
||||
readonly baseURL?: string
|
||||
readonly credentials?: BedrockCredentials
|
||||
readonly credentials?: BedrockCredentials | CredentialProvider
|
||||
readonly region?: string
|
||||
readonly topP?: number
|
||||
}
|
||||
@@ -31,7 +32,7 @@ const bedrockBaseURL = (region: string) => `https://bedrock-runtime.${region}.am
|
||||
|
||||
const configuredRoute = (input: Config) => {
|
||||
const { apiKey, credentials, region, baseURL, ...rest } = input
|
||||
const resolvedRegion = region ?? credentials?.region ?? "us-east-1"
|
||||
const resolvedRegion = region ?? (typeof credentials === "function" ? undefined : credentials?.region) ?? "us-east-1"
|
||||
return BedrockConverse.route.with({
|
||||
...rest,
|
||||
provider: id,
|
||||
|
||||
@@ -255,7 +255,13 @@ const requireTerminalEvent = (route: string) => (events: Stream.Stream<LLMEvent,
|
||||
if (LLMEvent.is.finish(event) || LLMEvent.is.providerError(event)) terminal = true
|
||||
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)),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Cause, Context, Effect, Layer, Option, Schema } from "effect"
|
||||
import { Cause, Context, Effect, Layer } from "effect"
|
||||
import {
|
||||
FetchHttpClient,
|
||||
Headers,
|
||||
@@ -198,20 +198,8 @@ const responseBody = (body: string | void, request: HttpClientRequest.HttpClient
|
||||
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 }) => {
|
||||
if (body.body && body.body.length <= 500) {
|
||||
const decoded = Option.getOrUndefined(decodeProviderBody(body.body))
|
||||
return `Provider request failed with HTTP ${status}: ${decoded?.error?.message ?? decoded?.message ?? body.body}`
|
||||
}
|
||||
if (body.body && body.body.length <= 500) return `Provider request failed with HTTP ${status}: ${body.body}`
|
||||
return `Provider request failed with HTTP ${status}`
|
||||
}
|
||||
|
||||
|
||||
@@ -45,34 +45,35 @@ const isToolResultValue = (value: unknown): value is ToolResultValue =>
|
||||
(value.type === "text" || value.type === "json" || value.type === "error" || value.type === "content") &&
|
||||
"value" in value
|
||||
|
||||
const toolResultValueSchema = Schema.Union([
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("json"),
|
||||
value: Schema.Unknown,
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
value: Schema.Unknown,
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("error"),
|
||||
value: Schema.Unknown,
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("content"),
|
||||
value: Schema.Array(Tool.Content),
|
||||
}),
|
||||
]).annotate({ identifier: "LLM.ToolResult" })
|
||||
export type ToolResultValue = Schema.Schema.Type<typeof toolResultValueSchema>
|
||||
|
||||
export const ToolResultValue = Object.assign(toolResultValueSchema, {
|
||||
is: isToolResultValue,
|
||||
make: (value: unknown, type: ToolResultValue["type"] = "json"): ToolResultValue => {
|
||||
if (isToolResultValue(value)) return value
|
||||
if (type === "content") return { type, value: Array.isArray(value) ? value : [] }
|
||||
return { type, value }
|
||||
export const ToolResultValue = Object.assign(
|
||||
Schema.Union([
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("json"),
|
||||
value: Schema.Unknown,
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
value: Schema.Unknown,
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("error"),
|
||||
value: Schema.Unknown,
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("content"),
|
||||
value: Schema.Array(Tool.Content),
|
||||
}),
|
||||
]).annotate({ identifier: "LLM.ToolResult" }),
|
||||
{
|
||||
is: isToolResultValue,
|
||||
make: (value: unknown, type: ToolResultValue["type"] = "json"): ToolResultValue => {
|
||||
if (isToolResultValue(value)) return value
|
||||
if (type === "content") return { type, value: Array.isArray(value) ? value : [] }
|
||||
return { type, value }
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
export type ToolResultValue = Schema.Schema.Type<typeof ToolResultValue>
|
||||
|
||||
export interface ToolOutput {
|
||||
readonly structured: unknown
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Effect, JsonSchema, Schema } from "effect"
|
||||
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"
|
||||
|
||||
/**
|
||||
@@ -240,7 +244,8 @@ const project = (
|
||||
): ToolOutputType =>
|
||||
ToolOutput.make(
|
||||
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 }
|
||||
|
||||
+7
-1
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-24T16:22:29.494Z"
|
||||
},
|
||||
|
||||
+6
-1
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-23T02:29:10.955Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-19T16:05:51.868Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-19T14:41:43.188Z"
|
||||
},
|
||||
|
||||
+5
-1
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-19T14:57:16.284Z"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"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",
|
||||
"recordedAt": "2026-07-18T11:28:39.267Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:15:39.002Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:15:37.979Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:15:52.400Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:15:48.408Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:21:59.606Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:20:55.140Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:15:36.438Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:15:34.867Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:15:43.608Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-22T18:15:42.429Z"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"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",
|
||||
"recordedAt": "2026-07-18T11:28:42.077Z"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"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",
|
||||
"recordedAt": "2026-07-19T15:56:20.098Z"
|
||||
},
|
||||
|
||||
@@ -751,6 +751,26 @@ describe("Bedrock Converse route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("resolves fresh SigV4 credentials for each request", () => {
|
||||
let calls = 0
|
||||
const signed = AmazonBedrock.configure({
|
||||
baseURL: "https://bedrock-runtime.test",
|
||||
credentials: async () => {
|
||||
calls++
|
||||
return {
|
||||
region: "us-east-1",
|
||||
accessKeyId: "AKIAIOSFODNN7EXAMPLE",
|
||||
secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
}
|
||||
},
|
||||
}).model("anthropic.claude-3-5-sonnet-20240620-v1:0")
|
||||
|
||||
return LLMClient.generate(LLMRequest.update(baseRequest, { model: signed })).pipe(
|
||||
Effect.provide(fixedBytes(eventStreamBody(["messageStop", { stopReason: "end_turn" }]))),
|
||||
Effect.tap(() => Effect.sync(() => expect(calls).toBe(1))),
|
||||
)
|
||||
})
|
||||
|
||||
it.effect("emits cachePoint markers after system, user-text, and assistant-text with cache hints", () =>
|
||||
Effect.gen(function* () {
|
||||
const cache = new CacheHint({ type: "ephemeral" })
|
||||
|
||||
@@ -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", () =>
|
||||
Effect.gen(function* () {
|
||||
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* () {
|
||||
const details = [{ type: "reasoning.text", text: "detail", format: "unknown", index: 0 }]
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
@@ -861,11 +843,11 @@ describe("OpenAI Chat route", () => {
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.reasoning).toBe("detailscalar")
|
||||
expect(response.events.filter(LLMEvent.is.reasoningStart)).toHaveLength(2)
|
||||
expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(2)
|
||||
expect(response.reasoning).toBe("detail")
|
||||
expect(response.events.filter(LLMEvent.is.reasoningStart)).toHaveLength(1)
|
||||
expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(1)
|
||||
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)
|
||||
|
||||
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([
|
||||
{ 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([
|
||||
{ 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([
|
||||
{ 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* () {
|
||||
const body = sseEvents(
|
||||
deltaChunk({
|
||||
@@ -1182,31 +1164,27 @@ describe("OpenAI Chat route", () => {
|
||||
const input = LLMRequest.update(request, {
|
||||
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: "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: ':"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* () {
|
||||
const response = yield* LLMClient.generate(
|
||||
LLMRequest.update(request, {
|
||||
tools: [
|
||||
ToolDefinition.make({ name: "weather", description: "Get weather", inputSchema: { type: "object" } }),
|
||||
],
|
||||
tools: [ToolDefinition.make({ name: "weather", description: "Get weather", inputSchema: { type: "object" } })],
|
||||
}),
|
||||
).pipe(
|
||||
Effect.provide(
|
||||
|
||||
@@ -177,8 +177,8 @@ test("shows all and expands historical diff summary without overlap", async ({ p
|
||||
const firstUser = userMessage(undefined, {
|
||||
summary: {
|
||||
diffs: Array.from({ length: 12 }, (_, index) => ({
|
||||
file: `src/diff-${index}.ts`,
|
||||
status: "modified",
|
||||
file: `src/diff-${index}.ts`,
|
||||
status: "modified",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
patch: `@@ -1 +1 @@\n-export const value = ${index}\n+export const value = ${index + 1}`,
|
||||
|
||||
@@ -85,7 +85,8 @@ async function mockServers(page: Page, requests: string[]) {
|
||||
const current = url.origin === serverA ? sessionA : sessionB
|
||||
const directory = url.searchParams.get("directory")
|
||||
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/session") return json(route, { data: [currentSession(current)], cursor: {} })
|
||||
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)
|
||||
}
|
||||
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")
|
||||
return json(route, {
|
||||
location: { directory },
|
||||
data: [
|
||||
{
|
||||
id: remote ? "server-b" : "server-a",
|
||||
name: remote ? "Server B Provider" : "Server A Provider",
|
||||
package: "test",
|
||||
},
|
||||
],
|
||||
data: [{ 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/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 directory = url.searchParams.get("directory")
|
||||
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/session/active")
|
||||
return json(route, { data: url.origin === serverB ? { [sessionB.id]: { type: "running" } } : {} })
|
||||
|
||||
@@ -171,8 +171,8 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
||||
expect(bottomGap).toBeLessThanOrEqual(16)
|
||||
const lazyDiff = page.waitForRequest((request) => {
|
||||
const url = new URL(request.url())
|
||||
return (
|
||||
url.pathname === "/api/vcs/diff" &&
|
||||
return (
|
||||
url.pathname === "/api/vcs/diff" &&
|
||||
url.searchParams.get("location[directory]")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true
|
||||
)
|
||||
})
|
||||
|
||||
@@ -86,10 +86,15 @@ test.describe("session timeline projection", () => {
|
||||
],
|
||||
{ summary: { diffs: Array.from({ length: 11 }, (_, index) => summaryDiff(index)) } },
|
||||
)
|
||||
const aborted = assistantMessage([{ id: "prt_before_abort", type: "text", text: "Before interruption" }], {
|
||||
id: "msg_1001_assistant_aborted",
|
||||
error: { name: "MessageAbortedError", data: { message: "Stopped" } },
|
||||
})
|
||||
const aborted = assistantMessage(
|
||||
[
|
||||
{ id: "prt_before_abort", type: "text", text: "Before interruption" },
|
||||
],
|
||||
{
|
||||
id: "msg_1001_assistant_aborted",
|
||||
error: { name: "MessageAbortedError", data: { message: "Stopped" } },
|
||||
},
|
||||
)
|
||||
const failed = assistantMessage([{ id: "prt_after_abort", type: "text", text: "After interruption" }], {
|
||||
id: "msg_1002_assistant_failed",
|
||||
error: {
|
||||
|
||||
@@ -89,7 +89,8 @@ async function mockServer(page: Page) {
|
||||
if (url.origin !== server) return route.fallback()
|
||||
if ([`/api/session/${unresolvedSessionID}`, `/session/${unresolvedSessionID}`].includes(url.pathname))
|
||||
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/active") return json(route, { data: {} })
|
||||
const currentSessionInfo = sessions.find((item) => url.pathname === `/api/session/${item.id}`)
|
||||
|
||||
@@ -300,8 +300,9 @@ export const fixture = {
|
||||
.filter((message) => message.info.role === "user")
|
||||
.map((message) => message.info.id),
|
||||
targetPartIDs: targetMessages.flatMap(currentPartIDs),
|
||||
expandedShellPartID: targetMessages.flatMap((message) => message.parts).find((part) => part.tool === "bash")!
|
||||
.callID,
|
||||
expandedShellPartID: targetMessages
|
||||
.flatMap((message) => message.parts)
|
||||
.find((part) => part.tool === "bash")!.callID,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -144,8 +144,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
|
||||
location: location(config),
|
||||
data: currentProviders(providerConfig(config)),
|
||||
})
|
||||
if (path === "/api/model")
|
||||
return json(route, { location: location(config), data: currentModels(providerConfig(config)) })
|
||||
if (path === "/api/model") return json(route, { location: location(config), data: currentModels(providerConfig(config)) })
|
||||
if (path === "/api/model/default")
|
||||
return json(route, { location: location(config), data: currentDefaultModel(providerConfig(config)) })
|
||||
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]
|
||||
if (fileRead && config.fileContent) {
|
||||
const value = await config.fileContent(decodeURIComponent(fileRead))
|
||||
const content =
|
||||
value && typeof value === "object" && "content" in value ? String(value.content) : String(value ?? "")
|
||||
const content = 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" } })
|
||||
}
|
||||
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") {
|
||||
return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } })
|
||||
}
|
||||
if (/^\/question\/[^/]+\/(reply|reject)$/.test(path) && route.request().method() === "POST")
|
||||
return json(route, true)
|
||||
if (/^\/question\/[^/]+\/(reply|reject)$/.test(path) && route.request().method() === "POST") return json(route, true)
|
||||
if (/^\/session\/[^/]+\/permissions\/[^/]+$/.test(path) && route.request().method() === "POST")
|
||||
return json(route, true)
|
||||
if (
|
||||
@@ -389,13 +386,11 @@ function providerConfig(config: MockServerConfig) {
|
||||
|
||||
function currentProviders(value: unknown) {
|
||||
if (!record(value) || !Array.isArray(value.all)) return Array.isArray(value) ? value : []
|
||||
return value.all
|
||||
.filter(record)
|
||||
.flatMap((provider) =>
|
||||
typeof provider.id === "string" && typeof provider.name === "string"
|
||||
? [{ id: provider.id, name: provider.name, package: provider.id }]
|
||||
: [],
|
||||
)
|
||||
return value.all.filter(record).flatMap((provider) =>
|
||||
typeof provider.id === "string" && typeof provider.name === "string"
|
||||
? [{ id: provider.id, name: provider.name, package: provider.id }]
|
||||
: [],
|
||||
)
|
||||
}
|
||||
|
||||
function currentModels(value: unknown) {
|
||||
@@ -445,7 +440,9 @@ function currentDefaultModel(value: unknown) {
|
||||
if (!record(value) || !record(value.default)) return null
|
||||
const selected = value.default
|
||||
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) {
|
||||
@@ -565,16 +562,22 @@ function legacyAgent(part: Record<string, unknown>): PromptAgentAttachment[] {
|
||||
}
|
||||
|
||||
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 { 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")
|
||||
return [
|
||||
{ type: "text", text: part.text, ...(jsonRecord(part.metadata) ? { state: jsonRecord(part.metadata) } : {}) },
|
||||
]
|
||||
return [{ type: "text", text: part.text, ...(jsonRecord(part.metadata) ? { state: jsonRecord(part.metadata) } : {}) }]
|
||||
if (part.type === "reasoning" && typeof part.text === "string") {
|
||||
const time = record(part.time) ? part.time : undefined
|
||||
return [
|
||||
@@ -615,12 +618,7 @@ function legacyAssistantContent(part: Record<string, unknown>, created: number):
|
||||
...(jsonRecord(part.providerResultState) ? { providerResultState: jsonRecord(part.providerResultState) } : {}),
|
||||
}
|
||||
if (state.status === "pending")
|
||||
return [
|
||||
{
|
||||
...base,
|
||||
state: { status: "streaming", input: typeof state.raw === "string" ? state.raw : JSON.stringify(input) },
|
||||
},
|
||||
]
|
||||
return [{ ...base, state: { status: "streaming", input: typeof state.raw === "string" ? state.raw : JSON.stringify(input) } }]
|
||||
if (state.status === "completed")
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -174,7 +174,8 @@ export async function installSseTransport<T>(
|
||||
const fetch = (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const request = new Request(input, init)
|
||||
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 record = {
|
||||
|
||||
@@ -20,10 +20,7 @@
|
||||
<meta property="twitter:image" content="/social-share.png" />
|
||||
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
|
||||
</head>
|
||||
<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"
|
||||
>
|
||||
<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">
|
||||
<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>
|
||||
<script src="/src/entry.tsx" type="module"></script>
|
||||
|
||||
@@ -9,7 +9,14 @@ import { Font } from "@opencode-ai/ui/font"
|
||||
import { Splash } from "@opencode-ai/ui/logo"
|
||||
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
||||
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 { Effect } from "effect"
|
||||
import {
|
||||
|
||||
@@ -123,7 +123,9 @@ export const SettingsProvidersV2: Component<{
|
||||
const credentials = integration.data?.connections.filter((item) => item.type === "credential") ?? []
|
||||
if (credentials.length === 0) throw new Error(`No removable credentials found for ${name}`)
|
||||
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({
|
||||
variant: "success",
|
||||
|
||||
@@ -5,16 +5,7 @@ import { Switch } from "@opencode-ai/ui/switch"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import {
|
||||
type Accessor,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createResource,
|
||||
For,
|
||||
type JSXElement,
|
||||
onCleanup,
|
||||
Show,
|
||||
} from "solid-js"
|
||||
import { type Accessor, createEffect, createMemo, createResource, For, type JSXElement, onCleanup, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
|
||||
import { useLanguage } from "@/context/language"
|
||||
@@ -300,10 +291,7 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
|
||||
const lspCount = createMemo(() => lspItems().length)
|
||||
const [pluginList] = createResource(
|
||||
() => (props.shown() ? sdk().directory : undefined),
|
||||
(directory) =>
|
||||
sdk()
|
||||
.api.plugin.list({ location: { directory } })
|
||||
.then((result) => result.data),
|
||||
(directory) => sdk().api.plugin.list({ location: { directory } }).then((result) => result.data),
|
||||
)
|
||||
const plugins = createMemo(() => (pluginList.latest ?? []).map((item) => item.id))
|
||||
const pluginCount = createMemo(() => plugins().length)
|
||||
|
||||
@@ -190,7 +190,10 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
||||
.find((item) => ServerConnection.key(item) === (route.server ?? server.key))
|
||||
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) => {
|
||||
|
||||
@@ -124,7 +124,9 @@ export const createDirSyncContext = (
|
||||
const [store, setStore] = current()
|
||||
setStore("limit", (value) => value + count)
|
||||
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)
|
||||
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 {
|
||||
AgentListInput,
|
||||
AgentListOutput,
|
||||
@@ -26,7 +31,12 @@ import { batch } from "solid-js"
|
||||
import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
||||
import type { State, VcsCache } from "./types"
|
||||
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 { QueryClient, queryOptions } from "@tanstack/solid-query"
|
||||
import { loadMcpQuery, loadMcpResourcesQuery } from "../server-sync"
|
||||
@@ -138,7 +148,10 @@ export async function bootstrapGlobal(input: {
|
||||
}) {
|
||||
const slow = [
|
||||
() => 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
|
||||
@@ -198,7 +211,11 @@ function warmSessions(input: {
|
||||
).then(() => undefined)
|
||||
}
|
||||
|
||||
export const loadProvidersQuery = (scope: ServerScope, directory: string | null, sdk: CatalogApi) =>
|
||||
export const loadProvidersQuery = (
|
||||
scope: ServerScope,
|
||||
directory: string | null,
|
||||
sdk: CatalogApi,
|
||||
) =>
|
||||
queryOptions({
|
||||
queryKey: [scope, directory, "providers"],
|
||||
queryFn: () =>
|
||||
@@ -225,16 +242,28 @@ type ReferenceListApi = {
|
||||
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({
|
||||
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))
|
||||
|
||||
export const loadPathQuery = (scope: ServerScope, directory: string | null, api: LocationApi) =>
|
||||
export const loadPathQuery = (
|
||||
scope: ServerScope,
|
||||
directory: string | null,
|
||||
api: LocationApi,
|
||||
) =>
|
||||
queryOptions<Path>({
|
||||
queryKey: [scope, directory, "path"],
|
||||
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[]>({
|
||||
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: [],
|
||||
})
|
||||
|
||||
@@ -319,41 +353,46 @@ export async function bootstrapDirectory(input: {
|
||||
})),
|
||||
input.mcp &&
|
||||
(() =>
|
||||
loadCommands(input.directory, input.api.command).then((commands) => input.setStore("command", commands))),
|
||||
() => input.queryClient.fetchQuery(loadReferencesQuery(input.scope, input.directory, input.api.reference)),
|
||||
loadCommands(input.directory, input.api.command).then((commands) =>
|
||||
input.setStore("command", commands),
|
||||
)),
|
||||
() =>
|
||||
input.queryClient.fetchQuery(
|
||||
loadReferencesQuery(input.scope, input.directory, input.api.reference),
|
||||
),
|
||||
() =>
|
||||
retry(() =>
|
||||
input.api.permission.request
|
||||
.list({ location: { directory: input.directory } })
|
||||
.then((result) => result.data)
|
||||
.then((permissions) => {
|
||||
const ids = permissions.map((permission) => permission.sessionID)
|
||||
const grouped = groupBySession(
|
||||
permissions.filter((permission) => !!permission.id && !!permission.sessionID),
|
||||
)
|
||||
const warm = input.session
|
||||
? Promise.all(ids.map((sessionID) => input.session!.resolve(sessionID))).then(() => undefined)
|
||||
: warmSessions({ ids, store: input.store, setStore: input.setStore, api: input.api.session })
|
||||
return warm.then(() =>
|
||||
batch(() => {
|
||||
const current = input.session?.data.permission ?? input.store.permission
|
||||
for (const sessionID of Object.keys(current)) {
|
||||
if (grouped[sessionID]) continue
|
||||
if (input.session?.get(sessionID)?.location.directory !== input.directory) continue
|
||||
if (input.session) input.session.set("permission", sessionID, [])
|
||||
if (!input.session) input.setStore("permission", sessionID, [])
|
||||
}
|
||||
for (const [sessionID, permissions] of Object.entries(grouped)) {
|
||||
const value = reconcile(
|
||||
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||
{ key: "id" },
|
||||
)
|
||||
if (input.session) input.session.set("permission", sessionID, value)
|
||||
if (!input.session) input.setStore("permission", sessionID, value)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
const ids = permissions.map((permission) => permission.sessionID)
|
||||
const grouped = groupBySession(
|
||||
permissions.filter((permission) => !!permission.id && !!permission.sessionID),
|
||||
)
|
||||
const warm = input.session
|
||||
? Promise.all(ids.map((sessionID) => input.session!.resolve(sessionID))).then(() => undefined)
|
||||
: warmSessions({ ids, store: input.store, setStore: input.setStore, api: input.api.session })
|
||||
return warm.then(() =>
|
||||
batch(() => {
|
||||
const current = input.session?.data.permission ?? input.store.permission
|
||||
for (const sessionID of Object.keys(current)) {
|
||||
if (grouped[sessionID]) continue
|
||||
if (input.session?.get(sessionID)?.location.directory !== input.directory) continue
|
||||
if (input.session) input.session.set("permission", sessionID, [])
|
||||
if (!input.session) input.setStore("permission", sessionID, [])
|
||||
}
|
||||
for (const [sessionID, permissions] of Object.entries(grouped)) {
|
||||
const value = reconcile(
|
||||
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||
{ key: "id" },
|
||||
)
|
||||
if (input.session) input.session.set("permission", sessionID, value)
|
||||
if (!input.session) input.setStore("permission", sessionID, value)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
),
|
||||
() =>
|
||||
retry(() =>
|
||||
@@ -361,47 +400,56 @@ export async function bootstrapDirectory(input: {
|
||||
.list({ location: { directory: input.directory } })
|
||||
.then((result) => result.data)
|
||||
.then((questions) => {
|
||||
const ids = questions.map((question) => question.sessionID)
|
||||
const grouped = groupBySession(
|
||||
questions.filter((question) => !!question.id && !!question.sessionID) as QuestionRequest[],
|
||||
)
|
||||
const warm = input.session
|
||||
? Promise.all(ids.map((sessionID) => input.session!.resolve(sessionID))).then(() => undefined)
|
||||
: warmSessions({ ids, store: input.store, setStore: input.setStore, api: input.api.session })
|
||||
return warm.then(() =>
|
||||
batch(() => {
|
||||
const current = input.session?.data.question ?? input.store.question
|
||||
for (const sessionID of Object.keys(current)) {
|
||||
if (grouped[sessionID]) continue
|
||||
if (input.session?.get(sessionID)?.location.directory !== input.directory) continue
|
||||
if (input.session) input.session.set("question", sessionID, [])
|
||||
if (!input.session) input.setStore("question", sessionID, [])
|
||||
}
|
||||
for (const [sessionID, questions] of Object.entries(grouped)) {
|
||||
const value = reconcile(
|
||||
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||
{ key: "id" },
|
||||
)
|
||||
if (input.session) input.session.set("question", sessionID, value)
|
||||
if (!input.session) input.setStore("question", sessionID, value)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
const ids = questions.map((question) => question.sessionID)
|
||||
const grouped = groupBySession(
|
||||
questions.filter((question) => !!question.id && !!question.sessionID) as QuestionRequest[],
|
||||
)
|
||||
const warm = input.session
|
||||
? Promise.all(ids.map((sessionID) => input.session!.resolve(sessionID))).then(() => undefined)
|
||||
: warmSessions({ ids, store: input.store, setStore: input.setStore, api: input.api.session })
|
||||
return warm.then(() =>
|
||||
batch(() => {
|
||||
const current = input.session?.data.question ?? input.store.question
|
||||
for (const sessionID of Object.keys(current)) {
|
||||
if (grouped[sessionID]) continue
|
||||
if (input.session?.get(sessionID)?.location.directory !== input.directory) continue
|
||||
if (input.session) input.session.set("question", sessionID, [])
|
||||
if (!input.session) input.setStore("question", sessionID, [])
|
||||
}
|
||||
for (const [sessionID, questions] of Object.entries(grouped)) {
|
||||
const value = reconcile(
|
||||
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||
{ key: "id" },
|
||||
)
|
||||
if (input.session) input.session.set("question", sessionID, value)
|
||||
if (!input.session) input.setStore("question", sessionID, value)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
),
|
||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||
input.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(
|
||||
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) => {
|
||||
const project = getFilename(input.directory)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||
description: formatServerError(err, input.translate),
|
||||
})
|
||||
}),
|
||||
input.queryClient
|
||||
.fetchQuery(loadProvidersQuery(input.scope, input.directory, input.api))
|
||||
.catch((err) => {
|
||||
const project = getFilename(input.directory)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||
description: formatServerError(err, input.translate),
|
||||
})
|
||||
}),
|
||||
].filter(Boolean) as (() => Promise<any>)[]
|
||||
|
||||
await waitForPaint()
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Binary } from "@opencode-ai/core/util/binary"
|
||||
import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
||||
import type { Message, Part, Project, Todo } from "@/types"
|
||||
import type {
|
||||
FileDiffInfo,
|
||||
PermissionRequest,
|
||||
QuestionRequest,
|
||||
SessionInfo,
|
||||
SessionStatus,
|
||||
} from "@opencode-ai/client/promise"
|
||||
Message,
|
||||
Part,
|
||||
Project,
|
||||
Todo,
|
||||
} from "@/types"
|
||||
import type { FileDiffInfo, PermissionRequest, QuestionRequest, SessionInfo, SessionStatus } from "@opencode-ai/client/promise"
|
||||
import type { State, VcsCache } from "./types"
|
||||
import { trimSessions } from "./session-trim"
|
||||
import { dropSessionCaches } from "./session-cache"
|
||||
|
||||
@@ -81,9 +81,9 @@ describe("Home V2 session index", () => {
|
||||
])
|
||||
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "root",
|
||||
location: { directory: "/project" },
|
||||
expect.objectContaining({
|
||||
id: "root",
|
||||
location: { directory: "/project" },
|
||||
projectID: "project",
|
||||
title: "root",
|
||||
time: { created: 1, updated: 30 },
|
||||
|
||||
@@ -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 {
|
||||
FileDiffInfo,
|
||||
PermissionRequest,
|
||||
|
||||
@@ -67,74 +67,64 @@ function currentMessages(data: MessageResponse["data"]): SessionMessageInfo[] {
|
||||
},
|
||||
]
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: item.info.id,
|
||||
type: "assistant",
|
||||
agent: item.info.agent,
|
||||
model: { id: item.info.modelID, providerID: item.info.providerID, variant: item.info.variant },
|
||||
content: item.parts.flatMap((part): SessionMessageAssistant["content"] => {
|
||||
if (part.type === "text") return [{ type: "text", text: part.text }]
|
||||
if (part.type === "reasoning")
|
||||
return [
|
||||
{
|
||||
type: "reasoning",
|
||||
text: part.text,
|
||||
time: part.time ? { created: part.time.start, completed: part.time.end } : undefined,
|
||||
},
|
||||
]
|
||||
if (part.type !== "tool") return []
|
||||
const state: SessionMessageAssistantTool["state"] = (() => {
|
||||
if (part.state.status === "pending")
|
||||
return { status: "streaming" as const, input: JSON.stringify(part.state.input) }
|
||||
if (part.state.status === "running")
|
||||
return {
|
||||
status: "running" as const,
|
||||
input: part.state.input as CurrentToolObject,
|
||||
metadata: (part.state.metadata ?? {}) as CurrentToolObject,
|
||||
}
|
||||
if (part.state.status === "error")
|
||||
return {
|
||||
status: "error" as const,
|
||||
input: part.state.input as CurrentToolObject,
|
||||
error: { type: "tool_error", message: part.state.error },
|
||||
metadata: part.state.metadata as CurrentToolObject | undefined,
|
||||
}
|
||||
return {
|
||||
status: "completed" as const,
|
||||
input: part.state.input as CurrentToolObject,
|
||||
content: [{ type: "text" as const, text: part.state.output }],
|
||||
metadata: part.state.metadata as CurrentToolObject,
|
||||
}
|
||||
})()
|
||||
return [{
|
||||
id: item.info.id,
|
||||
type: "assistant",
|
||||
agent: item.info.agent,
|
||||
model: { id: item.info.modelID, providerID: item.info.providerID, variant: item.info.variant },
|
||||
content: item.parts.flatMap((part): SessionMessageAssistant["content"] => {
|
||||
if (part.type === "text") return [{ type: "text", text: part.text }]
|
||||
if (part.type === "reasoning")
|
||||
return [
|
||||
{
|
||||
id: part.id,
|
||||
type: "tool" as const,
|
||||
name: part.tool,
|
||||
state,
|
||||
time: {
|
||||
created: part.state.status === "pending" ? item.info.time.created : part.state.time.start,
|
||||
ran: part.state.status === "pending" ? undefined : part.state.time.start,
|
||||
completed:
|
||||
part.state.status === "completed" || part.state.status === "error" ? part.state.time.end : undefined,
|
||||
},
|
||||
type: "reasoning",
|
||||
text: part.text,
|
||||
time: part.time ? { created: part.time.start, completed: part.time.end } : undefined,
|
||||
},
|
||||
]
|
||||
}),
|
||||
time: item.info.time,
|
||||
cost: item.info.cost,
|
||||
tokens: item.info.tokens,
|
||||
finish: item.info.finish as
|
||||
| "stop"
|
||||
| "length"
|
||||
| "tool-calls"
|
||||
| "content-filter"
|
||||
| "error"
|
||||
| "unknown"
|
||||
| undefined,
|
||||
},
|
||||
]
|
||||
if (part.type !== "tool") return []
|
||||
const state: SessionMessageAssistantTool["state"] = (() => {
|
||||
if (part.state.status === "pending") return { status: "streaming" as const, input: JSON.stringify(part.state.input) }
|
||||
if (part.state.status === "running")
|
||||
return {
|
||||
status: "running" as const,
|
||||
input: part.state.input as CurrentToolObject,
|
||||
metadata: (part.state.metadata ?? {}) as CurrentToolObject,
|
||||
}
|
||||
if (part.state.status === "error")
|
||||
return {
|
||||
status: "error" as const,
|
||||
input: part.state.input as CurrentToolObject,
|
||||
error: { type: "tool_error", message: part.state.error },
|
||||
metadata: part.state.metadata as CurrentToolObject | undefined,
|
||||
}
|
||||
return {
|
||||
status: "completed" as const,
|
||||
input: part.state.input as CurrentToolObject,
|
||||
content: [{ type: "text" as const, text: part.state.output }],
|
||||
metadata: part.state.metadata as CurrentToolObject,
|
||||
}
|
||||
})()
|
||||
return [
|
||||
{
|
||||
id: part.id,
|
||||
type: "tool" as const,
|
||||
name: part.tool,
|
||||
state,
|
||||
time: {
|
||||
created: part.state.status === "pending" ? item.info.time.created : part.state.time.start,
|
||||
ran: part.state.status === "pending" ? undefined : part.state.time.start,
|
||||
completed:
|
||||
part.state.status === "completed" || part.state.status === "error" ? part.state.time.end : undefined,
|
||||
},
|
||||
},
|
||||
]
|
||||
}),
|
||||
time: item.info.time,
|
||||
cost: item.info.cost,
|
||||
tokens: item.info.tokens,
|
||||
finish: item.info.finish as "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" | undefined,
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -438,259 +428,270 @@ describe("server session", () => {
|
||||
|
||||
// V2 messages are ordered projections and do not expose V1 assistant parent IDs.
|
||||
describe.skip("V1 assistant parent projections", () => {
|
||||
test("backfills an assistant-only initial page through its user root", async () => {
|
||||
const user = userMessage("message-1")
|
||||
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
|
||||
const client = rootMessageClient(
|
||||
[response(assistants.map((info) => ({ info, parts: [] })))],
|
||||
[singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
test("backfills an assistant-only initial page through its user root", async () => {
|
||||
const user = userMessage("message-1")
|
||||
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
|
||||
const client = rootMessageClient(
|
||||
[
|
||||
response(assistants.map((info) => ({ info, parts: [] }))),
|
||||
],
|
||||
[singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
|
||||
await store.sync("child")
|
||||
await store.sync("child")
|
||||
|
||||
expect(client.requests).toEqual([{ sessionID: "child", limit: 20, order: "desc" }])
|
||||
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: user.id }])
|
||||
expect(store.data.message.child).toEqual([user, ...assistants])
|
||||
expect(store.history.more("child")).toBe(false)
|
||||
})
|
||||
expect(client.requests).toEqual([{ sessionID: "child", limit: 20, order: "desc" }])
|
||||
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: user.id }])
|
||||
expect(store.data.message.child).toEqual([user, ...assistants])
|
||||
expect(store.history.more("child")).toBe(false)
|
||||
})
|
||||
|
||||
test("keeps assistant history when its deleted parent cannot be backfilled", async () => {
|
||||
const missing = Promise.withResolvers<SingleMessageResponse>()
|
||||
const assistant = assistantMessage("message-2", "message-missing")
|
||||
const client = rootMessageClient([response([{ info: assistant, parts: [] }])], [missing.promise])
|
||||
const store = createServerSession(client)
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
test("keeps assistant history when its deleted parent cannot be backfilled", async () => {
|
||||
const missing = Promise.withResolvers<SingleMessageResponse>()
|
||||
const assistant = assistantMessage("message-2", "message-missing")
|
||||
const client = rootMessageClient([response([{ info: assistant, parts: [] }])], [missing.promise])
|
||||
const store = createServerSession(client)
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
|
||||
missing.reject(new Error("Message not found: message-missing", { cause: { status: 404 } }))
|
||||
await loading
|
||||
missing.reject(new Error("Message not found: message-missing", { cause: { status: 404 } }))
|
||||
await loading
|
||||
|
||||
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: "message-missing" }])
|
||||
expect(store.data.message.child).toEqual([assistant])
|
||||
expect(store.history.more("child")).toBe(false)
|
||||
})
|
||||
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: "message-missing" }])
|
||||
expect(store.data.message.child).toEqual([assistant])
|
||||
expect(store.history.more("child")).toBe(false)
|
||||
})
|
||||
|
||||
test("drops a cached parent when a forced refresh confirms it was deleted", async () => {
|
||||
const missing = Promise.withResolvers<SingleMessageResponse>()
|
||||
const parent = userMessage("message-1")
|
||||
const part = textPart(parent.id)
|
||||
const assistant = assistantMessage("message-2", parent.id)
|
||||
const client = rootMessageClient(
|
||||
[
|
||||
response([
|
||||
{ info: parent, parts: [part] },
|
||||
{ info: assistant, parts: [] },
|
||||
]),
|
||||
response([{ info: assistant, parts: [] }]),
|
||||
],
|
||||
[missing.promise],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
await store.sync("child")
|
||||
const loading = store.sync("child", { force: true })
|
||||
await client.rootRequested(1)
|
||||
test("drops a cached parent when a forced refresh confirms it was deleted", async () => {
|
||||
const missing = Promise.withResolvers<SingleMessageResponse>()
|
||||
const parent = userMessage("message-1")
|
||||
const part = textPart(parent.id)
|
||||
const assistant = assistantMessage("message-2", parent.id)
|
||||
const client = rootMessageClient(
|
||||
[
|
||||
response([
|
||||
{ info: parent, parts: [part] },
|
||||
{ info: assistant, parts: [] },
|
||||
]),
|
||||
response([{ info: assistant, parts: [] }]),
|
||||
],
|
||||
[missing.promise],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
await store.sync("child")
|
||||
const loading = store.sync("child", { force: true })
|
||||
await client.rootRequested(1)
|
||||
|
||||
missing.reject(new Error(`Message not found: ${parent.id}`, { cause: { status: 404 } }))
|
||||
await loading
|
||||
missing.reject(new Error(`Message not found: ${parent.id}`, { cause: { status: 404 } }))
|
||||
await loading
|
||||
|
||||
expect(store.data.message.child).toEqual([assistant])
|
||||
expect(store.data.part[parent.id]).toBeUndefined()
|
||||
})
|
||||
expect(store.data.message.child).toEqual([assistant])
|
||||
expect(store.data.part[parent.id]).toBeUndefined()
|
||||
})
|
||||
|
||||
test("does not let an optimistic user suppress initial root backfill", async () => {
|
||||
const user = userMessage("message-1")
|
||||
const part = textPart(user.id)
|
||||
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
|
||||
const client = rootMessageClient(
|
||||
[response(assistants.map((info) => ({ info, parts: [] })))],
|
||||
[singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
store.optimistic.add({ sessionID: "child", message: user, parts: [part] })
|
||||
test("does not let an optimistic user suppress initial root backfill", async () => {
|
||||
const user = userMessage("message-1")
|
||||
const part = textPart(user.id)
|
||||
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
|
||||
const client = rootMessageClient(
|
||||
[
|
||||
response(assistants.map((info) => ({ info, parts: [] }))),
|
||||
],
|
||||
[singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
store.optimistic.add({ sessionID: "child", message: user, parts: [part] })
|
||||
|
||||
await store.sync("child")
|
||||
store.optimistic.remove({ sessionID: "child", messageID: user.id })
|
||||
await store.sync("child")
|
||||
store.optimistic.remove({ sessionID: "child", messageID: user.id })
|
||||
|
||||
expect(client.requests).toHaveLength(1)
|
||||
expect(client.rootRequests).toHaveLength(1)
|
||||
expect(store.data.message.child).toEqual([user, ...assistants])
|
||||
})
|
||||
expect(client.requests).toHaveLength(1)
|
||||
expect(client.rootRequests).toHaveLength(1)
|
||||
expect(store.data.message.child).toEqual([user, ...assistants])
|
||||
})
|
||||
|
||||
test("backfills the parent of fetched assistants when another user is cached", async () => {
|
||||
const unrelated = userMessage("message-0", { time: { created: 0 } })
|
||||
const user = userMessage("message-1")
|
||||
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: unrelated, parts: [] }]), response(assistants.map((info) => ({ info, parts: [] })))],
|
||||
[singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
await store.sync("child")
|
||||
test("backfills the parent of fetched assistants when another user is cached", async () => {
|
||||
const unrelated = userMessage("message-0", { time: { created: 0 } })
|
||||
const user = userMessage("message-1")
|
||||
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
|
||||
const client = rootMessageClient(
|
||||
[
|
||||
response([{ info: unrelated, parts: [] }]),
|
||||
response(assistants.map((info) => ({ info, parts: [] }))),
|
||||
],
|
||||
[singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
await store.sync("child")
|
||||
|
||||
await store.sync("child", { force: true })
|
||||
await store.sync("child", { force: true })
|
||||
|
||||
expect(client.requests).toHaveLength(2)
|
||||
expect(client.rootRequests).toHaveLength(1)
|
||||
expect(store.data.message.child).toEqual([unrelated, user, ...assistants])
|
||||
})
|
||||
expect(client.requests).toHaveLength(2)
|
||||
expect(client.rootRequests).toHaveLength(1)
|
||||
expect(store.data.message.child).toEqual([unrelated, user, ...assistants])
|
||||
})
|
||||
|
||||
test("preserves cached history between an injected parent and the page boundary", async () => {
|
||||
const user = userMessage("message-1")
|
||||
const cached = userMessage("message-3", { time: { created: 3 } })
|
||||
const assistant = assistantMessage("message-4", user.id)
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: cached, parts: [] }]), response([{ info: assistant, parts: [] }])],
|
||||
[singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
await store.sync("child")
|
||||
test("preserves cached history between an injected parent and the page boundary", async () => {
|
||||
const user = userMessage("message-1")
|
||||
const cached = userMessage("message-3", { time: { created: 3 } })
|
||||
const assistant = assistantMessage("message-4", user.id)
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: cached, parts: [] }]), response([{ info: assistant, parts: [] }])],
|
||||
[singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
await store.sync("child")
|
||||
|
||||
await store.sync("child", { force: true })
|
||||
await store.sync("child", { force: true })
|
||||
|
||||
expect(store.data.message.child).toEqual([user, cached, assistant])
|
||||
})
|
||||
expect(store.data.message.child).toEqual([user, cached, assistant])
|
||||
})
|
||||
|
||||
test("refreshes a cached parent omitted by an assistant-only replacement page", async () => {
|
||||
const stale = userMessage("message-1", { summary: { title: "stale", diffs: [] } })
|
||||
const fresh = { ...stale, summary: { title: "fresh", diffs: [] } }
|
||||
const stalePart = textPart(stale.id, { text: "stale" })
|
||||
const freshPart = { ...stalePart, text: "fresh" }
|
||||
const assistant = assistantMessage("message-2", stale.id)
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: stale, parts: [stalePart] }]), response([{ info: assistant, parts: [] }])],
|
||||
[singleResponse(fresh, [freshPart])],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
await store.sync("child")
|
||||
test("refreshes a cached parent omitted by an assistant-only replacement page", async () => {
|
||||
const stale = userMessage("message-1", { summary: { title: "stale", diffs: [] } })
|
||||
const fresh = { ...stale, summary: { title: "fresh", diffs: [] } }
|
||||
const stalePart = textPart(stale.id, { text: "stale" })
|
||||
const freshPart = { ...stalePart, text: "fresh" }
|
||||
const assistant = assistantMessage("message-2", stale.id)
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: stale, parts: [stalePart] }]), response([{ info: assistant, parts: [] }])],
|
||||
[singleResponse(fresh, [freshPart])],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
await store.sync("child")
|
||||
|
||||
await store.sync("child", { force: true })
|
||||
await store.sync("child", { force: true })
|
||||
|
||||
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: stale.id }])
|
||||
expect(store.data.message.child).toEqual([fresh, assistant])
|
||||
expect(store.data.part[stale.id]).toEqual([freshPart])
|
||||
})
|
||||
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: stale.id }])
|
||||
expect(store.data.message.child).toEqual([fresh, assistant])
|
||||
expect(store.data.part[stale.id]).toEqual([freshPart])
|
||||
})
|
||||
|
||||
test("refreshes a confirmed optimistic parent while preserving pending parts", async () => {
|
||||
const stale = userMessage("message-1", { summary: { title: "stale", diffs: [] } })
|
||||
const fresh = { ...stale, summary: { title: "fresh", diffs: [] } }
|
||||
const confirmed = textPart(stale.id, { id: "confirmed", text: "stale" })
|
||||
const refreshed = { ...confirmed, text: "fresh" }
|
||||
const pending = textPart(stale.id, { id: "pending", text: "pending" })
|
||||
const assistant = assistantMessage("message-2", stale.id)
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: stale, parts: [confirmed] }]), response([{ info: assistant, parts: [] }])],
|
||||
[singleResponse(fresh, [refreshed])],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
store.optimistic.add({ sessionID: "child", message: stale, parts: [confirmed, pending] })
|
||||
await store.sync("child")
|
||||
test("refreshes a confirmed optimistic parent while preserving pending parts", async () => {
|
||||
const stale = userMessage("message-1", { summary: { title: "stale", diffs: [] } })
|
||||
const fresh = { ...stale, summary: { title: "fresh", diffs: [] } }
|
||||
const confirmed = textPart(stale.id, { id: "confirmed", text: "stale" })
|
||||
const refreshed = { ...confirmed, text: "fresh" }
|
||||
const pending = textPart(stale.id, { id: "pending", text: "pending" })
|
||||
const assistant = assistantMessage("message-2", stale.id)
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: stale, parts: [confirmed] }]), response([{ info: assistant, parts: [] }])],
|
||||
[singleResponse(fresh, [refreshed])],
|
||||
)
|
||||
const store = createServerSession(client)
|
||||
store.optimistic.add({ sessionID: "child", message: stale, parts: [confirmed, pending] })
|
||||
await store.sync("child")
|
||||
|
||||
await store.sync("child", { force: true })
|
||||
await store.sync("child", { force: true })
|
||||
|
||||
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: stale.id }])
|
||||
expect(store.data.message.child).toEqual([fresh, assistant])
|
||||
expect(store.data.part[stale.id]).toEqual([refreshed, pending])
|
||||
})
|
||||
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: stale.id }])
|
||||
expect(store.data.message.child).toEqual([fresh, assistant])
|
||||
expect(store.data.part[stale.id]).toEqual([refreshed, pending])
|
||||
})
|
||||
|
||||
test("uses a parent received by SSE during the replacement load", async () => {
|
||||
const pending = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const assistant = assistantMessage("message-2", user.id)
|
||||
const client = rootMessageClient([pending.promise], [])
|
||||
const store = createServerSession(client)
|
||||
const loading = store.sync("child")
|
||||
test("uses a parent received by SSE during the replacement load", async () => {
|
||||
const pending = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const assistant = assistantMessage("message-2", user.id)
|
||||
const client = rootMessageClient([pending.promise], [])
|
||||
const store = createServerSession(client)
|
||||
const loading = store.sync("child")
|
||||
|
||||
store.apply({ type: "message.updated", properties: { info: user } })
|
||||
pending.resolve(response([{ info: assistant, parts: [] }]))
|
||||
await loading
|
||||
store.apply({ type: "message.updated", properties: { info: user } })
|
||||
pending.resolve(response([{ info: assistant, parts: [] }]))
|
||||
await loading
|
||||
|
||||
expect(client.rootRequests).toEqual([])
|
||||
expect(store.data.message.child).toEqual([user, assistant])
|
||||
})
|
||||
expect(client.rootRequests).toEqual([])
|
||||
expect(store.data.message.child).toEqual([user, assistant])
|
||||
})
|
||||
|
||||
test("uses a successful retry over events received by a failed backfill attempt", async () => {
|
||||
const failed = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const live = { ...user, agent: "stale" }
|
||||
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
|
||||
const client = rootMessageClient(
|
||||
[response(assistants.map((info) => ({ info, parts: [] })))],
|
||||
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client, { retry: retryImmediately })
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
test("uses a successful retry over events received by a failed backfill attempt", async () => {
|
||||
const failed = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const live = { ...user, agent: "stale" }
|
||||
const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)]
|
||||
const client = rootMessageClient(
|
||||
[
|
||||
response(
|
||||
assistants.map((info) => ({ info, parts: [] })),
|
||||
),
|
||||
],
|
||||
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client, { retry: retryImmediately })
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
|
||||
store.apply({ type: "message.updated", properties: { info: live } })
|
||||
failed.reject(new Error("retry"))
|
||||
await loading
|
||||
store.apply({ type: "message.updated", properties: { info: live } })
|
||||
failed.reject(new Error("retry"))
|
||||
await loading
|
||||
|
||||
expect(client.requests).toHaveLength(1)
|
||||
expect(client.rootRequests).toHaveLength(2)
|
||||
expect(store.data.message.child).toEqual([user, ...assistants])
|
||||
})
|
||||
expect(client.requests).toHaveLength(1)
|
||||
expect(client.rootRequests).toHaveLength(2)
|
||||
expect(store.data.message.child).toEqual([user, ...assistants])
|
||||
})
|
||||
|
||||
test("preserves newer-page events across a failed parent retry", async () => {
|
||||
const failed = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const assistant = assistantMessage("message-2", user.id)
|
||||
const live = { ...assistant, cost: 1 }
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: assistant, parts: [] }])],
|
||||
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client, { retry: retryImmediately })
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
test("preserves newer-page events across a failed parent retry", async () => {
|
||||
const failed = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const assistant = assistantMessage("message-2", user.id)
|
||||
const live = { ...assistant, cost: 1 }
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: assistant, parts: [] }])],
|
||||
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client, { retry: retryImmediately })
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
|
||||
store.apply({ type: "message.updated", properties: { info: live } })
|
||||
failed.reject(new Error("retry"))
|
||||
await loading
|
||||
store.apply({ type: "message.updated", properties: { info: live } })
|
||||
failed.reject(new Error("retry"))
|
||||
await loading
|
||||
|
||||
expect(store.data.message.child).toEqual([user, live])
|
||||
})
|
||||
expect(store.data.message.child).toEqual([user, live])
|
||||
})
|
||||
|
||||
test("preserves unrelated message events across a failed parent retry", async () => {
|
||||
const failed = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const assistant = assistantMessage("message-2", user.id)
|
||||
const live = userMessage("message-4", { time: { created: 4 } })
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: assistant, parts: [] }])],
|
||||
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client, { retry: retryImmediately })
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
test("preserves unrelated message events across a failed parent retry", async () => {
|
||||
const failed = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const assistant = assistantMessage("message-2", user.id)
|
||||
const live = userMessage("message-4", { time: { created: 4 } })
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: assistant, parts: [] }])],
|
||||
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client, { retry: retryImmediately })
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
|
||||
store.apply({ type: "message.updated", properties: { info: live } })
|
||||
failed.reject(new Error("retry"))
|
||||
await loading
|
||||
store.apply({ type: "message.updated", properties: { info: live } })
|
||||
failed.reject(new Error("retry"))
|
||||
await loading
|
||||
|
||||
expect(store.data.message.child).toEqual([user, assistant, live])
|
||||
})
|
||||
expect(store.data.message.child).toEqual([user, assistant, live])
|
||||
})
|
||||
|
||||
test("preserves newer-page part events across a failed parent retry", async () => {
|
||||
const failed = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const assistant = assistantMessage("message-2", user.id)
|
||||
const stale = textPart(assistant.id, { text: "stale" })
|
||||
const live = { ...stale, text: "live" }
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: assistant, parts: [stale] }])],
|
||||
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client, { retry: retryImmediately })
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
test("preserves newer-page part events across a failed parent retry", async () => {
|
||||
const failed = deferredResponse()
|
||||
const user = userMessage("message-1")
|
||||
const assistant = assistantMessage("message-2", user.id)
|
||||
const stale = textPart(assistant.id, { text: "stale" })
|
||||
const live = { ...stale, text: "live" }
|
||||
const client = rootMessageClient(
|
||||
[response([{ info: assistant, parts: [stale] }])],
|
||||
[failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)],
|
||||
)
|
||||
const store = createServerSession(client, { retry: retryImmediately })
|
||||
const loading = store.sync("child")
|
||||
await client.rootRequested(1)
|
||||
|
||||
store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: live, time: 2 } })
|
||||
failed.reject(new Error("retry"))
|
||||
await loading
|
||||
store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: live, time: 2 } })
|
||||
failed.reject(new Error("retry"))
|
||||
await loading
|
||||
|
||||
expect(store.data.part[assistant.id]).toEqual([live])
|
||||
})
|
||||
expect(store.data.part[assistant.id]).toEqual([live])
|
||||
})
|
||||
})
|
||||
|
||||
test("merges live events into the initial page", async () => {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Binary } from "@opencode-ai/core/util/binary"
|
||||
import { retry } from "@opencode-ai/core/util/retry"
|
||||
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 { batch } from "solid-js"
|
||||
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 },
|
||||
]
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: message.id,
|
||||
type: "assistant",
|
||||
agent: message.agent ?? message.mode,
|
||||
model: { id: message.modelID, providerID: message.providerID, variant: message.variant },
|
||||
content: [],
|
||||
time: message.time,
|
||||
},
|
||||
]
|
||||
return [{
|
||||
id: message.id,
|
||||
type: "assistant",
|
||||
agent: message.agent ?? message.mode,
|
||||
model: { id: message.modelID, providerID: message.providerID, variant: message.variant },
|
||||
content: [],
|
||||
time: message.time,
|
||||
}]
|
||||
}
|
||||
|
||||
function needsOlderTurnRoot(source: readonly SessionMessageInfo[]) {
|
||||
@@ -241,7 +243,11 @@ export function createServerSession(
|
||||
const indexProjectedMessage = (message: Message) => {
|
||||
const current = data.session_message[message.sessionID] ?? []
|
||||
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) => {
|
||||
|
||||
@@ -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 { getFilename } from "@opencode-ai/core/util/path"
|
||||
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 {
|
||||
globalConfig: () => loadGlobalConfigQuery(scope),
|
||||
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),
|
||||
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, serverAPI.agent),
|
||||
references: (directory: PathKey) => loadReferencesQuery(scope, directory, serverAPI.reference),
|
||||
|
||||
@@ -268,13 +268,10 @@ function createWorkspaceTerminalSession(
|
||||
const index = store.all.findIndex((x) => x.id === id)
|
||||
const pty = store.all[index]
|
||||
if (!pty) return
|
||||
const data = await sdk.api.pty
|
||||
.create({ location, title: pty.title })
|
||||
.then((result) => result.data)
|
||||
.catch((error: unknown) => {
|
||||
console.error("Failed to clone terminal", error)
|
||||
return undefined
|
||||
})
|
||||
const data = await sdk.api.pty.create({ location, title: pty.title }).then((result) => result.data).catch((error: unknown) => {
|
||||
console.error("Failed to clone terminal", error)
|
||||
return undefined
|
||||
})
|
||||
if (!data?.id) return
|
||||
|
||||
const active = store.active === pty.id
|
||||
|
||||
@@ -45,7 +45,13 @@ import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
||||
import { TextField } from "@opencode-ai/ui/text-field"
|
||||
import { TextReveal } from "@opencode-ai/ui/text-reveal"
|
||||
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 { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||
import { Popover as KobaltePopover } from "@kobalte/core/popover"
|
||||
|
||||
@@ -54,9 +54,9 @@ describe("reviewDiffNeedsLoad", () => {
|
||||
patch: "@@ -0,0 +1 @@\n+value",
|
||||
}),
|
||||
).toBe(false)
|
||||
expect(reviewDiffNeedsLoad({ file: "empty.txt", patch: "", additions: 0, deletions: 0, status: "modified" })).toBe(
|
||||
false,
|
||||
)
|
||||
expect(
|
||||
reviewDiffNeedsLoad({ file: "empty.txt", patch: "", additions: 0, deletions: 0, status: "modified" }),
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
export type Project = Omit<ProjectListOutput[number], "canonical"> & { worktree: string }
|
||||
|
||||
@@ -119,15 +119,7 @@ function installPackage(name) {
|
||||
try {
|
||||
const result = childProcess.spawnSync(
|
||||
"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 },
|
||||
)
|
||||
if (result.status !== 0) return false
|
||||
@@ -158,9 +150,7 @@ function main() {
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to install OpenCode. Try manually installing ${names.map((name) => JSON.stringify(name)).join(" or ")}.`,
|
||||
)
|
||||
throw new Error(`Failed to install OpenCode. Try manually installing ${names.map((name) => JSON.stringify(name)).join(" or ")}.`)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -40,13 +40,15 @@ try {
|
||||
const token = encodeURIComponent(credential)
|
||||
const health = await waitForReady(info.url, headers)
|
||||
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), {
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
})
|
||||
const tokenHealth = await fetch(
|
||||
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")
|
||||
const tokenOpenApi = await fetch(new URL(`/openapi.json?auth_token=${token}`, info.url), {
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
})
|
||||
const tokenOpenApi = await fetch(
|
||||
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 ((await pluginIDs(info.url, headers)).includes("smoke")) throw new Error("Smoke plugin existed before creation")
|
||||
const plugin = path.join(root, ".opencode", "plugins", "smoke.ts")
|
||||
@@ -61,8 +63,7 @@ try {
|
||||
const unauthorizedOpenApi = await fetch(new URL("/openapi.json", info.url), {
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
})
|
||||
if (unauthorizedOpenApi.status !== 401)
|
||||
throw new Error("Compiled service exposed application routes without authentication")
|
||||
if (unauthorizedOpenApi.status !== 401) throw new Error("Compiled service exposed application routes without authentication")
|
||||
const unauthorizedStop = await fetch(new URL("/api/service/stop", info.url), {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
@@ -93,8 +94,7 @@ try {
|
||||
} finally {
|
||||
processes.forEach((process) => process.kill())
|
||||
await Promise.all(processes.map((process) => process.exited))
|
||||
if (failure)
|
||||
errors.push(fs.readFile(path.join(root, "data", "opencode", "log", "opencode.log"), "utf8").catch(() => ""))
|
||||
if (failure) errors.push(fs.readFile(path.join(root, "data", "opencode", "log", "opencode.log"), "utf8").catch(() => ""))
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
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]
|
||||
: [],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { AgentSideConnection, PermissionOption, ToolCallLocation } from "@agentclientprotocol/sdk"
|
||||
import type { AgentSideConnection, PermissionOption, ToolCallContent, ToolCallLocation } from "@agentclientprotocol/sdk"
|
||||
import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise"
|
||||
import { Patch } from "@opencode-ai/util/patch"
|
||||
import { Result } from "effect"
|
||||
import { isAbsolute, resolve } from "node:path"
|
||||
import { pendingToolCall, stringValue, toLocations, toToolKind, type ToolInput } from "./tool"
|
||||
|
||||
@@ -26,8 +28,9 @@ export async function replyPermission(input: {
|
||||
}) {
|
||||
const toolName = input.tool?.name ?? input.event.data.action
|
||||
const toolInput = { ...input.event.data.metadata, ...input.tool?.input }
|
||||
const previews = await permissionPreviews(toolName, toolInput, input.cwd)
|
||||
const toolCallID = input.event.data.source?.id ?? input.event.data.id
|
||||
const title = permissionTitle(toolName, toolInput, input.event.data.resources)
|
||||
const title = permissionTitle(toolName, toolInput, previews)
|
||||
const result = await input.connection
|
||||
.requestPermission({
|
||||
sessionId: input.clientSessionID ?? input.sessionID,
|
||||
@@ -41,7 +44,8 @@ export async function replyPermission(input: {
|
||||
},
|
||||
cwd: input.cwd,
|
||||
}),
|
||||
locations: permissionLocations(toolName, toolInput, input.event.data.resources, input.cwd),
|
||||
locations: permissionLocations(toolName, toolInput, input.event.data.resources, input.cwd, previews),
|
||||
...(previews.length > 0 ? { content: previews } : {}),
|
||||
},
|
||||
options,
|
||||
})
|
||||
@@ -90,8 +94,54 @@ export async function syncEditedFiles(input: {
|
||||
)
|
||||
}
|
||||
|
||||
function permissionTitle(toolName: string, input: ToolInput, resources: ReadonlyArray<string>) {
|
||||
if (toToolKind(toolName) === "edit" && resources.length > 1) return `${resources.length} files`
|
||||
async function permissionPreviews(toolName: string, input: ToolInput, cwd: string): Promise<ToolCallContent[]> {
|
||||
const tool = toolName.toLocaleLowerCase()
|
||||
if (tool === "patch" || tool === "apply_patch") return patchPreviews(input, cwd)
|
||||
const path = filePath(input)
|
||||
if (!path) return []
|
||||
const oldText = await readText(path, cwd)
|
||||
if (tool === "write") {
|
||||
const content = stringValue(input.content)
|
||||
return content === undefined ? [] : [{ type: "diff", path, oldText, newText: content }]
|
||||
}
|
||||
if (tool !== "edit") return []
|
||||
const oldString = stringValue(input.oldString)
|
||||
const newString = stringValue(input.newString)
|
||||
if (oldString === undefined || newString === undefined) return []
|
||||
const newText =
|
||||
input.replaceAll === true ? oldText.replaceAll(oldString, newString) : oldText.replace(oldString, newString)
|
||||
return [{ type: "diff", path, oldText, newText }]
|
||||
}
|
||||
|
||||
async function patchPreviews(input: ToolInput, cwd: string): Promise<ToolCallContent[]> {
|
||||
const patchText = stringValue(input.patchText)
|
||||
if (!patchText) return []
|
||||
try {
|
||||
const parsed = Patch.parse(patchText)
|
||||
if (Result.isFailure(parsed)) return []
|
||||
return await Promise.all(
|
||||
parsed.success.map(async (hunk): Promise<ToolCallContent> => {
|
||||
const oldText = hunk.type === "add" ? "" : await readText(hunk.path, cwd)
|
||||
if (hunk.type === "add") {
|
||||
const newText = hunk.contents.endsWith("\n") || hunk.contents === "" ? hunk.contents : `${hunk.contents}\n`
|
||||
return { type: "diff", path: hunk.path, oldText, newText }
|
||||
}
|
||||
if (hunk.type === "delete") return { type: "diff", path: hunk.path, oldText, newText: "" }
|
||||
return {
|
||||
type: "diff",
|
||||
path: hunk.movePath ?? hunk.path,
|
||||
oldText,
|
||||
newText: Patch.derive(hunk.path, hunk.chunks, oldText).content,
|
||||
}
|
||||
}),
|
||||
)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function permissionTitle(toolName: string, input: ToolInput, previews: ReadonlyArray<ToolCallContent>) {
|
||||
if (previews.length > 1) return `${previews.length} files`
|
||||
switch (toolName.toLocaleLowerCase()) {
|
||||
case "external_directory":
|
||||
return stringValue(input.description) ?? stringValue(input.command) ?? stringValue(input.parentDir)
|
||||
@@ -107,7 +157,7 @@ function permissionTitle(toolName: string, input: ToolInput, resources: Readonly
|
||||
case "write":
|
||||
case "patch":
|
||||
case "apply_patch":
|
||||
return filePath(input)
|
||||
return filePath(input) ?? (previews[0]?.type === "diff" ? previews[0].path : undefined)
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
@@ -118,12 +168,21 @@ function permissionLocations(
|
||||
input: ToolInput,
|
||||
resources: ReadonlyArray<string>,
|
||||
cwd: string,
|
||||
previews: ReadonlyArray<ToolCallContent>,
|
||||
): ToolCallLocation[] {
|
||||
const paths = previews.flatMap((preview) => (preview.type === "diff" ? [preview.path] : []))
|
||||
if (paths.length > 0) return [...new Set(paths)].map((path) => ({ path }))
|
||||
const locations = toLocations(toolName, input, cwd)
|
||||
if (locations.length > 0) return locations
|
||||
return resources.filter((resource) => resource !== "*").map((path) => ({ path }))
|
||||
}
|
||||
|
||||
function readText(path: string, cwd: string) {
|
||||
return Bun.file(resolvePath(path, cwd))
|
||||
.text()
|
||||
.catch(() => "")
|
||||
}
|
||||
|
||||
function filePath(input: ToolInput) {
|
||||
return stringValue(input.path) ?? stringValue(input.filePath) ?? stringValue(input.filepath)
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ export default Runtime.handler(
|
||||
return response.text()
|
||||
})
|
||||
: Bun.file(input.file).text(),
|
||||
catch: (cause) =>
|
||||
new Error(`Failed to read session data: ${cause instanceof Error ? cause.message : String(cause)}`),
|
||||
catch: (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 encoded = Schema.encodeSync(SessionTransfer.Data)(data)
|
||||
@@ -53,9 +52,9 @@ export default Runtime.handler(
|
||||
return
|
||||
}
|
||||
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 })))(
|
||||
yield* Effect.promise(() => response.text()),
|
||||
)
|
||||
const imported = yield* Schema.decodeUnknownEffect(
|
||||
Schema.fromJsonString(Schema.Struct({ data: Session.Info })),
|
||||
)(yield* Effect.promise(() => response.text()))
|
||||
process.stdout.write(`Imported session: ${imported.data.id}${EOL}`)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -36,6 +36,8 @@ export default Runtime.handler(
|
||||
|
||||
const hostname = new URL(endpoint.url).hostname
|
||||
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}`,
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -48,16 +48,12 @@ export function migrateV1(legacy: TuiConfigV1.Info | undefined, kv: Record<strin
|
||||
const attentionSoundPack = kv.attention_sound_pack
|
||||
const diffView = kv.diff_viewer_view ?? (legacy?.diff_style === "stacked" ? "unified" : undefined)
|
||||
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 {
|
||||
...(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 }),
|
||||
...(plugins.length ? { plugins } : {}),
|
||||
|
||||
@@ -468,7 +468,10 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
v1InvalidOutput = true
|
||||
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 === "ignore") return yield* Service.ensure({ ...options, version: undefined })
|
||||
|
||||
|
||||
@@ -413,7 +413,9 @@ function UpdateFooter(props: {
|
||||
const completion = smoothstep(headerFade.progress())
|
||||
return Array.from({ length: width }, (_, index) => {
|
||||
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 {
|
||||
char: success || index < filled ? "━" : "·",
|
||||
color: success ? blend(color, colors.accent, completion) : color,
|
||||
|
||||
@@ -52,10 +52,9 @@ export const layer = Layer.effect(
|
||||
|
||||
const readPolicy = Effect.fnUntraced(function* () {
|
||||
const values = yield* Effect.forEach(["config.json", "opencode.json", "opencode.jsonc"], (name) =>
|
||||
fs.readFileString(path.join(global.config, name)).pipe(
|
||||
Effect.map(decodePolicy),
|
||||
Effect.catch(() => Effect.succeed(undefined)),
|
||||
),
|
||||
fs
|
||||
.readFileString(path.join(global.config, name))
|
||||
.pipe(Effect.map(decodePolicy), Effect.catch(() => Effect.succeed(undefined))),
|
||||
)
|
||||
return values.findLast((value) => value !== undefined) ?? true
|
||||
})
|
||||
@@ -95,10 +94,10 @@ export const layer = Layer.effect(
|
||||
const latest = Effect.fnUntraced(function* () {
|
||||
const response = yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
fetch(`https://update.opencode.ai/api/${encodeURIComponent(channel)}/cli/npm`, {
|
||||
headers: { "User-Agent": `opencode/${OPENCODE_VERSION}` },
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
}),
|
||||
fetch(
|
||||
`https://update.opencode.ai/api/${encodeURIComponent(channel)}/cli/npm`,
|
||||
{ headers: { "User-Agent": `opencode/${OPENCODE_VERSION}` }, signal: AbortSignal.timeout(10_000) },
|
||||
),
|
||||
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}`))
|
||||
@@ -119,7 +118,7 @@ export const layer = Layer.effect(
|
||||
pnpm: ["pnpm", "install", "--global", target],
|
||||
yarn: ["yarn", "global", "add", target],
|
||||
}
|
||||
const result = yield* method === "bun"
|
||||
const result = yield* (method === "bun"
|
||||
? Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
// Bun does not prune old versions from its shared package cache.
|
||||
@@ -128,38 +127,38 @@ export const layer = Layer.effect(
|
||||
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
|
||||
return yield* Effect.fail(new Error(result.stderr.trim() || `Failed to update with ${method}`))
|
||||
})
|
||||
|
||||
const check = Effect.fn("cli.updater.check")(
|
||||
function* () {
|
||||
if (OPENCODE_LOCAL || ["1", "true"].includes(process.env.OPENCODE_DISABLE_AUTOUPDATE?.toLowerCase() ?? ""))
|
||||
return yield* Effect.logInfo("update check skipped", {
|
||||
reason: OPENCODE_LOCAL ? "local-install" : "disabled",
|
||||
version: OPENCODE_VERSION,
|
||||
channel: OPENCODE_CHANNEL,
|
||||
})
|
||||
const policy = yield* readPolicy()
|
||||
if (policy === false) return yield* Effect.logInfo("update check skipped", { reason: "policy-disabled" })
|
||||
|
||||
return yield* Effect.gen(function* () {
|
||||
const version = yield* latest()
|
||||
yield* Effect.logInfo("update check", {
|
||||
current: OPENCODE_VERSION,
|
||||
latest: version,
|
||||
})
|
||||
const next = action(OPENCODE_VERSION, version, policy)
|
||||
if (next === "none") return yield* Effect.logInfo("update check done", { action: "up-to-date" })
|
||||
const detected = yield* method()
|
||||
if (!detected) return yield* Effect.logWarning("automatic update skipped: installation method not found")
|
||||
yield* upgrade(detected, version)
|
||||
yield* Effect.logInfo("updated OpenCode", { from: OPENCODE_VERSION, to: version, method: detected })
|
||||
const check = Effect.fn("cli.updater.check")(function* () {
|
||||
if (
|
||||
OPENCODE_LOCAL ||
|
||||
["1", "true"].includes(process.env.OPENCODE_DISABLE_AUTOUPDATE?.toLowerCase() ?? "")
|
||||
)
|
||||
return yield* Effect.logInfo("update check skipped", {
|
||||
reason: OPENCODE_LOCAL ? "local-install" : "disabled",
|
||||
version: OPENCODE_VERSION,
|
||||
channel: OPENCODE_CHANNEL,
|
||||
})
|
||||
},
|
||||
Effect.catchCause((cause) => Effect.logWarning("automatic update failed", { cause })),
|
||||
)
|
||||
const policy = yield* readPolicy()
|
||||
if (policy === false) return yield* Effect.logInfo("update check skipped", { reason: "policy-disabled" })
|
||||
|
||||
return yield* Effect.gen(function* () {
|
||||
const version = yield* latest()
|
||||
yield* Effect.logInfo("update check", {
|
||||
current: OPENCODE_VERSION,
|
||||
latest: version,
|
||||
})
|
||||
const next = action(OPENCODE_VERSION, version, policy)
|
||||
if (next === "none") return yield* Effect.logInfo("update check done", { action: "up-to-date" })
|
||||
const detected = yield* method()
|
||||
if (!detected) return yield* Effect.logWarning("automatic update skipped: installation method not found")
|
||||
yield* upgrade(detected, version)
|
||||
yield* Effect.logInfo("updated OpenCode", { from: OPENCODE_VERSION, to: version, method: detected })
|
||||
})
|
||||
}, Effect.catchCause((cause) => Effect.logWarning("automatic update failed", { cause })))
|
||||
|
||||
return Service.of({ check })
|
||||
}),
|
||||
|
||||
@@ -141,7 +141,7 @@ export async function createTimelineHost(): Promise<TimelineHost> {
|
||||
if (closeTask) return closeTask
|
||||
closed = true
|
||||
closeTask = (async () => {
|
||||
await active?.catch(() => {})
|
||||
await active?.catch(() => { })
|
||||
signals.forEach((signal) => process.off(signal, cancel))
|
||||
})()
|
||||
return closeTask
|
||||
@@ -211,7 +211,7 @@ export async function createTimelineHost(): Promise<TimelineHost> {
|
||||
if (closeTask) return closeTask
|
||||
closed = true
|
||||
closeTask = (async () => {
|
||||
await active?.catch(() => {})
|
||||
await active?.catch(() => { })
|
||||
try {
|
||||
await shutdown(activeRenderer)
|
||||
await bounded(renderTask)
|
||||
|
||||
@@ -211,7 +211,7 @@ describe("acp permission behavior", () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("authorizes edit resources and syncs the completed file", async () => {
|
||||
test("previews edits during approval and syncs the completed file", async () => {
|
||||
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-acp-permission-"))
|
||||
const file = path.join(cwd, "file.ts")
|
||||
await fs.writeFile(file, "before")
|
||||
@@ -240,7 +240,6 @@ describe("acp permission behavior", () => {
|
||||
send(
|
||||
permissionAsked("ses_edit", "perm_edit", {
|
||||
action: "edit",
|
||||
resources: ["file.ts"],
|
||||
source: { type: "tool", messageID: "msg_edit", id: "call_edit" },
|
||||
}),
|
||||
)
|
||||
@@ -279,8 +278,8 @@ describe("acp permission behavior", () => {
|
||||
title: "file.ts",
|
||||
kind: "edit",
|
||||
locations: [{ path: "file.ts" }],
|
||||
content: [{ type: "diff", path: "file.ts", oldText: "before", newText: "after" }],
|
||||
})
|
||||
expect(permissionRequests[0]?.toolCall.content).toBeUndefined()
|
||||
expect(writes).toEqual([{ sessionId: "ses_edit", path: file, content: "after" }])
|
||||
} finally {
|
||||
await fixture.stop()
|
||||
@@ -288,7 +287,7 @@ describe("acp permission behavior", () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("authorizes and syncs each file in a patch", async () => {
|
||||
test("previews and syncs each file in a patch", async () => {
|
||||
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-acp-patch-permission-"))
|
||||
await Promise.all([
|
||||
fs.writeFile(path.join(cwd, "first.ts"), "one\n"),
|
||||
@@ -331,7 +330,6 @@ describe("acp permission behavior", () => {
|
||||
send(
|
||||
permissionAsked("ses_patch", "perm_patch", {
|
||||
action: "edit",
|
||||
resources: ["first.ts", "second.ts"],
|
||||
source: { type: "tool", messageID: "msg_patch", id: "call_patch" },
|
||||
}),
|
||||
)
|
||||
@@ -373,8 +371,11 @@ describe("acp permission behavior", () => {
|
||||
title: "2 files",
|
||||
kind: "edit",
|
||||
locations: [{ path: "first.ts" }, { path: "second.ts" }],
|
||||
content: [
|
||||
{ type: "diff", path: "first.ts", oldText: "one\n", newText: "two\n" },
|
||||
{ type: "diff", path: "second.ts", oldText: "alpha\n", newText: "beta\n" },
|
||||
],
|
||||
})
|
||||
expect(permissionRequests[0]?.toolCall.content).toBeUndefined()
|
||||
expect(writes.toSorted((a, b) => a.path.localeCompare(b.path))).toEqual([
|
||||
{ sessionId: "ses_patch", path: path.join(cwd, "first.ts"), content: "two\n" },
|
||||
{ sessionId: "ses_patch", path: path.join(cwd, "second.ts"), content: "beta\n" },
|
||||
@@ -555,7 +556,6 @@ function permissionAsked(
|
||||
id: string,
|
||||
input: {
|
||||
readonly action?: string
|
||||
readonly resources?: ReadonlyArray<string>
|
||||
readonly metadata?: Record<string, unknown>
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly id: string }
|
||||
} = {},
|
||||
@@ -564,7 +564,7 @@ function permissionAsked(
|
||||
id,
|
||||
sessionID,
|
||||
action: input.action ?? "shell",
|
||||
resources: [...(input.resources ?? ["*"])],
|
||||
resources: ["*"],
|
||||
metadata: input.metadata ?? { command: "printf hello" },
|
||||
...(input.source ? { source: input.source } : {}),
|
||||
})
|
||||
|
||||
@@ -102,7 +102,9 @@ test("migrates before the first update and does not remigrate afterward", async
|
||||
draft.animations = 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()
|
||||
}),
|
||||
)
|
||||
@@ -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 () => {
|
||||
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 {
|
||||
const config = await run(
|
||||
|
||||
@@ -17,9 +17,7 @@ export default defineScript({
|
||||
const session = `mini-stage2-${process.pid}`
|
||||
const snapshots = path.join(artifacts, "mini-stage2")
|
||||
const explicitDirectory = path.join(artifacts, "explicit-model")
|
||||
yield* Effect.promise(() =>
|
||||
Promise.all([snapshots, explicitDirectory].map((dir) => mkdir(dir, { recursive: true }))),
|
||||
)
|
||||
yield* Effect.promise(() => Promise.all([snapshots, explicitDirectory].map((dir) => mkdir(dir, { recursive: true }))))
|
||||
/** @param {string} directory @param {string | undefined} model */
|
||||
const mini = (directory, model) => [
|
||||
"env",
|
||||
@@ -171,7 +169,14 @@ export default defineScript({
|
||||
|
||||
yield* Effect.promise(() => tmux(["clear-history", "-t", session]))
|
||||
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))
|
||||
yield* Effect.promise(() => Bun.write(path.join(snapshots, "07-explicit-model.txt"), explicitModel))
|
||||
|
||||
@@ -159,7 +159,14 @@ test("import validates a file and sends it to the resolved location", async () =
|
||||
})
|
||||
|
||||
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(stdout).toBe(`Imported session: ${info.id}${os.EOL}`)
|
||||
|
||||
@@ -3,7 +3,13 @@ import { readFile } from "node:fs/promises"
|
||||
import { createRequire } from "node:module"
|
||||
import { defineConfig, type Plugin, type UserConfig } from "vite"
|
||||
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
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
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 { Command } from "@opencode-ai/schema/command"
|
||||
import { Config } from "@opencode-ai/schema/config"
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -180,7 +180,6 @@ export type Endpoint5_12Input = {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<PromptInput.FileAttachment> | undefined
|
||||
readonly agents?: ReadonlyArray<AgentAttachment> | undefined
|
||||
readonly skills?: ReadonlyArray<PromptInput.SkillAttachment> | undefined
|
||||
readonly metadata?: { readonly [x: string]: unknown } | undefined
|
||||
readonly delivery?: "steer" | "queue" | undefined
|
||||
readonly resume?: boolean | undefined
|
||||
@@ -197,7 +196,6 @@ export type Endpoint5_13Input = {
|
||||
readonly model?: Model.Ref | undefined
|
||||
readonly files?: ReadonlyArray<PromptInput.FileAttachment> | undefined
|
||||
readonly agents?: ReadonlyArray<AgentAttachment> | undefined
|
||||
readonly skills?: ReadonlyArray<PromptInput.SkillAttachment> | undefined
|
||||
readonly delivery?: "steer" | "queue" | undefined
|
||||
readonly resume?: boolean | undefined
|
||||
}
|
||||
|
||||
@@ -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"],
|
||||
files: input["files"],
|
||||
agents: input["agents"],
|
||||
skills: input["skills"],
|
||||
metadata: input["metadata"],
|
||||
delivery: input["delivery"],
|
||||
resume: input["resume"],
|
||||
@@ -435,7 +434,6 @@ const Endpoint5_13 = (raw: RawClient["server.session"]) => (input: Endpoint5_13I
|
||||
model: input["model"],
|
||||
files: input["files"],
|
||||
agents: input["agents"],
|
||||
skills: input["skills"],
|
||||
delivery: input["delivery"],
|
||||
resume: input["resume"],
|
||||
},
|
||||
|
||||
@@ -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"],
|
||||
files: input["files"],
|
||||
agents: input["agents"],
|
||||
skills: input["skills"],
|
||||
metadata: input["metadata"],
|
||||
delivery: input["delivery"],
|
||||
resume: input["resume"],
|
||||
@@ -639,7 +638,6 @@ export function make(options: ClientOptions) {
|
||||
model: input["model"],
|
||||
files: input["files"],
|
||||
agents: input["agents"],
|
||||
skills: input["skills"],
|
||||
delivery: input["delivery"],
|
||||
resume: input["resume"],
|
||||
},
|
||||
|
||||
@@ -1080,8 +1080,6 @@ export type PromptFileAttachment = {
|
||||
|
||||
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 SessionMessageAssistantReasoning = {
|
||||
@@ -1567,7 +1565,6 @@ export type SessionMessageUser = {
|
||||
text: string
|
||||
files?: Array<PromptFileAttachment>
|
||||
agents?: Array<PromptAgentAttachment>
|
||||
skills?: Array<PromptSkillAttachment>
|
||||
type: "user"
|
||||
}
|
||||
|
||||
@@ -1575,7 +1572,6 @@ export type SessionPendingUserData = {
|
||||
text: string
|
||||
files?: Array<PromptFileAttachment>
|
||||
agents?: Array<PromptAgentAttachment>
|
||||
skills?: Array<PromptSkillAttachment>
|
||||
metadata?: { [x: string]: JsonValue }
|
||||
}
|
||||
|
||||
@@ -1583,7 +1579,6 @@ export type SessionPendingUserData1 = {
|
||||
text: string
|
||||
files?: Array<PromptFileAttachment>
|
||||
agents?: Array<PromptAgentAttachment>
|
||||
skills?: Array<PromptSkillAttachment>
|
||||
metadata?: { [x: string]: any }
|
||||
}
|
||||
|
||||
@@ -2584,12 +2579,6 @@ export type SessionImportInput = {
|
||||
readonly name: 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"
|
||||
}
|
||||
| {
|
||||
@@ -2835,12 +2824,6 @@ export type SessionImportInput = {
|
||||
readonly name: 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"
|
||||
}
|
||||
| {
|
||||
@@ -3086,12 +3069,6 @@ export type SessionImportInput = {
|
||||
readonly name: 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"
|
||||
}
|
||||
| {
|
||||
@@ -3343,10 +3320,6 @@ export type SessionPromptInput = {
|
||||
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
|
||||
@@ -3364,10 +3337,6 @@ export type SessionPromptInput = {
|
||||
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
|
||||
@@ -3385,10 +3354,6 @@ export type SessionPromptInput = {
|
||||
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
|
||||
@@ -3406,35 +3371,10 @@ export type SessionPromptInput = {
|
||||
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
|
||||
}["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 id?: string | null
|
||||
readonly text: string
|
||||
@@ -3448,10 +3388,6 @@ export type SessionPromptInput = {
|
||||
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
|
||||
@@ -3469,10 +3405,6 @@ export type SessionPromptInput = {
|
||||
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
|
||||
@@ -3490,10 +3422,6 @@ export type SessionPromptInput = {
|
||||
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
|
||||
@@ -3520,10 +3448,6 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["id"]
|
||||
@@ -3543,10 +3467,6 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["command"]
|
||||
@@ -3566,10 +3486,6 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["arguments"]
|
||||
@@ -3589,10 +3505,6 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["agent"]
|
||||
@@ -3612,10 +3524,6 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["model"]
|
||||
@@ -3635,10 +3543,6 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["files"]
|
||||
@@ -3658,36 +3562,9 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["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 id?: string | null
|
||||
readonly command: string
|
||||
@@ -3704,10 +3581,6 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["delivery"]
|
||||
@@ -3727,10 +3600,6 @@ export type SessionCommandInput = {
|
||||
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
|
||||
}["resume"]
|
||||
|
||||
@@ -66,7 +66,8 @@ export async function ensure(options: EnsureOptions = {}): Promise<Endpoint> {
|
||||
if (registration.timedOut && registration.info !== undefined) {
|
||||
timeouts = {
|
||||
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) {
|
||||
announce("missing")
|
||||
@@ -187,7 +188,8 @@ async function probeResult(info: Info, allowLegacy = false) {
|
||||
const body = result.value.body
|
||||
if (body !== undefined && "version" in body && "pid" in body) {
|
||||
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 {
|
||||
service: {
|
||||
info,
|
||||
|
||||
@@ -9,7 +9,12 @@ if (mode === "record-start") {
|
||||
}
|
||||
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")
|
||||
const owner = await writeFile(registration + ".owner", String(process.pid), { flag: "wx" })
|
||||
.then(() => true)
|
||||
@@ -54,7 +59,8 @@ const server = Bun.serve({
|
||||
if (mode === "legacy") return Response.json({ healthy: true })
|
||||
if (mode === "starting" && !(await Bun.file(registration + ".release").exists()))
|
||||
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")
|
||||
return Response.json({ healthy: true, version, pid: process.pid })
|
||||
return Response.json({ healthy: true, version, pid: process.pid })
|
||||
|
||||
+20
-5
@@ -93,10 +93,16 @@
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enum": ["admin", "member"]
|
||||
"enum": [
|
||||
"admin",
|
||||
"member"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["name", "email"],
|
||||
"required": [
|
||||
"name",
|
||||
"email"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
@@ -137,7 +143,9 @@
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": ["query"],
|
||||
"required": [
|
||||
"query"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
@@ -208,10 +216,17 @@
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enum": ["admin", "member"]
|
||||
"enum": [
|
||||
"admin",
|
||||
"member"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "email"],
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"email"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
||||
+4475
-1104
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 =
|
||||
"\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 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"
|
||||
const lineTerminators = "\u000A\u000D\u2028\u2029"
|
||||
const trim = (file: string, input: string, expected: string) =>
|
||||
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_"),
|
||||
])
|
||||
|
||||
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"
|
||||
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"
|
||||
add("test/built-ins/String/prototype/trimStart/this-value-whitespace.js", "trimStart", [
|
||||
assertion(
|
||||
"all whitespace",
|
||||
directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace,
|
||||
"a" + directionalWhitespace + "b" + directionalWhitespace,
|
||||
),
|
||||
assertion("all whitespace", directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace, "a" + directionalWhitespace + "b" + directionalWhitespace),
|
||||
])
|
||||
add("test/built-ins/String/prototype/trimStart/this-value-line-terminator.js", "trimStart", [
|
||||
assertion(
|
||||
"all line terminators",
|
||||
lineTerminators + "a" + lineTerminators + "b" + lineTerminators,
|
||||
"a" + lineTerminators + "b" + lineTerminators,
|
||||
),
|
||||
assertion("all line terminators", lineTerminators + "a" + lineTerminators + "b" + lineTerminators, "a" + lineTerminators + "b" + lineTerminators),
|
||||
])
|
||||
add("test/built-ins/String/prototype/trimEnd/this-value-whitespace.js", "trimEnd", [
|
||||
assertion(
|
||||
"all whitespace",
|
||||
directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace,
|
||||
directionalWhitespace + "a" + directionalWhitespace + "b",
|
||||
),
|
||||
assertion("all whitespace", directionalWhitespace + "a" + directionalWhitespace + "b" + directionalWhitespace, directionalWhitespace + "a" + directionalWhitespace + "b"),
|
||||
])
|
||||
add("test/built-ins/String/prototype/trimEnd/this-value-line-terminator.js", "trimEnd", [
|
||||
assertion(
|
||||
"all line terminators",
|
||||
lineTerminators + "a" + lineTerminators + "b" + lineTerminators,
|
||||
lineTerminators + "a" + lineTerminators + "b",
|
||||
),
|
||||
assertion("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", [
|
||||
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", [
|
||||
assertion("fraction truncates to zero", "abc", "", [0.9]),
|
||||
])
|
||||
add("test/built-ins/String/prototype/padStart/fill-string-empty.js", "padStart", [
|
||||
assertion("empty fill", "abc", "abc", [5, ""]),
|
||||
])
|
||||
add("test/built-ins/String/prototype/padStart/fill-string-empty.js", "padStart", [assertion("empty fill", "abc", "abc", [5, ""])])
|
||||
add("test/built-ins/String/prototype/padStart/normal-operation.js", "padStart", [
|
||||
assertion("truncated multi-character fill", "abc", "defdabc", [7, "def"]),
|
||||
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("fraction truncates", "abc", "abc", [3.9999, "def"]),
|
||||
])
|
||||
add("test/built-ins/String/prototype/padEnd/fill-string-empty.js", "padEnd", [
|
||||
assertion("empty fill", "abc", "abc", [5, ""]),
|
||||
])
|
||||
add("test/built-ins/String/prototype/padEnd/fill-string-empty.js", "padEnd", [assertion("empty fill", "abc", "abc", [5, ""])])
|
||||
add("test/built-ins/String/prototype/padEnd/normal-operation.js", "padEnd", [
|
||||
assertion("truncated multi-character fill", "abc", "abcdefd", [7, "def"]),
|
||||
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_T7.js", "charAt", [
|
||||
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_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/S15.5.4.4_A1_T7.js", "charAt", [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_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_A2.js", "charAt", [
|
||||
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]),
|
||||
])
|
||||
|
||||
add("test/built-ins/String/prototype/charCodeAt/S15.5.4.5_A1_T4.js", "charCodeAt", [
|
||||
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_T4.js", "charCodeAt", [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/pos-rounding.js", "charCodeAt", [
|
||||
assertion("-0.99999", "abc", 0x61, [-0.99999]),
|
||||
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", [
|
||||
assertion("a", "abc", 97, [0]),
|
||||
assertion("b", "abc", 98, [1]),
|
||||
assertion("c", "abc", 99, [2]),
|
||||
assertion("ordinary BMP", "\uAAAA\uBBBB", 0xaaaa, [0]),
|
||||
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]),
|
||||
assertion("a", "abc", 97, [0]), assertion("b", "abc", 98, [1]), assertion("c", "abc", 99, [2]),
|
||||
assertion("ordinary BMP", "\uAAAA\uBBBB", 0xaaaa, [0]), 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", [
|
||||
assertion("D800 before DBFF", "\uD800\uDBFF", 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("DBFF before DBFF", "\uDBFF\uDBFF", 0xdbff, [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("D800 before DBFF", "\uD800\uDBFF", 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("DBFF before DBFF", "\uDBFF\uDBFF", 0xdbff, [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]),
|
||||
])
|
||||
add("test/built-ins/String/prototype/codePointAt/return-utf16-decode.js", "codePointAt", [
|
||||
assertion("U+10000", "\uD800\uDC00", 65536, [0]),
|
||||
assertion("U+101D0", "\uD800\uDDD0", 66000, [0]),
|
||||
assertion("U+103FF", "\uD800\uDFFF", 66559, [0]),
|
||||
assertion("U+BA800", "\uDAAA\uDC00", 763904, [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+10000", "\uD800\uDC00", 65536, [0]), assertion("U+101D0", "\uD800\uDDD0", 66000, [0]),
|
||||
assertion("U+103FF", "\uD800\uDFFF", 66559, [0]), assertion("U+BA800", "\uDAAA\uDC00", 763904, [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]),
|
||||
])
|
||||
add("test/built-ins/String/prototype/codePointAt/return-code-unit-coerced-position.js", "codePointAt", [
|
||||
assertion("NaN", "\uD800\uDC00", 65536, [NaN]),
|
||||
assertion("undefined", "\uD800\uDC00", 65536, [undefined]),
|
||||
assertion("NaN", "\uD800\uDC00", 65536, [NaN]), assertion("undefined", "\uD800\uDC00", 65536, [undefined]),
|
||||
])
|
||||
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 infinity", input: "abc", args: [-Infinity], outcome: "undefined" },
|
||||
])
|
||||
add(
|
||||
"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: "greater than size", input: "abc", args: [4], outcome: "undefined" },
|
||||
{ label: "positive infinity", input: "abc", args: [Infinity], outcome: "undefined" },
|
||||
],
|
||||
)
|
||||
add("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: "greater than size", input: "abc", args: [4], outcome: "undefined" },
|
||||
{ label: "positive infinity", input: "abc", args: [Infinity], outcome: "undefined" },
|
||||
])
|
||||
|
||||
add("test/built-ins/String/prototype/at/returns-code-unit.js", "at", [
|
||||
assertion("position 0", "12\uD80034", "1", [0]),
|
||||
assertion("position 1", "12\uD80034", "2", [1]),
|
||||
assertion("unpaired surrogate", "12\uD80034", "\uD800", [2]),
|
||||
assertion("position 3", "12\uD80034", "3", [3]),
|
||||
assertion("position 0", "12\uD80034", "1", [0]), assertion("position 1", "12\uD80034", "2", [1]),
|
||||
assertion("unpaired surrogate", "12\uD80034", "\uD800", [2]), assertion("position 3", "12\uD80034", "3", [3]),
|
||||
assertion("position 4", "12\uD80034", "4", [4]),
|
||||
])
|
||||
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]),
|
||||
),
|
||||
)
|
||||
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])))
|
||||
add("test/built-ins/String/prototype/at/returns-item-relative-index.js", "at", [
|
||||
assertion("zero", "12345", "1", [0]),
|
||||
assertion("negative one", "12345", "5", [-1]),
|
||||
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]),
|
||||
assertion("zero", "12345", "1", [0]), assertion("negative one", "12345", "5", [-1]),
|
||||
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/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", [
|
||||
assertion("empty string", "", ""),
|
||||
assertion("non-empty string", "str", "str"),
|
||||
assertion("empty string", "", ""), assertion("non-empty string", "str", "str"),
|
||||
])
|
||||
|
||||
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("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("NFKD long", "\u00C5\u2ADC\u0958\u2126\u0344", "A\u030A\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", [
|
||||
"NFKD",
|
||||
]),
|
||||
assertion("NFKD long", "\u00C5\u2ADC\u0958\u2126\u0344", "A\u030A\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", ["NFKD"]),
|
||||
])
|
||||
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("undefined", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", [
|
||||
undefined,
|
||||
]),
|
||||
assertion("undefined", "\u00C5\u2ADC\u0958\u2126\u0344", "\xC5\u2ADD\u0338\u0915\u093C\u03A9\u0308\u0301", [undefined]),
|
||||
])
|
||||
add("test/built-ins/String/prototype/normalize/form-is-not-valid-throws.js", "normalize", [
|
||||
{ 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", [
|
||||
assertion("D70", "o\u0308", 0, ["ö"]),
|
||||
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("breve after diaeresis", "ä\u0306", 0, ["a\u0308\u0306"]),
|
||||
assertion("diaeresis after breve", "ă\u0308", 0, ["a\u0306\u0308"]),
|
||||
assertion("Hangul", "\u1111\u1171\u11B6", 0, ["퓛"]),
|
||||
assertion("angstrom compatibility", "Å", 0, ["Å"]),
|
||||
assertion("angstrom decomposed", "Å", 0, ["A\u030A"]),
|
||||
assertion("reordered horn and dot", "x\u031B\u0323", 0, ["x\u0323\u031B"]),
|
||||
assertion("Vietnamese precomposed 1", "ự", 0, ["ụ\u031B"]),
|
||||
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("D70", "o\u0308", 0, ["ö"]), 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("breve after diaeresis", "ä\u0306", 0, ["a\u0308\u0306"]), assertion("diaeresis after breve", "ă\u0308", 0, ["a\u0306\u0308"]),
|
||||
assertion("Hangul", "\u1111\u1171\u11B6", 0, ["퓛"]), assertion("angstrom compatibility", "Å", 0, ["Å"]),
|
||||
assertion("angstrom decomposed", "Å", 0, ["A\u030A"]), assertion("reordered horn and dot", "x\u031B\u0323", 0, ["x\u0323\u031B"]),
|
||||
assertion("Vietnamese precomposed 1", "ự", 0, ["ụ\u031B"]), 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"]),
|
||||
])
|
||||
|
||||
add(
|
||||
"test/built-ins/String/fromCharCode/S15.5.3.2_A2.js",
|
||||
"fromCharCode",
|
||||
[{ label: "no arguments", expected: "" }],
|
||||
true,
|
||||
)
|
||||
add(
|
||||
"test/built-ins/String/fromCharCode/S15.5.3.2_A3_T1.js",
|
||||
"fromCharCode",
|
||||
[{ label: "ABBA", args: [65, 66, 66, 65], expected: "ABBA" }],
|
||||
true,
|
||||
)
|
||||
add(
|
||||
"test/built-ins/String/fromCharCode/S9.7_A1.js",
|
||||
"fromCharCode",
|
||||
[
|
||||
{ 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("test/built-ins/String/fromCharCode/S15.5.3.2_A2.js", "fromCharCode", [{ label: "no arguments", expected: "" }], true)
|
||||
add("test/built-ins/String/fromCharCode/S15.5.3.2_A3_T1.js", "fromCharCode", [{ label: "ABBA", args: [65, 66, 66, 65], expected: "ABBA" }], true)
|
||||
add("test/built-ins/String/fromCharCode/S9.7_A1.js", "fromCharCode", [
|
||||
{ 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(
|
||||
"test/built-ins/String/fromCodePoint/arguments-is-empty.js",
|
||||
"fromCodePoint",
|
||||
[{ label: "no arguments", expected: "" }],
|
||||
true,
|
||||
)
|
||||
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: "maximum code point", args: [1114111], expected: "\uDBFF\uDFFF" },
|
||||
],
|
||||
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" },
|
||||
],
|
||||
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,
|
||||
)
|
||||
add("test/built-ins/String/fromCodePoint/arguments-is-empty.js", "fromCodePoint", [{ label: "no arguments", expected: "" }], true)
|
||||
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: "maximum code point", args: [1114111], expected: "\uDBFF\uDFFF" },
|
||||
], 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" },
|
||||
], 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", () => {
|
||||
for (const vector of vectors) {
|
||||
@@ -734,18 +558,16 @@ describe("Test262-adapted core String behavior", () => {
|
||||
const expression = vector.static
|
||||
? `String.${vector.method}(${args})`
|
||||
: `${JSON.stringify(item.input)}.${vector.method}(${args})`
|
||||
const observed =
|
||||
vector.static && vector.method === "fromCharCode" && typeof item.expected === "number"
|
||||
? `${expression}.charCodeAt(0)`
|
||||
: expression
|
||||
const checked =
|
||||
item.outcome === "undefined"
|
||||
? `${observed} === undefined`
|
||||
: item.outcome === "length"
|
||||
? `${observed}.length`
|
||||
: item.outcome === "RangeError"
|
||||
? `(() => { try { ${observed}; return false } catch (error) { return error instanceof RangeError } })()`
|
||||
: observed
|
||||
const observed = vector.static && vector.method === "fromCharCode" && typeof item.expected === "number"
|
||||
? `${expression}.charCodeAt(0)`
|
||||
: expression
|
||||
const checked = item.outcome === "undefined"
|
||||
? `${observed} === undefined`
|
||||
: item.outcome === "length"
|
||||
? `${observed}.length`
|
||||
: item.outcome === "RangeError"
|
||||
? `(() => { try { ${observed}; return false } catch (error) { return error instanceof RangeError } })()`
|
||||
: observed
|
||||
return `{ label: ${JSON.stringify(item.label)}, value: ${checked} }`
|
||||
})
|
||||
const expected = vector.assertions.map((item) => ({
|
||||
|
||||
@@ -121,35 +121,9 @@ run("Test262-adapted regexp split behavior", [
|
||||
]
|
||||
`,
|
||||
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)))
|
||||
`,
|
||||
expected: [
|
||||
"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-|$000|-bar",
|
||||
"foo-|$000|-bar",
|
||||
"foo-|$000|-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",
|
||||
"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-|$000|-bar", "foo-|$000|-bar", "foo-|$000|-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: [
|
||||
"azc azc azc",
|
||||
"abc abc abc",
|
||||
"abc abc abc",
|
||||
"o ppercase!",
|
||||
"o Uppercase?",
|
||||
" UPPERCASE!",
|
||||
"ca-bbcca-bbc",
|
||||
"abb$3cabb$3cabb$3cabb$3c",
|
||||
"azc azc azc", "abc abc abc", "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",
|
||||
"acbacaa",
|
||||
"aacabca",
|
||||
"a(b)c(b)a",
|
||||
"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",
|
||||
"acbacaa", "aacabca", "a(b)c(b)a", "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"
|
||||
return ["$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9"].map((replacement) => str.replaceAll("ABC", replacement))
|
||||
`,
|
||||
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",
|
||||
],
|
||||
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"],
|
||||
},
|
||||
{
|
||||
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]`,
|
||||
expected: [5, "one", "two", "three", "four", "five"],
|
||||
labels: [
|
||||
"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[2] is "three"',
|
||||
'The value of __split[3] is "four"',
|
||||
'The value of __split[4] is "five"',
|
||||
"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[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]]`,
|
||||
expected: ["o", "n", "e", "e"],
|
||||
labels: [
|
||||
'The value of __split[0] is "o"',
|
||||
'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[0] is "o"', 'The value of __split[1] is "n"',
|
||||
'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",
|
||||
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"],
|
||||
labels: [
|
||||
"implicit separator, result is array",
|
||||
"implicit separator, result.length",
|
||||
"implicit separator, [0] is the same string",
|
||||
],
|
||||
labels: ["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",
|
||||
@@ -466,33 +456,23 @@ const cases = [
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailMissingLetter.js",
|
||||
code: `return ["word".includes("a", 0)]`,
|
||||
expected: [false],
|
||||
labels: ['"word".includes("a", 0)'],
|
||||
code: `return ["word".includes("a", 0)]`, expected: [false], labels: ['"word".includes("a", 0)'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/includes/String.prototype.includes_SuccessNoLocation.js",
|
||||
code: `return ["word".includes("w")]`,
|
||||
expected: [true],
|
||||
labels: ['"word".includes("w")'],
|
||||
code: `return ["word".includes("w")]`, expected: [true], labels: ['"word".includes("w")'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailBadLocation.js",
|
||||
code: `return ["word".includes("w", 5)]`,
|
||||
expected: [false],
|
||||
labels: ['"word".includes("w", 5)'],
|
||||
code: `return ["word".includes("w", 5)]`, expected: [false], labels: ['"word".includes("w", 5)'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/includes/String.prototype.includes_FailLocation.js",
|
||||
code: `return ["word".includes("o", 3)]`,
|
||||
expected: [false],
|
||||
labels: ['"word".includes("o", 3)'],
|
||||
code: `return ["word".includes("o", 3)]`, expected: [false], labels: ['"word".includes("o", 3)'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/includes/String.prototype.includes_Success.js",
|
||||
code: `return ["word".includes("w", 0)]`,
|
||||
expected: [true],
|
||||
labels: ['"word".includes("w", 0)'],
|
||||
code: `return ["word".includes("w", 0)]`, expected: [true], labels: ['"word".includes("w", 0)'],
|
||||
},
|
||||
{
|
||||
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",
|
||||
code: `const text = "The future is cool!"; return [text.includes("Flash"), text.includes("FUTURE")]`,
|
||||
expected: [false, false],
|
||||
labels: ["Flash if not included", "includes is case sensitive"],
|
||||
expected: [false, false], labels: ["Flash if not included", "includes is case sensitive"],
|
||||
},
|
||||
{
|
||||
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],
|
||||
labels: [
|
||||
'str.includes("!", str.length + 1) returns false',
|
||||
'str.includes("!", 100) returns false',
|
||||
'str.includes("!", Infinity) returns false',
|
||||
'str.includes("!", str.length) returns false',
|
||||
'str.includes("!", str.length + 1) returns false', 'str.includes("!", 100) 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",
|
||||
code: `const text = "The future is cool!"; return [text.includes("", text.length), text.includes(""), text.includes("", Infinity)]`,
|
||||
expected: [true, true, true],
|
||||
labels: [
|
||||
'str.includes("", str.length) returns true',
|
||||
'str.includes("") returns true',
|
||||
'str.includes("", Infinity) returns true',
|
||||
],
|
||||
labels: ['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",
|
||||
@@ -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)]`,
|
||||
expected: [true, true, true],
|
||||
labels: [
|
||||
'str.startsWith("The future", 0) === true',
|
||||
'str.startsWith("future", 4) === true',
|
||||
'str.startsWith("The future", 0) === true', 'str.startsWith("future", 4) === 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",
|
||||
code: `const text = "The future is cool!"; return [text.startsWith("The "), text.startsWith("The future"), text.startsWith(text)]`,
|
||||
expected: [true, true, true],
|
||||
labels: [
|
||||
'str.startsWith("The ") === true',
|
||||
'str.startsWith("The future") === true',
|
||||
"str.startsWith(str) === true",
|
||||
],
|
||||
labels: ['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",
|
||||
@@ -588,11 +556,7 @@ const cases = [
|
||||
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!")]`,
|
||||
expected: [false, false, false],
|
||||
labels: [
|
||||
'str.startsWith("Flash") === false',
|
||||
"startsWith is case sensitive",
|
||||
'str.startsWith("future is cool!") === false',
|
||||
],
|
||||
labels: ['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",
|
||||
@@ -602,10 +566,8 @@ const cases = [
|
||||
]`,
|
||||
expected: [false, false, false, true, true],
|
||||
labels: [
|
||||
'str.startsWith("!", str.length) 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("!", str.length) returns false', 'str.startsWith("!", 100) returns false',
|
||||
'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)",
|
||||
],
|
||||
},
|
||||
@@ -613,11 +575,7 @@ const cases = [
|
||||
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)]`,
|
||||
expected: [true, true, true],
|
||||
labels: [
|
||||
'str.startsWith("") returns true',
|
||||
'str.startsWith("", str.length) returns true',
|
||||
'str.startsWith("", Infinity) returns true',
|
||||
],
|
||||
labels: ['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",
|
||||
@@ -630,47 +588,34 @@ const cases = [
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success.js",
|
||||
code: `return ["word".endsWith("d")]`,
|
||||
expected: [true],
|
||||
labels: ['"word".endsWith("d")'],
|
||||
code: `return ["word".endsWith("d")]`, expected: [true], labels: ['"word".endsWith("d")'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_2.js",
|
||||
code: `return ["word".endsWith("d", 4)]`,
|
||||
expected: [true],
|
||||
labels: ['"word".endsWith("d", 4)'],
|
||||
code: `return ["word".endsWith("d", 4)]`, expected: [true], labels: ['"word".endsWith("d", 4)'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_3.js",
|
||||
code: `return ["word".endsWith("d", 25)]`,
|
||||
expected: [true],
|
||||
labels: ['"word".endsWith("d", 25)'],
|
||||
code: `return ["word".endsWith("d", 25)]`, expected: [true], labels: ['"word".endsWith("d", 25)'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Success_4.js",
|
||||
code: `return ["word".endsWith("r", 3)]`,
|
||||
expected: [true],
|
||||
labels: ['"word".endsWith("r", 3)'],
|
||||
code: `return ["word".endsWith("r", 3)]`, expected: [true], labels: ['"word".endsWith("r", 3)'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail.js",
|
||||
code: `return ["word".endsWith("r")]`,
|
||||
expected: [false],
|
||||
labels: ['"word".endsWith("r")'],
|
||||
code: `return ["word".endsWith("r")]`, expected: [false], labels: ['"word".endsWith("r")'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/endsWith/String.prototype.endsWith_Fail_2.js",
|
||||
code: `return ["word".endsWith("d", 3)]`,
|
||||
expected: [false],
|
||||
labels: ['"word".endsWith("d", 3)'],
|
||||
code: `return ["word".endsWith("d", 3)]`, expected: [false], labels: ['"word".endsWith("d", 3)'],
|
||||
},
|
||||
{
|
||||
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)]`,
|
||||
expected: [true, true, true],
|
||||
labels: [
|
||||
'str.endsWith("The future", 10) === true',
|
||||
'str.endsWith("future", 10) === true',
|
||||
'str.endsWith("The future", 10) === true', 'str.endsWith("future", 10) === 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",
|
||||
code: `const text = "The future is cool!"; return [text.endsWith("is Flash!"), text.endsWith("IS COOL!"), text.endsWith("The future")]`,
|
||||
expected: [false, false, false],
|
||||
labels: [
|
||||
'str.endsWith("is Flash!") === false',
|
||||
"endsWith is case sensitive",
|
||||
'str.endsWith("The future") === false',
|
||||
],
|
||||
labels: ['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",
|
||||
@@ -710,11 +651,8 @@ const cases = [
|
||||
]`,
|
||||
expected: [true, true, true, true, true],
|
||||
labels: [
|
||||
'str.endsWith("") returns true',
|
||||
'str.endsWith("", str.length) returns true',
|
||||
'str.endsWith("", Infinity) returns true',
|
||||
'str.endsWith("", -1) returns true',
|
||||
'str.endsWith("", -Infinity) returns true',
|
||||
'str.endsWith("") returns true', 'str.endsWith("", str.length) 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",
|
||||
code: `return ["abcd".indexOf("abcdab")]`,
|
||||
expected: [-1],
|
||||
labels: ['#1: "abcd".indexOf("abcdab")===-1'],
|
||||
code: `return ["abcd".indexOf("abcdab")]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab")===-1'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T2.js",
|
||||
code: `return ["abcd".indexOf("abcdab", 0)]`,
|
||||
expected: [-1],
|
||||
labels: ['#1: "abcd".indexOf("abcdab",0)===-1'],
|
||||
code: `return ["abcd".indexOf("abcdab", 0)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",0)===-1'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T3.js",
|
||||
code: `return ["abcd".indexOf("abcdab", 99)]`,
|
||||
expected: [-1],
|
||||
labels: ['#1: "abcd".indexOf("abcdab",99)===-1'],
|
||||
code: `return ["abcd".indexOf("abcdab", 99)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",99)===-1'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A2_T4.js",
|
||||
code: `return ["abcd".indexOf("abcdab", NaN)]`,
|
||||
expected: [-1],
|
||||
labels: ['#1: "abcd".indexOf("abcdab",NaN)===-1'],
|
||||
code: `return ["abcd".indexOf("abcdab", NaN)]`, expected: [-1], labels: ['#1: "abcd".indexOf("abcdab",NaN)===-1'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T1.js",
|
||||
code: `return ["$$abcdabcd".indexOf("ab", NaN)]`,
|
||||
expected: [2],
|
||||
labels: ['#1: "$$abcdabcd".indexOf("ab",NaN)===2'],
|
||||
code: `return ["$$abcdabcd".indexOf("ab", NaN)]`, expected: [2], labels: ['#1: "$$abcdabcd".indexOf("ab",NaN)===2'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/indexOf/S15.5.4.7_A3_T3.js",
|
||||
code: `return ["$$abcdabcd".indexOf("ab", -Infinity)]`,
|
||||
expected: [2],
|
||||
labels: ['#1: "$$abcdabcd".indexOf("ab", function(){return -Infinity;}())===2'],
|
||||
code: `return ["$$abcdabcd".indexOf("ab", -Infinity)]`, expected: [2], labels: ['#1: "$$abcdabcd".indexOf("ab", function(){return -Infinity;}())===2'],
|
||||
},
|
||||
{
|
||||
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],
|
||||
labels: [
|
||||
"position 0",
|
||||
"position 1",
|
||||
"ToInteger: truncate towards 0 (-0.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)",
|
||||
"position 0", "position 1", "ToInteger: truncate towards 0 (-0.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",
|
||||
code: `return ["foo".indexOf(""), "__foo__".indexOf("foo")]`,
|
||||
expected: [0, 2],
|
||||
labels: ['"foo".indexOf("")', '"__foo__".indexOf("foo")'],
|
||||
expected: [0, 2], labels: ['"foo".indexOf("")', '"__foo__".indexOf("foo")'],
|
||||
},
|
||||
{
|
||||
path: "test/built-ins/String/prototype/lastIndexOf/not-a-substring.js",
|
||||
code: `return ["abc".lastIndexOf("d")]`,
|
||||
expected: [-1],
|
||||
labels: [
|
||||
"String.prototype.lastIndexOf returns -1 when searchString is shorter than this and searchString is not a substring of this.",
|
||||
],
|
||||
labels: ["String.prototype.lastIndexOf returns -1 when searchString is shorter than this and searchString is not a substring of this."],
|
||||
},
|
||||
] 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 }))
|
||||
}
|
||||
@@ -103,16 +103,4 @@ export namespace 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))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
},
|
||||
"exports": {
|
||||
"./environment": "./src/environment/index.ts",
|
||||
"./testing/environment-conformance": "./test/lib/environment-conformance.ts",
|
||||
"./session/runner": "./src/session/runner/index.ts",
|
||||
"./instructions": "./src/instructions/index.ts",
|
||||
"./*": "./src/*.ts"
|
||||
|
||||
+213
-93
@@ -1,9 +1,15 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"id": "15060ec5-05f7-4b86-b2a5-9108609432b3",
|
||||
"prevIds": ["1551a157-8959-4ba9-a52b-4ea3b7b28cae"],
|
||||
"id": "2d214a71-3b0a-48c1-a667-741952c4e188",
|
||||
"prevIds": [
|
||||
"f14a9b18-8207-487e-a3d3-227e629ba9ad"
|
||||
],
|
||||
"ddl": [
|
||||
{
|
||||
"name": "workspace",
|
||||
"entityType": "tables"
|
||||
},
|
||||
{
|
||||
"name": "account_state",
|
||||
"entityType": "tables"
|
||||
@@ -69,8 +75,84 @@
|
||||
"entityType": "tables"
|
||||
},
|
||||
{
|
||||
"name": "workspace",
|
||||
"entityType": "tables"
|
||||
"type": "text",
|
||||
"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",
|
||||
@@ -1303,59 +1385,28 @@
|
||||
"table": "session_v2"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "id",
|
||||
"entityType": "columns",
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
"name": "fk_workspace_project_id_project_id_fk",
|
||||
"entityType": "fks",
|
||||
"table": "workspace"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"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"],
|
||||
"columns": [
|
||||
"active_account_id"
|
||||
],
|
||||
"tableTo": "account",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "SET NULL",
|
||||
"nameExplicit": false,
|
||||
@@ -1364,9 +1415,13 @@
|
||||
"table": "account_state"
|
||||
},
|
||||
{
|
||||
"columns": ["aggregate_id"],
|
||||
"columns": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"tableTo": "event_sequence",
|
||||
"columnsTo": ["aggregate_id"],
|
||||
"columnsTo": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
@@ -1375,9 +1430,13 @@
|
||||
"table": "event"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id"],
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
@@ -1386,9 +1445,13 @@
|
||||
"table": "permission"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id"],
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
@@ -1397,9 +1460,13 @@
|
||||
"table": "project_directory"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session_v2",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
@@ -1408,9 +1475,13 @@
|
||||
"table": "instruction_entry"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session_v2",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
@@ -1419,9 +1490,13 @@
|
||||
"table": "instruction_state"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session_v2",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
@@ -1430,9 +1505,13 @@
|
||||
"table": "session_message"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"tableTo": "session_v2",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
@@ -1441,9 +1520,13 @@
|
||||
"table": "session_pending"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id"],
|
||||
"columns": [
|
||||
"project_id"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": ["id"],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": "NO ACTION",
|
||||
"onDelete": "CASCADE",
|
||||
"nameExplicit": false,
|
||||
@@ -1452,124 +1535,161 @@
|
||||
"table": "session_v2"
|
||||
},
|
||||
{
|
||||
"columns": ["email", "url"],
|
||||
"columns": [
|
||||
"email",
|
||||
"url"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "control_account_pk",
|
||||
"entityType": "pks",
|
||||
"table": "control_account"
|
||||
},
|
||||
{
|
||||
"columns": ["project_id", "directory"],
|
||||
"columns": [
|
||||
"project_id",
|
||||
"directory"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "project_directory_pk",
|
||||
"entityType": "pks",
|
||||
"table": "project_directory"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id", "key"],
|
||||
"columns": [
|
||||
"session_id",
|
||||
"key"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "instruction_entry_pk",
|
||||
"entityType": "pks",
|
||||
"table": "instruction_entry"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "workspace_pk",
|
||||
"table": "workspace",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "account_state_pk",
|
||||
"table": "account_state",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "account_pk",
|
||||
"table": "account",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "credential_pk",
|
||||
"table": "credential",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["aggregate_id"],
|
||||
"columns": [
|
||||
"aggregate_id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "event_sequence_pk",
|
||||
"table": "event_sequence",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "event_pk",
|
||||
"table": "event",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["key"],
|
||||
"columns": [
|
||||
"key"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "kv_pk",
|
||||
"table": "kv",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "permission_pk",
|
||||
"table": "permission",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "project_pk",
|
||||
"table": "project",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["hash"],
|
||||
"columns": [
|
||||
"hash"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "instruction_blob_pk",
|
||||
"table": "instruction_blob",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["session_id"],
|
||||
"columns": [
|
||||
"session_id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "instruction_state_pk",
|
||||
"table": "instruction_state",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "session_message_pk",
|
||||
"table": "session_message",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "session_pending_pk",
|
||||
"table": "session_pending",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"columns": [
|
||||
"id"
|
||||
],
|
||||
"nameExplicit": false,
|
||||
"name": "session_v2_pk",
|
||||
"table": "session_v2",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": ["id"],
|
||||
"nameExplicit": false,
|
||||
"name": "workspace_pk",
|
||||
"table": "workspace",
|
||||
"entityType": "pks"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
@@ -1820,4 +1940,4 @@
|
||||
}
|
||||
],
|
||||
"renames": []
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ if (!Number.isInteger(iterations) || iterations < 1) {
|
||||
}
|
||||
|
||||
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>) =>
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -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>
|
||||
@@ -1,21 +1,24 @@
|
||||
import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core"
|
||||
|
||||
import { Account } from "../account"
|
||||
import { Timestamps } from "../database/schema.sql"
|
||||
|
||||
export const AccountTable = sqliteTable("account", {
|
||||
id: text().primaryKey(),
|
||||
id: text().$type<Account.ID>().primaryKey(),
|
||||
email: text().notNull(),
|
||||
url: text().notNull(),
|
||||
access_token: text().notNull(),
|
||||
refresh_token: text().notNull(),
|
||||
access_token: text().$type<Account.AccessToken>().notNull(),
|
||||
refresh_token: text().$type<Account.RefreshToken>().notNull(),
|
||||
token_expiry: integer(),
|
||||
...Timestamps,
|
||||
})
|
||||
|
||||
export const AccountStateTable = sqliteTable("account_state", {
|
||||
id: integer().primaryKey(),
|
||||
active_account_id: text().references(() => AccountTable.id, { onDelete: "set null" }),
|
||||
active_org_id: text(),
|
||||
active_account_id: text()
|
||||
.$type<Account.ID>()
|
||||
.references(() => AccountTable.id, { onDelete: "set null" }),
|
||||
active_org_id: text().$type<Account.OrgID>(),
|
||||
})
|
||||
|
||||
// LEGACY
|
||||
@@ -24,8 +27,8 @@ export const ControlAccountTable = sqliteTable(
|
||||
{
|
||||
email: text().notNull(),
|
||||
url: text().notNull(),
|
||||
access_token: text().notNull(),
|
||||
refresh_token: text().notNull(),
|
||||
access_token: text().$type<Account.AccessToken>().notNull(),
|
||||
refresh_token: text().$type<Account.RefreshToken>().notNull(),
|
||||
token_expiry: integer(),
|
||||
active: integer({ mode: "boolean" })
|
||||
.notNull()
|
||||
|
||||
@@ -88,7 +88,9 @@ function mapBedrockSettings(
|
||||
: typeof settings.bearerToken === "string"
|
||||
? settings.bearerToken
|
||||
: undefined
|
||||
const credentials = mapBedrockCredentials(settings)
|
||||
const credentials =
|
||||
mapBedrockCredentials(settings) ??
|
||||
(typeof settings.credentialProvider === "function" ? settings.credentialProvider : undefined)
|
||||
return {
|
||||
...baseSettings,
|
||||
...(typeof settings.baseURL !== "string" && typeof settings.endpoint === "string"
|
||||
|
||||
+494
-505
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user