Compare commits

..

3 Commits

Author SHA1 Message Date
Aiden Cline b09ebbea3c refactor(cli): own Code Mode host wiring 2026-07-07 10:39:32 -05:00
Aiden Cline 42b63d6660 refactor(core): separate Code Mode host wiring 2026-07-07 09:31:59 -05:00
Aiden Cline e113aad5e0 feat(core): expose server API in Code Mode 2026-07-06 23:06:40 -05:00
385 changed files with 9470 additions and 14674 deletions
+6 -116
View File
@@ -101,6 +101,7 @@
"dependencies": { "dependencies": {
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
"@opencode-ai/codemode": "workspace:*",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*", "@opencode-ai/plugin": "workspace:*",
"@opencode-ai/schema": "workspace:*", "@opencode-ai/schema": "workspace:*",
@@ -873,7 +874,6 @@
"name": "@opencode-ai/simulation", "name": "@opencode-ai/simulation",
"version": "1.17.13", "version": "1.17.13",
"dependencies": { "dependencies": {
"@napi-rs/canvas": "1.0.2",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/llm": "workspace:*", "@opencode-ai/llm": "workspace:*",
"@opentui/core": "catalog:", "@opentui/core": "catalog:",
@@ -1612,11 +1612,11 @@
"@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="], "@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="],
"@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="], "@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="],
@@ -1984,30 +1984,6 @@
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
"@napi-rs/canvas": ["@napi-rs/canvas@1.0.2", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "1.0.2", "@napi-rs/canvas-darwin-arm64": "1.0.2", "@napi-rs/canvas-darwin-x64": "1.0.2", "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.2", "@napi-rs/canvas-linux-arm64-gnu": "1.0.2", "@napi-rs/canvas-linux-arm64-musl": "1.0.2", "@napi-rs/canvas-linux-riscv64-gnu": "1.0.2", "@napi-rs/canvas-linux-x64-gnu": "1.0.2", "@napi-rs/canvas-linux-x64-musl": "1.0.2", "@napi-rs/canvas-win32-arm64-msvc": "1.0.2", "@napi-rs/canvas-win32-x64-msvc": "1.0.2" } }, "sha512-EYEqlMYaCbpZDz+IgDH5xp9MTd3ui4dmGqbQYryhMLnSRxrhHKq5KQWHHKxFUcEP4Hp8/BWgvqXocX4j7iSbOQ=="],
"@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@1.0.2", "", { "os": "android", "cpu": "arm64" }, "sha512-IMXKVQod0ol4vt3gmClUfXz4JAgHYESGPCUqmH3lQxBoL0K/2greJaQE1HVBVxWWFKfLc4OLZVdxg7kXVyXv+g=="],
"@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@1.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sc8tPi6cF+5lqOzCCKFALJHhDiRwyMzTPYm3bbhdXsOunU0lQO5f05ucyOzN2r55I23Hg5bsjH63uSCvWp3EgQ=="],
"@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@1.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-niDXZ9LhKB1zLrUdYB64RHQFDGz9rr0eGx061qtJJU3U20EMMIx28ADF5fVYbhtOgkWQrBjFicfaye1yM0U62A=="],
"@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@1.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-sgatQL9JxGRH/Amzcvu0P3t8Am3duou74CisfuJ41Dwt8cWy723z/9KZ8LlgmxfypEwEZxSTNFJtU8d281lmhQ=="],
"@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgKuX0peF3xwY6ZF5QxGS4wbfDqpoFAJYXiLSp+guZKARQUKMkRqZSDrXKj7nfrec3UCMzC0PFCPte0ES98AiA=="],
"@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA=="],
"@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@1.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw=="],
"@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g=="],
"@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q=="],
"@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@1.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ=="],
"@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@1.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-okU8/t2foV6C31n0GtvEMbfD5rOFc70+/6xUNME9Guld29sgSOIGUEDScAWFlcP3k5TYQRl9TNkwJEEjh15w8A=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
"@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
@@ -2302,7 +2278,7 @@
"@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.127.0", "", { "os": "win32", "cpu": "x64" }, "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w=="], "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.127.0", "", { "os": "win32", "cpu": "x64" }, "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w=="],
"@oxc-project/types": ["@oxc-project/types@0.138.0", "", {}, "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA=="], "@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="],
"@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.20.0", "", { "os": "android", "cpu": "arm" }, "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg=="], "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.20.0", "", { "os": "android", "cpu": "arm" }, "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg=="],
@@ -2594,37 +2570,7 @@
"@remix-run/router": ["@remix-run/router@1.9.0", "", {}, "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA=="], "@remix-run/router": ["@remix-run/router@1.9.0", "", {}, "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.4", "", { "os": "android", "cpu": "arm64" }, "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ=="],
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg=="],
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ=="],
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.4", "", { "os": "linux", "cpu": "arm" }, "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA=="],
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w=="],
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng=="],
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg=="],
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ=="],
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw=="],
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ=="],
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.4", "", { "os": "none", "cpu": "arm64" }, "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA=="],
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.4", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg=="],
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA=="],
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="],
@@ -3166,8 +3112,6 @@
"@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="], "@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
@@ -3240,12 +3184,6 @@
"@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="], "@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="],
"@vercel/cli-config": ["@vercel/cli-config@0.2.0", "", { "dependencies": { "xdg-app-paths": "5", "zod": "4.1.11" } }, "sha512-fJRRRB7734BDuXZ89yBEaA2ncYhH7bWX30mk04W80J6VAfQc+4iB8lyzAdaGpFV3/vNlkt9VZt+/uoQoWX6UsQ=="],
"@vercel/cli-exec": ["@vercel/cli-exec@1.0.0", "", { "dependencies": { "execa": "5.1.1" } }, "sha512-kQF8LGie/Hbdq9/psJxLE7owRTcqMQMhgybU04gCeR7cbQAr5t8OrjefDNColJv1QSSucFt4pLwRiARVmlOnug=="],
"@vercel/functions": ["@vercel/functions@3.7.5", "", { "dependencies": { "@vercel/oidc": "3.8.0" }, "peerDependencies": { "@aws-sdk/credential-provider-web-identity": "*", "ws": ">=8" }, "optionalPeers": ["@aws-sdk/credential-provider-web-identity", "ws"] }, "sha512-ESf8BbeDebqRUyMi09JwRbQqpLn4g6fjcVVHPsHB56j2dSqRrSHO4h3X4aaxJf6iQQjzhAtDGI2xCWQ27JE8PA=="],
"@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
@@ -5050,8 +4988,6 @@
"opentui-spinner": ["opentui-spinner@0.0.7", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.3.4", "@opentui/react": "^0.3.4", "@opentui/solid": "^0.3.4", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-nPzwAvJG+y9rVEwwHLHqbsMzLnIk2zw+F9LqwA7aYJvpM5gsrKC2rrGi36A+tZpA+1RnWxXeWEgVZMchnaH18Q=="], "opentui-spinner": ["opentui-spinner@0.0.7", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.3.4", "@opentui/react": "^0.3.4", "@opentui/solid": "^0.3.4", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-nPzwAvJG+y9rVEwwHLHqbsMzLnIk2zw+F9LqwA7aYJvpM5gsrKC2rrGi36A+tZpA+1RnWxXeWEgVZMchnaH18Q=="],
"os-paths": ["os-paths@4.4.0", "", {}, "sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="], "oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="],
@@ -5466,8 +5402,6 @@
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
"rolldown": ["rolldown@1.1.4", "", { "dependencies": { "@oxc-project/types": "=0.138.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.4", "@rolldown/binding-darwin-arm64": "1.1.4", "@rolldown/binding-darwin-x64": "1.1.4", "@rolldown/binding-freebsd-x64": "1.1.4", "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", "@rolldown/binding-linux-arm64-gnu": "1.1.4", "@rolldown/binding-linux-arm64-musl": "1.1.4", "@rolldown/binding-linux-ppc64-gnu": "1.1.4", "@rolldown/binding-linux-s390x-gnu": "1.1.4", "@rolldown/binding-linux-x64-gnu": "1.1.4", "@rolldown/binding-linux-x64-musl": "1.1.4", "@rolldown/binding-openharmony-arm64": "1.1.4", "@rolldown/binding-wasm32-wasi": "1.1.4", "@rolldown/binding-win32-arm64-msvc": "1.1.4", "@rolldown/binding-win32-x64-msvc": "1.1.4" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA=="],
"rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="],
"rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
@@ -6088,12 +6022,8 @@
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
"xdg-app-paths": ["xdg-app-paths@5.5.1", "", { "dependencies": { "os-paths": "^4.0.1", "xdg-portable": "^7.2.0" } }, "sha512-hI3flOB4PLZIy5prbtTpirobtPE2ZtZ52szO+2mM9Efp6ErM398La+C1lIpNWDfNoQk+6Lsi6nMcCwVB7pxeMQ=="],
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="], "xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
"xdg-portable": ["xdg-portable@7.3.0", "", { "dependencies": { "os-paths": "^4.0.1" } }, "sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw=="],
"xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
"xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
@@ -6690,10 +6620,6 @@
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
"@oxc-parser/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
"@oxc-parser/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
"@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], "@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
@@ -6714,8 +6640,6 @@
"@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], "@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
@@ -6824,14 +6748,6 @@
"@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"@vercel/cli-config/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@vercel/cli-exec/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
"@vercel/functions/@vercel/oidc": ["@vercel/oidc@3.8.0", "", { "dependencies": { "@vercel/cli-config": "0.2.0", "@vercel/cli-exec": "1.0.0", "jose": "^5.9.6" } }, "sha512-r00laGW6Pv778RoR6M2NxX91ycSj+PBwVo+fOb9Bif+F0IyUKt25zrvBzfEzQpeAzbqOgPZyQibEWDdDFApd+A=="],
"@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@vitest/coverage-v8/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="], "@vitest/coverage-v8/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="],
"@vitest/coverage-v8/magicast": ["magicast@0.5.3", "", { "dependencies": { "@babel/parser": "^7.29.3", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="], "@vitest/coverage-v8/magicast": ["magicast@0.5.3", "", { "dependencies": { "@babel/parser": "^7.29.3", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="],
@@ -7128,8 +7044,6 @@
"openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"oxc-parser/@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="],
"p-any/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], "p-any/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="],
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@@ -7794,10 +7708,6 @@
"@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@oxc-parser/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], "@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], "@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
@@ -7806,8 +7716,6 @@
"@puppeteer/browsers/yargs/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=="], "@puppeteer/browsers/yargs/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=="],
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
"@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="], "@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
"@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], "@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
@@ -7878,22 +7786,6 @@
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
"@vercel/cli-exec/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
"@vercel/cli-exec/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
"@vercel/cli-exec/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"@vercel/cli-exec/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"@vercel/cli-exec/execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"@vercel/cli-exec/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"@vercel/cli-exec/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"@vercel/functions/@vercel/oidc/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
"@vitest/coverage-v8/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="], "@vitest/coverage-v8/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="],
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
@@ -8460,8 +8352,6 @@
"@stoplight/spectral-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@stoplight/spectral-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"@vercel/cli-exec/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
@@ -20,7 +20,7 @@ const profiles = [
{ name: "edit", tool: "edit", input: { filePath: "src/edit.ts" } }, { name: "edit", tool: "edit", input: { filePath: "src/edit.ts" } },
{ {
name: "multi patch", name: "multi patch",
tool: "patch", tool: "apply_patch",
input: { files: ["src/a.ts", "src/b.ts", "src/old.ts", "src/moved.ts"] }, input: { files: ["src/a.ts", "src/b.ts", "src/old.ts", "src/moved.ts"] },
}, },
] as const ] as const
@@ -25,7 +25,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
userMessage(), userMessage(),
assistantMessage( assistantMessage(
[ [
toolPart(patchID, "patch", "running", { files: [first.filePath] }, { metadata: { files: [first] } }), toolPart(patchID, "apply_patch", "running", { files: [first.filePath] }, { metadata: { files: [first] } }),
textPart(followingID, "Following incremental patch"), textPart(followingID, "Following incremental patch"),
], ],
{ completed: false }, { completed: false },
@@ -49,7 +49,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
partUpdated( partUpdated(
toolPart( toolPart(
patchID, patchID,
"patch", "apply_patch",
"running", "running",
{ files: [first.filePath, second.filePath] }, { files: [first.filePath, second.filePath] },
{ metadata: { files: [first, second] } }, { metadata: { files: [first, second] } },
@@ -61,7 +61,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
partUpdated( partUpdated(
toolPart( toolPart(
patchID, patchID,
"patch", "apply_patch",
"completed", "completed",
{ files: [first.filePath, second.filePath, third.filePath] }, { files: [first.filePath, second.filePath, third.filePath] },
{ metadata: { files: [first, second, third] } }, { metadata: { files: [first, second, third] } },
@@ -295,7 +295,7 @@ function performanceTurn(index: number) {
messageID: assistantID, messageID: assistantID,
type: "tool", type: "tool",
callID: `call_0000_${suffix}_patch`, callID: `call_0000_${suffix}_patch`,
tool: "patch", tool: "apply_patch",
state: { state: {
status: "completed", status: "completed",
input: { patchText: realisticPatch(index) }, input: { patchText: realisticPatch(index) },
@@ -131,7 +131,7 @@ function toolPart(
): MessagePart { ): MessagePart {
const metadata = const metadata =
metadataOverride ?? metadataOverride ??
(tool === "patch" (tool === "apply_patch"
? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] } ? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] }
: tool === "edit" || tool === "write" : tool === "edit" || tool === "write"
? { ? {
@@ -219,7 +219,7 @@ function turn(index: number): Message[] {
? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)] ? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)]
: []), : []),
...(index % 8 === 0 ...(index % 8 === 0
? [toolPart(index, 8, "patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] ? [toolPart(index, 8, "apply_patch", { files: [`src/generated/patch-${index}.ts`] }, 620)]
: []), : []),
...(index % 7 === 0 ...(index % 7 === 0
? [toolPart(index, 4, "bash", { command: "bun typecheck", description: "Verify generated output" }, 620)] ? [toolPart(index, 4, "bash", { command: "bun typecheck", description: "Verify generated output" }, 620)]
@@ -24,7 +24,7 @@ test("renders a completed single-file patch", async ({ page }) => {
assistantMessage([ assistantMessage([
toolPart( toolPart(
id, id,
"patch", "apply_patch",
"completed", "completed",
{ files: ["src/a.ts"] }, { files: ["src/a.ts"] },
{ {
@@ -35,7 +35,7 @@ test("preserves nested patch file state through outer collapse and reopen", asyn
assistantMessage([ assistantMessage([
toolPart( toolPart(
patchID, patchID,
"patch", "apply_patch",
"completed", "completed",
{ files: files.map((file) => file.filePath) }, { files: files.map((file) => file.filePath) },
{ metadata: { files } }, { metadata: { files } },
@@ -246,7 +246,7 @@ function editPart(id: string) {
function patchPart(id: string) { function patchPart(id: string) {
return toolPart( return toolPart(
id, id,
"patch", "apply_patch",
"completed", "completed",
{ files: ["src/a.ts", "src/b.ts"] }, { files: ["src/a.ts", "src/b.ts"] },
{ {
@@ -8,7 +8,7 @@ import {
} from "../performance/timeline-stability/fixture" } from "../performance/timeline-stability/fixture"
test("renders every tool error outcome without leaking hidden tools", async ({ page }) => { test("renders every tool error outcome without leaking hidden tools", async ({ page }) => {
const ordinary = ["bash", "edit", "write", "patch", "webfetch", "websearch", "task", "skill", "mcp_probe"] const ordinary = ["bash", "edit", "write", "apply_patch", "webfetch", "websearch", "task", "skill", "mcp_probe"]
const parts = ordinary.map((tool, index) => const parts = ordinary.map((tool, index) =>
toolPart(`prt_error_${index}`, tool, "error", errorInput(tool), { error: `${tool} failed visibly` }), toolPart(`prt_error_${index}`, tool, "error", errorInput(tool), { error: `${tool} failed visibly` }),
) )
@@ -90,7 +90,7 @@ function questionInput() {
function errorInput(tool: string) { function errorInput(tool: string) {
if (tool === "bash") return { command: "exit 1" } if (tool === "bash") return { command: "exit 1" }
if (["edit", "write"].includes(tool)) return { filePath: "src/error.ts", content: "" } if (["edit", "write"].includes(tool)) return { filePath: "src/error.ts", content: "" }
if (tool === "patch") return { files: ["src/error.ts"] } if (tool === "apply_patch") return { files: ["src/error.ts"] }
if (tool === "webfetch") return { url: "https://example.com" } if (tool === "webfetch") return { url: "https://example.com" }
if (tool === "websearch") return { query: "failure" } if (tool === "websearch") return { query: "failure" }
if (tool === "task") return { description: "Fail task", subagent_type: "explore" } if (tool === "task") return { description: "Fail task", subagent_type: "explore" }
@@ -120,7 +120,7 @@ function toolPart(
outputLength = 160, outputLength = 160,
): MessagePart { ): MessagePart {
const metadata = const metadata =
tool === "patch" tool === "apply_patch"
? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] } ? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] }
: tool === "edit" || tool === "write" : tool === "edit" || tool === "write"
? { ? {
@@ -199,7 +199,7 @@ function turn(index: number): Message[] {
? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)] ? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)]
: []), : []),
...(index % 8 === 0 ...(index % 8 === 0
? [toolPart(index, 8, "patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] ? [toolPart(index, 8, "apply_patch", { files: [`src/generated/patch-${index}.ts`] }, 620)]
: []), : []),
...(index % 7 === 0 ? [toolPart(index, 4, "bash", { command: "bun typecheck" }, 620)] : []), ...(index % 7 === 0 ? [toolPart(index, 4, "bash", { command: "bun typecheck" }, 620)] : []),
...(index % 10 === 0 ? [toolPart(index, 9, "webfetch", { url: "https://example.com/docs/sample" }, 120)] : []), ...(index % 10 === 0 ? [toolPart(index, 9, "webfetch", { url: "https://example.com/docs/sample" }, 120)] : []),
+1 -1
View File
@@ -55,7 +55,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
const path = url.pathname const path = url.pathname
if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry) if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry)
if (path === "/global/health") return json(route, { healthy: true }) if (path === "/global/health") return json(route, { healthy: true })
if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: true }) if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: false })
if (path === "/permission") if (path === "/permission")
return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? [])) return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? []))
if (path === "/question") if (path === "/question")
@@ -8,7 +8,7 @@ import type {
QuestionRequest, QuestionRequest,
Session, Session,
SessionStatus, SessionStatus,
FileDiffInfo, SnapshotFileDiff,
Todo, Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import type { State, VcsCache } from "./types" import type { State, VcsCache } from "./types"
@@ -188,7 +188,7 @@ export function applyDirectoryEvent(input: {
break break
} }
case "session.diff": { case "session.diff": {
const props = event.properties as { sessionID: string; diff: FileDiffInfo[] } const props = event.properties as { sessionID: string; diff: SnapshotFileDiff[] }
input.setStore("session_diff", props.sessionID, reconcile(list(props.diff), { key: "file" })) input.setStore("session_diff", props.sessionID, reconcile(list(props.diff), { key: "file" }))
break break
} }
@@ -5,7 +5,7 @@ import type {
PermissionRequest, PermissionRequest,
QuestionRequest, QuestionRequest,
SessionStatus, SessionStatus,
FileDiffInfo, SnapshotFileDiff,
Todo, Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache" import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache"
@@ -33,7 +33,7 @@ describe("app session cache", () => {
test("dropSessionCaches clears orphaned parts without message rows", () => { test("dropSessionCaches clears orphaned parts without message rows", () => {
const store: { const store: {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, FileDiffInfo[] | undefined> session_diff: Record<string, SnapshotFileDiff[] | undefined>
todo: Record<string, Todo[] | undefined> todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
@@ -67,7 +67,7 @@ describe("app session cache", () => {
const m = msg("msg_1", "ses_1") const m = msg("msg_1", "ses_1")
const store: { const store: {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, FileDiffInfo[] | undefined> session_diff: Record<string, SnapshotFileDiff[] | undefined>
todo: Record<string, Todo[] | undefined> todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
@@ -4,7 +4,7 @@ import type {
PermissionRequest, PermissionRequest,
QuestionRequest, QuestionRequest,
SessionStatus, SessionStatus,
FileDiffInfo, SnapshotFileDiff,
Todo, Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
@@ -12,7 +12,7 @@ export const SESSION_CACHE_LIMIT = 40
type SessionCache = { type SessionCache = {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, FileDiffInfo[] | undefined> session_diff: Record<string, SnapshotFileDiff[] | undefined>
todo: Record<string, Todo[] | undefined> todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
@@ -13,7 +13,7 @@ import type {
ReferenceInfo, ReferenceInfo,
Session, Session,
SessionStatus, SessionStatus,
FileDiffInfo, SnapshotFileDiff,
Todo, Todo,
VcsInfo, VcsInfo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
@@ -51,7 +51,7 @@ export type State = {
} }
session_working(id: string): boolean session_working(id: string): boolean
session_diff: { session_diff: {
[sessionID: string]: FileDiffInfo[] [sessionID: string]: SnapshotFileDiff[]
} }
todo: { todo: {
[sessionID: string]: Todo[] [sessionID: string]: Todo[]
+3 -3
View File
@@ -8,7 +8,7 @@ import type {
QuestionRequest, QuestionRequest,
Session, Session,
SessionStatus, SessionStatus,
FileDiffInfo, SnapshotFileDiff,
Todo, Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import { batch } from "solid-js" import { batch } from "solid-js"
@@ -139,7 +139,7 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
const [data, setData] = createStore({ const [data, setData] = createStore({
info: {} as Record<string, Session | undefined>, info: {} as Record<string, Session | undefined>,
session_status: {} as Record<string, SessionStatus>, session_status: {} as Record<string, SessionStatus>,
session_diff: {} as Record<string, FileDiffInfo[]>, session_diff: {} as Record<string, SnapshotFileDiff[]>,
todo: {} as Record<string, Todo[]>, todo: {} as Record<string, Todo[]>,
permission: {} as Record<string, PermissionRequest[]>, permission: {} as Record<string, PermissionRequest[]>,
question: {} as Record<string, QuestionRequest[]>, question: {} as Record<string, QuestionRequest[]>,
@@ -769,7 +769,7 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
return return
} }
case "session.diff": { case "session.diff": {
const props = event.properties as { sessionID: string; diff: FileDiffInfo[] } const props = event.properties as { sessionID: string; diff: SnapshotFileDiff[] }
setData("session_diff", props.sessionID, reconcile(cleanDiffs(props.diff), { key: "file" })) setData("session_diff", props.sessionID, reconcile(cleanDiffs(props.diff), { key: "file" }))
return return
} }
@@ -1,6 +1,6 @@
import { createEffect, onCleanup, type JSX } from "solid-js" import { createEffect, onCleanup, type JSX } from "solid-js"
import { makeEventListener } from "@solid-primitives/event-listener" import { makeEventListener } from "@solid-primitives/event-listener"
import type { FileDiffInfo, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
import { SessionReview } from "@opencode-ai/session-ui/session-review" import { SessionReview } from "@opencode-ai/session-ui/session-review"
import type { import type {
SessionReviewCommentActions, SessionReviewCommentActions,
@@ -14,7 +14,7 @@ import type { LineComment } from "@/context/comments"
export type DiffStyle = "unified" | "split" export type DiffStyle = "unified" | "split"
type ReviewDiff = FileDiffInfo | VcsFileDiff type ReviewDiff = SnapshotFileDiff | VcsFileDiff
export interface SessionReviewTabProps { export interface SessionReviewTabProps {
title?: JSX.Element title?: JSX.Element
@@ -8,7 +8,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Mark } from "@opencode-ai/ui/logo" import { Mark } from "@opencode-ai/ui/logo"
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd" import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
import type { DragEvent } from "@thisbeyond/solid-dnd" import type { DragEvent } from "@thisbeyond/solid-dnd"
import type { FileDiffInfo, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
@@ -23,6 +23,7 @@ import { useFile, type SelectedLineRange } from "@/context/file"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout" import { useLayout } from "@/context/layout"
import { useSettings } from "@/context/settings" import { useSettings } from "@/context/settings"
import { useSync } from "@/context/sync"
import { createFileTabListSync } from "@/pages/session/file-tab-scroll" import { createFileTabListSync } from "@/pages/session/file-tab-scroll"
import { FileTabContent } from "@/pages/session/file-tabs" import { FileTabContent } from "@/pages/session/file-tabs"
import { import {
@@ -35,9 +36,15 @@ import {
import { setSessionHandoff } from "@/pages/session/handoff" import { setSessionHandoff } from "@/pages/session/handoff"
import { useSessionLayout } from "@/pages/session/session-layout" import { useSessionLayout } from "@/pages/session/session-layout"
type RenderDiff = (SnapshotFileDiff & { file: string }) | VcsFileDiff
function renderDiff(value: SnapshotFileDiff | VcsFileDiff): value is RenderDiff {
return typeof value.file === "string"
}
export function SessionSidePanel(props: { export function SessionSidePanel(props: {
canReview: () => boolean canReview: () => boolean
diffs: () => (FileDiffInfo | VcsFileDiff)[] diffs: () => (SnapshotFileDiff | VcsFileDiff)[]
diffsReady: () => boolean diffsReady: () => boolean
empty: () => string empty: () => string
hasReview: () => boolean hasReview: () => boolean
@@ -52,6 +59,7 @@ export function SessionSidePanel(props: {
}) { }) {
const layout = useLayout() const layout = useLayout()
const settings = useSettings() const settings = useSettings()
const sync = useSync()
const file = useFile() const file = useFile()
const language = useLanguage() const language = useLanguage()
const command = useCommand() const command = useCommand()
@@ -80,7 +88,7 @@ export function SessionSidePanel(props: {
}) })
const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px")) const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px"))
const diffs = createMemo(() => props.diffs()) const diffs = createMemo(() => props.diffs().filter(renderDiff))
const diffFiles = createMemo(() => diffs().map((d) => d.file)) const diffFiles = createMemo(() => diffs().map((d) => d.file))
const kinds = createMemo(() => { const kinds = createMemo(() => {
const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => { const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => {
@@ -1245,7 +1245,7 @@ export function MessageTimeline(props: {
const value = row() const value = row()
if (value._tag !== "AssistantPart" || value.group.type !== "part") return false if (value._tag !== "AssistantPart" || value.group.type !== "part") return false
const part = getMsgPart(value.group.ref.messageID, value.group.ref.partID) const part = getMsgPart(value.group.ref.messageID, value.group.ref.partID)
return part?.type === "tool" && ["edit", "write", "patch", "apply_patch"].includes(part.tool) return part?.type === "tool" && ["edit", "write", "apply_patch"].includes(part.tool)
} }
const [ready, setReady] = createSignal(initialItem.size <= timelineFallbackItemSize || !asyncFile()) const [ready, setReady] = createSignal(initialItem.size <= timelineFallbackItemSize || !asyncFile())
let contentMeasureFrame: number | undefined let contentMeasureFrame: number | undefined
@@ -1,13 +1,17 @@
import type { FileDiffInfo, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
import type { Kind } from "@/components/file-tree-v2" import type { Kind } from "@/components/file-tree-v2"
import { normalizeFileTreeV2Path } from "@/components/file-tree-v2-model" import { normalizeFileTreeV2Path } from "@/components/file-tree-v2-model"
export type RenderDiff = FileDiffInfo | VcsFileDiff export type RenderDiff = (SnapshotFileDiff & { file: string }) | VcsFileDiff
export function normalizePath(p: string) { export function normalizePath(p: string) {
return normalizeFileTreeV2Path(p) return normalizeFileTreeV2Path(p)
} }
export function filterRenderableDiff(value: SnapshotFileDiff | VcsFileDiff): value is RenderDiff {
return typeof value.file === "string"
}
export function reviewDiffKinds(diffs: RenderDiff[]) { export function reviewDiffKinds(diffs: RenderDiff[]) {
const merge = (a: Kind | undefined, b: Kind) => { const merge = (a: Kind | undefined, b: Kind) => {
if (!a) return b if (!a) return b
@@ -1,5 +1,5 @@
import { createMemo, createSignal, Show, type JSX } from "solid-js" import { createMemo, createSignal, Show, type JSX } from "solid-js"
import type { FileDiffInfo, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
import { import {
SESSION_REVIEW_V2_SIDEBAR_WIDTH_MAX, SESSION_REVIEW_V2_SIDEBAR_WIDTH_MAX,
SESSION_REVIEW_V2_SIDEBAR_WIDTH_MIN, SESSION_REVIEW_V2_SIDEBAR_WIDTH_MIN,
@@ -21,11 +21,16 @@ import type {
import FileTreeV2 from "@/components/file-tree-v2" import FileTreeV2 from "@/components/file-tree-v2"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useSDK } from "@/context/sdk" import { useSDK } from "@/context/sdk"
import { filterReviewFiles, reviewDiffKinds, type RenderDiff } from "@/pages/session/v2/review-diff-kinds" import {
filterRenderableDiff,
filterReviewFiles,
reviewDiffKinds,
type RenderDiff,
} from "@/pages/session/v2/review-diff-kinds"
import type { ReviewPanelV2State } from "@/pages/session/v2/review-panel-v2-state" import type { ReviewPanelV2State } from "@/pages/session/v2/review-panel-v2-state"
import { applyFileListKeyDown, SessionFileListV2 } from "@/pages/session/v2/session-file-list-v2" import { applyFileListKeyDown, SessionFileListV2 } from "@/pages/session/v2/session-file-list-v2"
type ReviewDiff = FileDiffInfo | VcsFileDiff type ReviewDiff = SnapshotFileDiff | VcsFileDiff
export type ReviewPanelV2Props = { export type ReviewPanelV2Props = {
title?: JSX.Element title?: JSX.Element
@@ -49,7 +54,7 @@ export type ReviewPanelV2Props = {
export function ReviewPanelV2(props: ReviewPanelV2Props) { export function ReviewPanelV2(props: ReviewPanelV2Props) {
const sdk = useSDK() const sdk = useSDK()
const diffs = createMemo(() => props.diffs()) const diffs = createMemo(() => props.diffs().filter(filterRenderableDiff))
const filteredFiles = createMemo(() => const filteredFiles = createMemo(() =>
filterReviewFiles( filterReviewFiles(
diffs().map((diff) => diff.file), diffs().map((diff) => diff.file),
+2 -2
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import type { FileDiffInfo } from "@opencode-ai/sdk/v2" import type { SnapshotFileDiff } from "@opencode-ai/sdk/v2"
import type { Message } from "@opencode-ai/sdk/v2/client" import type { Message } from "@opencode-ai/sdk/v2/client"
import { diffs, message } from "./diffs" import { diffs, message } from "./diffs"
@@ -9,7 +9,7 @@ const item = {
additions: 1, additions: 1,
deletions: 1, deletions: 1,
status: "modified", status: "modified",
} satisfies FileDiffInfo } satisfies SnapshotFileDiff
describe("diffs", () => { describe("diffs", () => {
test("keeps valid arrays", () => { test("keeps valid arrays", () => {
+2 -2
View File
@@ -1,7 +1,7 @@
import type { FileDiffInfo } from "@opencode-ai/sdk/v2" import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
import type { Message } from "@opencode-ai/sdk/v2/client" import type { Message } from "@opencode-ai/sdk/v2/client"
type Diff = FileDiffInfo type Diff = SnapshotFileDiff | VcsFileDiff
function diff(value: unknown): value is Diff { function diff(value: unknown): value is Diff {
if (!value || typeof value !== "object" || Array.isArray(value)) return false if (!value || typeof value !== "object" || Array.isArray(value)) return false
Executable → Regular
View File
+1
View File
@@ -33,6 +33,7 @@
"dependencies": { "dependencies": {
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
"@opencode-ai/codemode": "workspace:*",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*", "@opencode-ai/plugin": "workspace:*",
"@opencode-ai/schema": "workspace:*", "@opencode-ai/schema": "workspace:*",
+66
View File
@@ -0,0 +1,66 @@
export * as CodeModeHost from "./code-mode"
import { NodeHttpClient } from "@effect/platform-node"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
import { OpenAPI, Tool } from "@opencode-ai/codemode"
import { Api } from "@opencode-ai/server/api"
import { ServerAuth } from "@opencode-ai/server/auth"
import { Effect, Layer } from "effect"
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
import { OpenApi } from "effect/unstable/httpapi"
import type { Server } from "node:http"
export function replacements(server: Server, password: string): LayerNode.Replacements {
return [ToolRegistry.codeModeReplacement(makeTools(client(server), password))]
}
export function makeTools(client: Layer.Layer<HttpClient.HttpClient>, password: string): ToolRegistry.CodeModeTools {
return {
opencode: bindTools(
OpenAPI.fromSpec({
spec: { ...OpenApi.fromApi(Api) },
baseUrl: "http://opencode.local",
headers: ServerAuth.headers({ username: "opencode", password }),
}).tools,
client,
),
}
}
function client(server: Server) {
return Layer.effect(
HttpClient.HttpClient,
Effect.gen(function* () {
const client = yield* HttpClient.HttpClient
return HttpClient.mapRequest(client, (request) => {
const address = server.address()
if (!address || typeof address === "string") throw new Error("OpenCode server is not listening")
const local =
address.address === "0.0.0.0" ? "127.0.0.1" : address.address === "::" ? "::1" : address.address
const host = local.includes(":") && !local.startsWith("[") ? `[${local}]` : local
const url = new URL(request.url)
return HttpClientRequest.setUrl(
request,
new URL(`${url.pathname}${url.search}${url.hash}`, `http://${host}:${address.port}`),
)
})
}),
).pipe(Layer.provide(NodeHttpClient.layerNodeHttp))
}
function bindTools(tools: OpenAPI.Tools, client: Layer.Layer<HttpClient.HttpClient>): ToolRegistry.CodeModeTools {
return Object.fromEntries(
Object.entries(tools).map(([name, value]) => [
name,
Tool.isDefinition<HttpClient.HttpClient>(value)
? Tool.make({
description: value.description,
input: value.input,
output: value.output,
run: (input) => value.run(input).pipe(Effect.provide(client)),
})
: bindTools(value, client),
]),
)
}
+2 -3
View File
@@ -37,8 +37,7 @@ function defaultCost(model: CurrentModel) {
export function runAgent(input: CurrentAgent): RunAgent { export function runAgent(input: CurrentAgent): RunAgent {
return { return {
id: input.id, name: input.id,
name: input.name,
description: input.description, description: input.description,
mode: input.mode, mode: input.mode,
hidden: input.hidden, hidden: input.hidden,
@@ -54,7 +53,7 @@ export function runCommand(input: CurrentCommand): RunCommand {
export function runSkill(input: CurrentSkill): RunCommand { export function runSkill(input: CurrentSkill): RunCommand {
return { return {
name: input.id, name: input.name,
description: input.description, description: input.description,
source: "skill", source: "skill",
} }
+2 -2
View File
@@ -628,11 +628,11 @@ function emitEdit(state: State): void {
function emitPatch(state: State): void { function emitPatch(state: State): void {
const file = path.join(process.cwd(), "src", "demo-format.ts") const file = path.join(process.cwd(), "src", "demo-format.ts")
const ref = make(state, "patch", { const ref = make(state, "apply_patch", {
patchText: "*** Begin Patch\n*** End Patch", patchText: "*** Begin Patch\n*** End Patch",
}) })
doneTool(state, ref, { doneTool(state, ref, {
title: "patch", title: "apply_patch",
output: "", output: "",
metadata: { metadata: {
files: [ files: [
+2 -2
View File
@@ -333,10 +333,10 @@ export function createPromptState(input: PromptInput): PromptState {
.map((item) => ({ .map((item) => ({
kind: "mention", kind: "mention",
display: "@" + item.name, display: "@" + item.name,
value: item.id, value: item.name,
part: { part: {
type: "agent", type: "agent",
name: item.id, name: item.name,
source: { source: {
start: 0, start: 0,
end: 0, end: 0,
+2
View File
@@ -84,6 +84,7 @@ type RunFooterOptions = {
theme: RunTheme theme: RunTheme
keymap: Keymap<Renderable, KeyEvent> keymap: Keymap<Renderable, KeyEvent>
tuiConfig: RunTuiConfig tuiConfig: RunTuiConfig
backgroundSubagents: boolean
diffStyle: RunDiffStyle diffStyle: RunDiffStyle
onPermissionReply: (input: PermissionReply) => void | Promise<void> onPermissionReply: (input: PermissionReply) => void | Promise<void>
onQuestionReply: (input: QuestionReply) => void | Promise<void> onQuestionReply: (input: QuestionReply) => void | Promise<void>
@@ -325,6 +326,7 @@ export class RunFooter implements FooterApi {
theme: footer.theme, theme: footer.theme,
diffStyle: options.diffStyle, diffStyle: options.diffStyle,
tuiConfig: options.tuiConfig, tuiConfig: options.tuiConfig,
backgroundSubagents: options.backgroundSubagents,
history: footer.history, history: footer.history,
agent: options.agentLabel, agent: options.agentLabel,
onSubmit: footer.handlePrompt, onSubmit: footer.handlePrompt,
+4 -1
View File
@@ -89,6 +89,7 @@ type RunFooterViewProps = {
theme: () => RunTheme theme: () => RunTheme
diffStyle?: RunDiffStyle diffStyle?: RunDiffStyle
tuiConfig: RunTuiConfig tuiConfig: RunTuiConfig
backgroundSubagents: boolean
history?: () => RunPrompt[] history?: () => RunPrompt[]
agent: string agent: string
onSubmit: (input: RunPrompt) => boolean onSubmit: (input: RunPrompt) => boolean
@@ -168,7 +169,9 @@ export function RunFooterView(props: RunFooterViewProps) {
return tabs().findIndex((item) => item.sessionID === sessionID) + 1 return tabs().findIndex((item) => item.sessionID === sessionID) + 1
}) })
const foregroundSubagents = createMemo(() => activeTabs().some((item) => !item.background)) const foregroundSubagents = createMemo(
() => props.backgroundSubagents && activeTabs().some((item) => !item.background),
)
const model = createMemo(() => { const model = createMemo(() => {
const current = props.currentModel() const current = props.currentModel()
return current ? modelInfo(props.providers(), current) : { model: props.state().model, provider: undefined } return current ? modelInfo(props.providers(), current) : { model: props.state().model, provider: undefined }
+4
View File
@@ -1,5 +1,6 @@
import { NodeFileSystem } from "@effect/platform-node" import { NodeFileSystem } from "@effect/platform-node"
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise" import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
import { truthy } from "@opencode-ai/core/flag/flag"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import { Effect } from "effect" import { Effect } from "effect"
import path from "node:path" import path from "node:path"
@@ -83,6 +84,9 @@ export async function runMini(input: MiniCommandInput) {
files: [], files: [],
initialInput, initialInput,
thinking: true, thinking: true,
backgroundSubagents:
truthy("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS") ||
(process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS === undefined && truthy("OPENCODE_EXPERIMENTAL")),
replay: input.replay ?? true, replay: input.replay ?? true,
replayLimit: input.replayLimit, replayLimit: input.replayLimit,
demo: input.demo, demo: input.demo,
+1
View File
@@ -281,6 +281,7 @@ export async function runNonInteractivePrompt(input: Input) {
metadata: { metadata: {
structured: event.data.structured, structured: event.data.structured,
content: event.data.content, content: event.data.content,
outputPaths: event.data.outputPaths,
result: event.data.result, result: event.data.result,
providerCall: current.provider, providerCall: current.provider,
providerResult: { executed: event.data.executed, state: event.data.resultState }, providerResult: { executed: event.data.executed, state: event.data.resultState },
+8 -5
View File
@@ -89,11 +89,12 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
!explicitModel && !sessionModel !explicitModel && !sessionModel
? await client.model ? await client.model
.default({ location: { directory: cwd, workspace } }) .default({ location: { directory: cwd, workspace } })
.then((result) => (result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined)) .then((result) =>
result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined,
)
: undefined : undefined
const model = pickRunModel(explicitModel, input.variant, sessionModel, defaultModel) const model = pickRunModel(explicitModel, input.variant, sessionModel, defaultModel)
if (input.variant && !model) if (input.variant && !model) return reportError(input, "Cannot select a variant before selecting a model", session?.id)
return reportError(input, "Cannot select a variant before selecting a model", session?.id)
if (model) { if (model) {
await waitForCatalogReady({ sdk: client, directory: cwd, workspace, model }) await waitForCatalogReady({ sdk: client, directory: cwd, workspace, model })
const available = await client.model.list({ location: { directory: cwd, workspace } }) const available = await client.model.list({ location: { directory: cwd, workspace } })
@@ -111,7 +112,9 @@ async function execute(input: RunCommandInput, prepared: Prepared, transport: Tr
if (!session && input.title !== undefined) { if (!session && input.title !== undefined) {
await client.session.rename({ await client.session.rename({
sessionID: selected.id, sessionID: selected.id,
title: input.title || prepared.message.slice(0, 50) + (prepared.message.length > 50 ? "..." : ""), title:
input.title ||
prepared.message.slice(0, 50) + (prepared.message.length > 50 ? "..." : ""),
}) })
} }
@@ -197,7 +200,7 @@ async function validateAgent(client: OpenCodeClient, directory: string, name?: s
warning(`failed to list agents${server ? ` from ${server}` : ""}. Falling back to default agent`) warning(`failed to list agents${server ? ` from ${server}` : ""}. Falling back to default agent`)
return return
} }
const agent = agents.find((item) => item.id === name) const agent = agents.find((item) => item.name === name)
if (!agent) { if (!agent) {
warning(`agent "${name}" not found. Falling back to default agent`) warning(`agent "${name}" not found. Falling back to default agent`)
return return
@@ -64,6 +64,7 @@ export type LifecycleInput = {
model: RunInput["model"] model: RunInput["model"]
variant: string | undefined variant: string | undefined
tuiConfig: RunTuiConfig | Promise<RunTuiConfig> tuiConfig: RunTuiConfig | Promise<RunTuiConfig>
backgroundSubagents: boolean
onPermissionReply: (input: PermissionReply) => void | Promise<void> onPermissionReply: (input: PermissionReply) => void | Promise<void>
onQuestionReply: (input: QuestionReply) => void | Promise<void> onQuestionReply: (input: QuestionReply) => void | Promise<void>
onQuestionReject: (input: QuestionReject) => void | Promise<void> onQuestionReject: (input: QuestionReject) => void | Promise<void>
@@ -235,6 +236,7 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
wrote, wrote,
keymap, keymap,
tuiConfig, tuiConfig,
backgroundSubagents: input.backgroundSubagents,
diffStyle: tuiConfig.diff_style ?? "auto", diffStyle: tuiConfig.diff_style ?? "auto",
onPermissionReply: input.onPermissionReply, onPermissionReply: input.onPermissionReply,
onQuestionReply: input.onQuestionReply, onQuestionReply: input.onQuestionReply,
+5
View File
@@ -56,6 +56,7 @@ type RunRuntimeInput = {
files: RunInput["files"] files: RunInput["files"]
initialInput?: string initialInput?: string
thinking: boolean thinking: boolean
backgroundSubagents: boolean
replay?: boolean replay?: boolean
replayLimit?: number replayLimit?: number
demo?: RunInput["demo"] demo?: RunInput["demo"]
@@ -74,6 +75,7 @@ type RunDeferredInput = {
files: RunInput["files"] files: RunInput["files"]
initialInput?: string initialInput?: string
thinking: boolean thinking: boolean
backgroundSubagents: boolean
replay?: boolean replay?: boolean
replayLimit?: number replayLimit?: number
demo?: RunInput["demo"] demo?: RunInput["demo"]
@@ -268,6 +270,7 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
model: state.model, model: state.model,
variant: state.activeVariant, variant: state.activeVariant,
tuiConfig: tuiConfigTask, tuiConfig: tuiConfigTask,
backgroundSubagents: input.backgroundSubagents,
onPermissionReply: async (next) => { onPermissionReply: async (next) => {
if (state.demo?.permission(next)) { if (state.demo?.permission(next)) {
return return
@@ -873,6 +876,7 @@ export async function runInteractiveDeferredMode(input: RunDeferredInput, deps?:
files: input.files, files: input.files,
initialInput: input.initialInput, initialInput: input.initialInput,
thinking: input.thinking, thinking: input.thinking,
backgroundSubagents: input.backgroundSubagents,
replay: input.replay, replay: input.replay,
replayLimit: input.replayLimit, replayLimit: input.replayLimit,
demo: input.demo, demo: input.demo,
@@ -924,6 +928,7 @@ export async function runInteractiveMode(
files: input.files, files: input.files,
initialInput: input.initialInput, initialInput: input.initialInput,
thinking: input.thinking, thinking: input.thinking,
backgroundSubagents: input.backgroundSubagents,
replay: input.replay, replay: input.replay,
replayLimit: input.replayLimit, replayLimit: input.replayLimit,
demo: input.demo, demo: input.demo,
+8 -6
View File
@@ -16,7 +16,7 @@
// backgrounding is intentionally absent: subagent jobs block the parent // backgrounding is intentionally absent: subagent jobs block the parent
// session, so only whole-session `v2.session.background(parentID)` exists. // session, so only whole-session `v2.session.background(parentID)` exists.
import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise" import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise"
import type { SessionMessageAssistantTool, SessionMessageInfo, ToolPart } from "@opencode-ai/sdk/v2" import type { SessionMessage, SessionMessageAssistantTool, ToolPart } from "@opencode-ai/sdk/v2"
import { Locale } from "@opencode-ai/tui/util/locale" import { Locale } from "@opencode-ai/tui/util/locale"
import type { FooterSubagentDetail, FooterSubagentState, FooterSubagentTab, StreamCommit } from "./types" import type { FooterSubagentDetail, FooterSubagentState, FooterSubagentTab, StreamCommit } from "./types"
@@ -54,7 +54,7 @@ export function legacyTool(input: {
callID: tool.id, callID: tool.id,
tool: tool.name, tool: tool.name,
} }
if (tool.state.status === "streaming") { if (tool.state.status === "pending") {
return { return {
...base, ...base,
state: { status: "pending", input: {}, raw: tool.state.input }, state: { status: "pending", input: {}, raw: tool.state.input },
@@ -83,6 +83,7 @@ export function legacyTool(input: {
metadata: { metadata: {
structured: tool.state.structured, structured: tool.state.structured,
content: tool.state.content, content: tool.state.content,
outputPaths: tool.state.outputPaths,
result: tool.state.result, result: tool.state.result,
providerCall, providerCall,
providerResult, providerResult,
@@ -178,7 +179,7 @@ export type SubagentTrackerInput = {
export type SubagentTracker = { export type SubagentTracker = {
main(event: V2Event): void main(event: V2Event): void
foreign(sessionID: string, event: V2Event): void foreign(sessionID: string, event: V2Event): void
hydrate(next: { messages: SessionMessageInfo[]; active: Record<string, unknown> }): Promise<void> hydrate(next: { messages: SessionMessage[]; active: Record<string, unknown> }): Promise<void>
select(sessionID: string | undefined): void select(sessionID: string | undefined): void
snapshot(): FooterSubagentState snapshot(): FooterSubagentState
} }
@@ -315,7 +316,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
messageID, messageID,
tool: item, tool: item,
}) })
if (item.state.status === "streaming") return if (item.state.status === "pending") return
child.callIDs.add(item.id) child.callIDs.add(item.id)
if (item.state.status === "running") { if (item.state.status === "running") {
setFrame(child, `tool:${item.id}`, toolCommit(part, "start")) setFrame(child, `tool:${item.id}`, toolCommit(part, "start"))
@@ -326,7 +327,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
setFrame(child, `tool:${item.id}`, toolCommit(part, "final")) setFrame(child, `tool:${item.id}`, toolCommit(part, "final"))
} }
const rebuild = (child: ChildState, messages: SessionMessageInfo[]) => { const rebuild = (child: ChildState, messages: SessionMessage[]) => {
child.frames = [] child.frames = []
child.text.clear() child.text.clear()
child.projectedText.clear() child.projectedText.clear()
@@ -411,7 +412,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
for (const [id, prompt] of pendingPrompts) { for (const [id, prompt] of pendingPrompts) {
if (!child.prompts.has(id)) child.prompts.set(id, prompt) if (!child.prompts.has(id)) child.prompts.set(id, prompt)
} }
rebuild(child, structuredClone(response.data).toReversed() as SessionMessageInfo[]) rebuild(child, structuredClone(response.data).toReversed() as SessionMessage[])
for (const [id, tool] of pendingTools) { for (const [id, tool] of pendingTools) {
if (!child.finishedTools.has(id) && !child.tools.has(id)) child.tools.set(id, tool) if (!child.finishedTools.has(id) && !child.tools.has(id)) child.tools.set(id, tool)
} }
@@ -621,6 +622,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
input: current?.input ?? {}, input: current?.input ?? {},
structured: event.data.structured, structured: event.data.structured,
content: event.data.content, content: event.data.content,
outputPaths: event.data.outputPaths,
result: event.data.result, result: event.data.result,
}, },
time: { time: {
+16 -15
View File
@@ -2,7 +2,7 @@ import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/p
import type { import type {
PermissionRequest, PermissionRequest,
QuestionRequest, QuestionRequest,
SessionMessageInfo, SessionMessage,
SessionMessageAssistantTool, SessionMessageAssistantTool,
} from "@opencode-ai/sdk/v2" } from "@opencode-ai/sdk/v2"
import { Event } from "@opencode-ai/schema/event" import { Event } from "@opencode-ai/schema/event"
@@ -389,7 +389,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
messageID, messageID,
tool: item, tool: item,
}) })
if (item.state.status === "streaming") return if (item.state.status === "pending") return
if (item.state.status === "running") { if (item.state.status === "running") {
if (state.tools.get(item.id)?.running) return if (state.tools.get(item.id)?.running) return
state.tools.set(item.id, { state.tools.set(item.id, {
@@ -417,7 +417,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
]) ])
} }
const renderMessage = (message: SessionMessageInfo, render: boolean, reuseVisibleWait: boolean) => { const renderMessage = (message: SessionMessage, render: boolean, reuseVisibleWait: boolean) => {
if (message.type === "user") { if (message.type === "user") {
const waiting = state.wait?.messageID === message.id const waiting = state.wait?.messageID === message.id
if (waiting && state.wait) state.wait.promoted = true if (waiting && state.wait) state.wait.promoted = true
@@ -438,34 +438,34 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
return return
} }
if (message.type === "shell") { if (message.type === "shell") {
state.shellCommands.set(message.shellID, message.command) state.shellCommands.set(message.shell.id, message.shell.command)
if (state.shellWait?.messageID === message.id) state.shellWait.callID = message.shellID if (state.shellWait?.messageID === message.id) state.shellWait.callID = message.shell.id
const completed = message.time.completed !== undefined const completed = message.time.completed !== undefined
if (!render) { if (!render) {
// Suppressed history: mark settled shells rendered so live redelivery // Suppressed history: mark settled shells rendered so live redelivery
// stays silent. A still-running shell stays unmarked and renders in // stays silent. A still-running shell stays unmarked and renders in
// full when its live shell.ended event arrives. // full when its live shell.ended event arrives.
if (completed) { if (completed) {
state.shellStarted.add(message.shellID) state.shellStarted.add(message.shell.id)
state.shellEnded.add(message.shellID) state.shellEnded.add(message.shell.id)
} }
return return
} }
if (!state.shellStarted.has(message.shellID)) { if (!state.shellStarted.has(message.shell.id)) {
state.shellStarted.add(message.shellID) state.shellStarted.add(message.shell.id)
write([ write([
shellCommit(message.shellID, message.command, { shellCommit(message.shell.id, message.shell.command, {
text: "running shell", text: "running shell",
phase: "start", phase: "start",
toolState: "running", toolState: "running",
}), }),
]) ])
} }
if (completed && message.output && !state.shellEnded.has(message.shellID)) { if (completed && message.output && !state.shellEnded.has(message.shell.id)) {
state.shellEnded.add(message.shellID) state.shellEnded.add(message.shell.id)
write(shellTerminal(message.shellID, message.command, message, message.output)) write(shellTerminal(message.shell.id, message.shell.command, message.shell, message.output))
} }
if (completed && state.shellWait?.callID === message.shellID) state.shellWait.resolve() if (completed && state.shellWait?.callID === message.shell.id) state.shellWait.resolve()
return return
} }
if (message.type !== "assistant") return if (message.type !== "assistant") return
@@ -534,7 +534,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
input.sdk.question.list({ sessionID: input.sessionID }), input.sdk.question.list({ sessionID: input.sessionID }),
input.sdk.session.active(), input.sdk.session.active(),
]) ])
const projected = structuredClone(messages.data).toReversed() as SessionMessageInfo[] const projected = structuredClone(messages.data).toReversed() as SessionMessage[]
for (const message of projected) renderMessage(message, next.render, next.reuseVisibleWait) for (const message of projected) renderMessage(message, next.render, next.reuseVisibleWait)
state.permissions = permissions.map(permission) state.permissions = permissions.map(permission)
state.questions = questions.map(question) state.questions = questions.map(question)
@@ -762,6 +762,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
input: current?.input ?? {}, input: current?.input ?? {},
structured: event.data.structured, structured: event.data.structured,
content: event.data.content, content: event.data.content,
outputPaths: event.data.outputPaths,
result: event.data.result, result: event.data.result,
}, },
time: { created: current?.started ?? event.created, ran: current?.started, completed: event.created }, time: { created: current?.started ?? event.created, ran: current?.started, completed: event.created },
+2 -2
View File
@@ -119,7 +119,7 @@ type ToolName =
| "bash" | "bash"
| "write" | "write"
| "edit" | "edit"
| "patch" | "apply_patch"
| "batch" | "batch"
| "task" | "task"
| "todowrite" | "todowrite"
@@ -1094,7 +1094,7 @@ const TOOL_RULES = {
}, },
permission: permEdit, permission: permEdit,
}, },
patch: { apply_patch: {
view: { view: {
output: false, output: false,
final: true, final: true,
+1 -1
View File
@@ -113,7 +113,6 @@ export type FooterQueuedPrompt = {
} }
export type RunAgent = { export type RunAgent = {
id: string
name: string name: string
description?: string description?: string
mode: "subagent" | "primary" | "all" mode: "subagent" | "primary" | "all"
@@ -136,6 +135,7 @@ export type RunInput = {
files: RunFilePart[] files: RunFilePart[]
initialInput?: string initialInput?: string
thinking: boolean thinking: boolean
backgroundSubagents: boolean
demo?: boolean demo?: boolean
} }
+11
View File
@@ -7,11 +7,13 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import { InstallationVersion } from "@opencode-ai/core/installation/version" import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { AppProcess } from "@opencode-ai/core/process" import { AppProcess } from "@opencode-ai/core/process"
import { Flock } from "@opencode-ai/core/util/flock"
import { start } from "@opencode-ai/server/process" import { start } from "@opencode-ai/server/process"
import { randomBytes, randomUUID } from "node:crypto" import { randomBytes, randomUUID } from "node:crypto"
import path from "node:path" import path from "node:path"
import { Effect, FileSystem, Logger, Option, Redacted, Schedule, Schema } from "effect" import { Effect, FileSystem, Logger, Option, Redacted, Schedule, Schema } from "effect"
import { HttpServer } from "effect/unstable/http" import { HttpServer } from "effect/unstable/http"
import { CodeModeHost } from "./code-mode"
import { Env } from "./env" import { Env } from "./env"
import { ServiceConfig } from "./services/service-config" import { ServiceConfig } from "./services/service-config"
import { Updater } from "./services/updater" import { Updater } from "./services/updater"
@@ -36,6 +38,14 @@ const processEffect = Effect.fnUntraced(function* (options: Options) {
if (options.mode === "service") yield* Effect.sync(() => process.chdir(Global.Path.home)) if (options.mode === "service") yield* Effect.sync(() => process.chdir(Global.Path.home))
return yield* Effect.scoped( return yield* Effect.scoped(
Effect.gen(function* () { Effect.gen(function* () {
if (options.mode === "service") {
const service = yield* ServiceConfig.options()
yield* Flock.effect(path.basename(service.file, ".json") + "-process", {
dir: path.dirname(service.file),
staleMs: 3_000,
timeoutMs: 15_000,
})
}
const environmentPassword = yield* Env.password const environmentPassword = yield* Env.password
// Keep the lease credential out of the environment inherited by tools. // Keep the lease credential out of the environment inherited by tools.
if (options.mode === "stdio") { if (options.mode === "stdio") {
@@ -54,6 +64,7 @@ const processEffect = Effect.fnUntraced(function* (options: Options) {
hostname: options.hostname ?? config.hostname ?? "127.0.0.1", hostname: options.hostname ?? config.hostname ?? "127.0.0.1",
port: Option.fromNullishOr(options.port ?? config.port), port: Option.fromNullishOr(options.port ?? config.port),
password, password,
replacements: (server) => CodeModeHost.replacements(server, password),
}).pipe(Effect.provide(Logger.layer([], { mergeWithExisting: false }))) }).pipe(Effect.provide(Logger.layer([], { mergeWithExisting: false })))
if (options.mode === "service") yield* register(address, password) if (options.mode === "service") yield* register(address, password)
const url = HttpServer.formatAddress(address) const url = HttpServer.formatAddress(address)
+39
View File
@@ -0,0 +1,39 @@
import { expect, test } from "bun:test"
import { CodeMode } from "@opencode-ai/codemode"
import { Effect, Layer } from "effect"
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
import { CodeModeHost } from "../src/code-mode"
test("exposes the authenticated OpenCode API through CodeMode", async () => {
const requests: Array<{ readonly url: string; readonly authorization?: string }> = []
const client = Layer.succeed(
HttpClient.HttpClient,
HttpClient.make((request) => {
requests.push({ url: request.url, authorization: request.headers.authorization })
return Effect.succeed(
HttpClientResponse.fromWeb(
request,
Response.json(
{ healthy: true, version: "test", pid: 1 },
{ headers: { "content-type": "application/json" } },
),
),
)
}),
)
const result = await CodeMode.make({ tools: CodeModeHost.makeTools(client, "secret") })
.execute("return await tools.opencode.v2.health.get({})")
.pipe(Effect.runPromise)
expect(result).toEqual({
ok: true,
value: { healthy: true, version: "test", pid: 1 },
toolCalls: [{ name: "opencode.v2.health.get" }],
})
expect(requests).toEqual([
{
url: "http://opencode.local/api/health",
authorization: `Basic ${Buffer.from("opencode:secret").toString("base64")}`,
},
])
})
+3 -2
View File
@@ -3,14 +3,15 @@ import { compile, emitEffectImported, emitEffectShape, emitPromise, write } from
import { import {
ClientApi, ClientApi,
effectOmitEndpoints, effectOmitEndpoints,
endpointNames,
groupNames, groupNames,
promiseOmitEndpoints, promiseOmitEndpoints,
} from "@opencode-ai/protocol/client" } from "@opencode-ai/protocol/client"
import { Effect } from "effect" import { Effect } from "effect"
import { fileURLToPath } from "url" import { fileURLToPath } from "url"
const promiseContract = compile(ClientApi, { groupNames, omitEndpoints: promiseOmitEndpoints }) const promiseContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
const effectContract = compile(ClientApi, { groupNames, omitEndpoints: effectOmitEndpoints }) const effectContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: effectOmitEndpoints })
await Effect.runPromise( await Effect.runPromise(
Effect.all( Effect.all(
+1
View File
@@ -1,6 +1,7 @@
export { export {
ClientApi, ClientApi,
effectOmitEndpoints, effectOmitEndpoints,
endpointNames,
groupNames, groupNames,
promiseOmitEndpoints, promiseOmitEndpoints,
} from "@opencode-ai/protocol/client" } from "@opencode-ai/protocol/client"
+114 -143
View File
@@ -111,167 +111,157 @@ export type Endpoint4_8Input = {
export type Endpoint4_8Output = EffectValue<ReturnType<RawClient["server.session"]["session.rename"]>> export type Endpoint4_8Output = EffectValue<ReturnType<RawClient["server.session"]["session.rename"]>>
export type SessionRenameOperation<E = never> = (input: Endpoint4_8Input) => Effect.Effect<Endpoint4_8Output, E> export type SessionRenameOperation<E = never> = (input: Endpoint4_8Input) => Effect.Effect<Endpoint4_8Output, E>
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.move"]>[0] type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
export type Endpoint4_9Input = { export type Endpoint4_9Input = {
readonly sessionID: Endpoint4_9Request["params"]["sessionID"] readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
readonly destination: Endpoint4_9Request["payload"]["destination"] readonly id?: Endpoint4_9Request["payload"]["id"]
readonly moveChanges?: Endpoint4_9Request["payload"]["moveChanges"] readonly prompt: Endpoint4_9Request["payload"]["prompt"]
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
readonly resume?: Endpoint4_9Request["payload"]["resume"]
} }
export type Endpoint4_9Output = EffectValue<ReturnType<RawClient["server.session"]["session.move"]>> export type Endpoint4_9Output = EffectValue<ReturnType<RawClient["server.session"]["session.prompt"]>>["data"]
export type SessionMoveOperation<E = never> = (input: Endpoint4_9Input) => Effect.Effect<Endpoint4_9Output, E> export type SessionPromptOperation<E = never> = (input: Endpoint4_9Input) => Effect.Effect<Endpoint4_9Output, E>
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.prompt"]>[0] type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.command"]>[0]
export type Endpoint4_10Input = { export type Endpoint4_10Input = {
readonly sessionID: Endpoint4_10Request["params"]["sessionID"] readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
readonly id?: Endpoint4_10Request["payload"]["id"] readonly id?: Endpoint4_10Request["payload"]["id"]
readonly prompt: Endpoint4_10Request["payload"]["prompt"] readonly command: Endpoint4_10Request["payload"]["command"]
readonly arguments?: Endpoint4_10Request["payload"]["arguments"]
readonly agent?: Endpoint4_10Request["payload"]["agent"]
readonly model?: Endpoint4_10Request["payload"]["model"]
readonly files?: Endpoint4_10Request["payload"]["files"]
readonly agents?: Endpoint4_10Request["payload"]["agents"]
readonly delivery?: Endpoint4_10Request["payload"]["delivery"] readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
readonly resume?: Endpoint4_10Request["payload"]["resume"] readonly resume?: Endpoint4_10Request["payload"]["resume"]
} }
export type Endpoint4_10Output = EffectValue<ReturnType<RawClient["server.session"]["session.prompt"]>>["data"] export type Endpoint4_10Output = EffectValue<ReturnType<RawClient["server.session"]["session.command"]>>["data"]
export type SessionPromptOperation<E = never> = (input: Endpoint4_10Input) => Effect.Effect<Endpoint4_10Output, E> export type SessionCommandOperation<E = never> = (input: Endpoint4_10Input) => Effect.Effect<Endpoint4_10Output, E>
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.command"]>[0] type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
export type Endpoint4_11Input = { export type Endpoint4_11Input = {
readonly sessionID: Endpoint4_11Request["params"]["sessionID"] readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
readonly id?: Endpoint4_11Request["payload"]["id"] readonly id?: Endpoint4_11Request["payload"]["id"]
readonly command: Endpoint4_11Request["payload"]["command"] readonly skill: Endpoint4_11Request["payload"]["skill"]
readonly arguments?: Endpoint4_11Request["payload"]["arguments"]
readonly agent?: Endpoint4_11Request["payload"]["agent"]
readonly model?: Endpoint4_11Request["payload"]["model"]
readonly files?: Endpoint4_11Request["payload"]["files"]
readonly agents?: Endpoint4_11Request["payload"]["agents"]
readonly delivery?: Endpoint4_11Request["payload"]["delivery"]
readonly resume?: Endpoint4_11Request["payload"]["resume"] readonly resume?: Endpoint4_11Request["payload"]["resume"]
} }
export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.command"]>>["data"] export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.skill"]>>
export type SessionCommandOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E> export type SessionSkillOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E>
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.skill"]>[0] type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
export type Endpoint4_12Input = { export type Endpoint4_12Input = {
readonly sessionID: Endpoint4_12Request["params"]["sessionID"] readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
readonly id?: Endpoint4_12Request["payload"]["id"] readonly text: Endpoint4_12Request["payload"]["text"]
readonly skill: Endpoint4_12Request["payload"]["skill"] readonly description?: Endpoint4_12Request["payload"]["description"]
readonly resume?: Endpoint4_12Request["payload"]["resume"] readonly metadata?: Endpoint4_12Request["payload"]["metadata"]
} }
export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.skill"]>> export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>>
export type SessionSkillOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E> export type SessionSyntheticOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E>
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0] type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
export type Endpoint4_13Input = { export type Endpoint4_13Input = {
readonly sessionID: Endpoint4_13Request["params"]["sessionID"] readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
readonly text: Endpoint4_13Request["payload"]["text"] readonly id?: Endpoint4_13Request["payload"]["id"]
readonly description?: Endpoint4_13Request["payload"]["description"] readonly command: Endpoint4_13Request["payload"]["command"]
readonly metadata?: Endpoint4_13Request["payload"]["metadata"]
readonly resume?: Endpoint4_13Request["payload"]["resume"]
} }
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>> export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.shell"]>>
export type SessionSyntheticOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E> export type SessionShellOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E>
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.shell"]>[0] type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
export type Endpoint4_14Input = { export type Endpoint4_14Input = {
readonly sessionID: Endpoint4_14Request["params"]["sessionID"] readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
readonly id?: Endpoint4_14Request["payload"]["id"] readonly id?: Endpoint4_14Request["payload"]["id"]
readonly command: Endpoint4_14Request["payload"]["command"]
} }
export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.shell"]>> export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>["data"]
export type SessionShellOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E> export type SessionCompactOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E>
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.compact"]>[0] type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
export type Endpoint4_15Input = { export type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] }
readonly sessionID: Endpoint4_15Request["params"]["sessionID"] export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>>
readonly id?: Endpoint4_15Request["payload"]["id"] export type SessionWaitOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E>
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
export type Endpoint4_16Input = {
readonly sessionID: Endpoint4_16Request["params"]["sessionID"]
readonly messageID: Endpoint4_16Request["payload"]["messageID"]
readonly files?: Endpoint4_16Request["payload"]["files"]
} }
export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>["data"] export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"]
export type SessionCompactOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E> export type SessionRevertStageOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E>
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.wait"]>[0] type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
export type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] } export type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>> export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>>
export type SessionWaitOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E> export type SessionRevertClearOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E>
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0] type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
export type Endpoint4_17Input = {
readonly sessionID: Endpoint4_17Request["params"]["sessionID"]
readonly messageID: Endpoint4_17Request["payload"]["messageID"]
readonly files?: Endpoint4_17Request["payload"]["files"]
}
export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"]
export type SessionRevertStageOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E>
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] } export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
export type Endpoint4_18Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>> export type Endpoint4_18Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>>
export type SessionRevertClearOperation<E = never> = (input: Endpoint4_18Input) => Effect.Effect<Endpoint4_18Output, E> export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_18Input) => Effect.Effect<Endpoint4_18Output, E>
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0] type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context"]>[0]
export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
export type Endpoint4_19Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>> export type Endpoint4_19Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"]
export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_19Input) => Effect.Effect<Endpoint4_19Output, E> export type SessionContextOperation<E = never> = (input: Endpoint4_19Input) => Effect.Effect<Endpoint4_19Output, E>
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context"]>[0] type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
export type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] } export type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] }
export type Endpoint4_20Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"] export type Endpoint4_20Output = EffectValue<
export type SessionContextOperation<E = never> = (input: Endpoint4_20Input) => Effect.Effect<Endpoint4_20Output, E>
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
export type Endpoint4_21Input = { readonly sessionID: Endpoint4_21Request["params"]["sessionID"] }
export type Endpoint4_21Output = EffectValue<
ReturnType<RawClient["server.session"]["session.instructions.entry.list"]> ReturnType<RawClient["server.session"]["session.instructions.entry.list"]>
>["data"] >["data"]
export type SessionInstructionsEntryListOperation<E = never> = ( export type SessionInstructionsEntryListOperation<E = never> = (
input: Endpoint4_20Input,
) => Effect.Effect<Endpoint4_20Output, E>
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
export type Endpoint4_21Input = {
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
readonly key: Endpoint4_21Request["params"]["key"]
readonly value: Endpoint4_21Request["payload"]["value"]
}
export type Endpoint4_21Output = EffectValue<ReturnType<RawClient["server.session"]["session.instructions.entry.put"]>>
export type SessionInstructionsEntryPutOperation<E = never> = (
input: Endpoint4_21Input, input: Endpoint4_21Input,
) => Effect.Effect<Endpoint4_21Output, E> ) => Effect.Effect<Endpoint4_21Output, E>
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0] type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
export type Endpoint4_22Input = { export type Endpoint4_22Input = {
readonly sessionID: Endpoint4_22Request["params"]["sessionID"] readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
readonly key: Endpoint4_22Request["params"]["key"] readonly key: Endpoint4_22Request["params"]["key"]
readonly value: Endpoint4_22Request["payload"]["value"]
} }
export type Endpoint4_22Output = EffectValue<ReturnType<RawClient["server.session"]["session.instructions.entry.put"]>> export type Endpoint4_22Output = EffectValue<
export type SessionInstructionsEntryPutOperation<E = never> = (
input: Endpoint4_22Input,
) => Effect.Effect<Endpoint4_22Output, E>
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
export type Endpoint4_23Input = {
readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
readonly key: Endpoint4_23Request["params"]["key"]
}
export type Endpoint4_23Output = EffectValue<
ReturnType<RawClient["server.session"]["session.instructions.entry.remove"]> ReturnType<RawClient["server.session"]["session.instructions.entry.remove"]>
> >
export type SessionInstructionsEntryRemoveOperation<E = never> = ( export type SessionInstructionsEntryRemoveOperation<E = never> = (
input: Endpoint4_23Input, input: Endpoint4_22Input,
) => Effect.Effect<Endpoint4_23Output, E> ) => Effect.Effect<Endpoint4_22Output, E>
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.log"]>[0] type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.log"]>[0]
export type Endpoint4_24Input = { export type Endpoint4_23Input = {
readonly sessionID: Endpoint4_24Request["params"]["sessionID"] readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
readonly after?: Endpoint4_24Request["query"]["after"] readonly after?: Endpoint4_23Request["query"]["after"]
readonly follow?: Endpoint4_24Request["query"]["follow"] readonly follow?: Endpoint4_23Request["query"]["follow"]
} }
export type Endpoint4_24Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>> export type Endpoint4_23Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>>
export type SessionLogOperation<E = never> = (input: Endpoint4_24Input) => Stream.Stream<Endpoint4_24Output, E> export type SessionLogOperation<E = never> = (input: Endpoint4_23Input) => Stream.Stream<Endpoint4_23Output, E>
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0] type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
export type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
export type Endpoint4_24Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>>
export type SessionInterruptOperation<E = never> = (input: Endpoint4_24Input) => Effect.Effect<Endpoint4_24Output, E>
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.background"]>[0]
export type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] } export type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] }
export type Endpoint4_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>> export type Endpoint4_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>>
export type SessionInterruptOperation<E = never> = (input: Endpoint4_25Input) => Effect.Effect<Endpoint4_25Output, E> export type SessionBackgroundOperation<E = never> = (input: Endpoint4_25Input) => Effect.Effect<Endpoint4_25Output, E>
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.background"]>[0] type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.message"]>[0]
export type Endpoint4_26Input = { readonly sessionID: Endpoint4_26Request["params"]["sessionID"] } export type Endpoint4_26Input = {
export type Endpoint4_26Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>> readonly sessionID: Endpoint4_26Request["params"]["sessionID"]
export type SessionBackgroundOperation<E = never> = (input: Endpoint4_26Input) => Effect.Effect<Endpoint4_26Output, E> readonly messageID: Endpoint4_26Request["params"]["messageID"]
type Endpoint4_27Request = Parameters<RawClient["server.session"]["session.message"]>[0]
export type Endpoint4_27Input = {
readonly sessionID: Endpoint4_27Request["params"]["sessionID"]
readonly messageID: Endpoint4_27Request["params"]["messageID"]
} }
export type Endpoint4_27Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"] export type Endpoint4_26Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"]
export type SessionMessageOperation<E = never> = (input: Endpoint4_27Input) => Effect.Effect<Endpoint4_27Output, E> export type SessionMessageOperation<E = never> = (input: Endpoint4_26Input) => Effect.Effect<Endpoint4_26Output, E>
export interface SessionApi<E = never> { export interface SessionApi<E = never> {
readonly list: SessionListOperation<E> readonly list: SessionListOperation<E>
@@ -283,7 +273,6 @@ export interface SessionApi<E = never> {
readonly switchAgent: SessionSwitchAgentOperation<E> readonly switchAgent: SessionSwitchAgentOperation<E>
readonly switchModel: SessionSwitchModelOperation<E> readonly switchModel: SessionSwitchModelOperation<E>
readonly rename: SessionRenameOperation<E> readonly rename: SessionRenameOperation<E>
readonly move: SessionMoveOperation<E>
readonly prompt: SessionPromptOperation<E> readonly prompt: SessionPromptOperation<E>
readonly command: SessionCommandOperation<E> readonly command: SessionCommandOperation<E>
readonly skill: SessionSkillOperation<E> readonly skill: SessionSkillOperation<E>
@@ -291,11 +280,9 @@ export interface SessionApi<E = never> {
readonly shell: SessionShellOperation<E> readonly shell: SessionShellOperation<E>
readonly compact: SessionCompactOperation<E> readonly compact: SessionCompactOperation<E>
readonly wait: SessionWaitOperation<E> readonly wait: SessionWaitOperation<E>
readonly revert: { readonly revertStage: SessionRevertStageOperation<E>
readonly stage: SessionRevertStageOperation<E> readonly revertClear: SessionRevertClearOperation<E>
readonly clear: SessionRevertClearOperation<E> readonly revertCommit: SessionRevertCommitOperation<E>
readonly commit: SessionRevertCommitOperation<E>
}
readonly context: SessionContextOperation<E> readonly context: SessionContextOperation<E>
readonly instructions: { readonly instructions: {
readonly entry: { readonly entry: {
@@ -440,15 +427,11 @@ export type IntegrationAttemptCancelOperation<E = never> = (
export interface IntegrationApi<E = never> { export interface IntegrationApi<E = never> {
readonly list: IntegrationListOperation<E> readonly list: IntegrationListOperation<E>
readonly get: IntegrationGetOperation<E> readonly get: IntegrationGetOperation<E>
readonly connect: { readonly connectKey: IntegrationConnectKeyOperation<E>
readonly key: IntegrationConnectKeyOperation<E> readonly connectOauth: IntegrationConnectOauthOperation<E>
readonly oauth: IntegrationConnectOauthOperation<E> readonly attemptStatus: IntegrationAttemptStatusOperation<E>
} readonly attemptComplete: IntegrationAttemptCompleteOperation<E>
readonly attempt: { readonly attemptCancel: IntegrationAttemptCancelOperation<E>
readonly status: IntegrationAttemptStatusOperation<E>
readonly complete: IntegrationAttemptCompleteOperation<E>
readonly cancel: IntegrationAttemptCancelOperation<E>
}
} }
type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0] type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
@@ -456,16 +439,8 @@ export type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query
export type Endpoint10_0Output = EffectValue<ReturnType<RawClient["server.mcp"]["mcp.list"]>> export type Endpoint10_0Output = EffectValue<ReturnType<RawClient["server.mcp"]["mcp.list"]>>
export type ServerMcpListOperation<E = never> = (input?: Endpoint10_0Input) => Effect.Effect<Endpoint10_0Output, E> export type ServerMcpListOperation<E = never> = (input?: Endpoint10_0Input) => Effect.Effect<Endpoint10_0Output, E>
type Endpoint10_1Request = Parameters<RawClient["server.mcp"]["mcp.resource.catalog"]>[0]
export type Endpoint10_1Input = { readonly location?: Endpoint10_1Request["query"]["location"] }
export type Endpoint10_1Output = EffectValue<ReturnType<RawClient["server.mcp"]["mcp.resource.catalog"]>>
export type ServerMcpResourceCatalogOperation<E = never> = (
input?: Endpoint10_1Input,
) => Effect.Effect<Endpoint10_1Output, E>
export interface ServerMcpApi<E = never> { export interface ServerMcpApi<E = never> {
readonly list: ServerMcpListOperation<E> readonly list: ServerMcpListOperation<E>
readonly resource: { readonly catalog: ServerMcpResourceCatalogOperation<E> }
} }
type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0] type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
@@ -515,7 +490,7 @@ export interface ProjectApi<E = never> {
type Endpoint13_0Request = Parameters<RawClient["server.form"]["form.request.list"]>[0] type Endpoint13_0Request = Parameters<RawClient["server.form"]["form.request.list"]>[0]
export type Endpoint13_0Input = { readonly location?: Endpoint13_0Request["query"]["location"] } export type Endpoint13_0Input = { readonly location?: Endpoint13_0Request["query"]["location"] }
export type Endpoint13_0Output = EffectValue<ReturnType<RawClient["server.form"]["form.request.list"]>> export type Endpoint13_0Output = EffectValue<ReturnType<RawClient["server.form"]["form.request.list"]>>
export type FormRequestListOperation<E = never> = (input?: Endpoint13_0Input) => Effect.Effect<Endpoint13_0Output, E> export type FormListRequestsOperation<E = never> = (input?: Endpoint13_0Input) => Effect.Effect<Endpoint13_0Output, E>
type Endpoint13_1Request = Parameters<RawClient["server.form"]["session.form.list"]>[0] type Endpoint13_1Request = Parameters<RawClient["server.form"]["session.form.list"]>[0]
export type Endpoint13_1Input = { readonly sessionID: Endpoint13_1Request["params"]["sessionID"] } export type Endpoint13_1Input = { readonly sessionID: Endpoint13_1Request["params"]["sessionID"] }
@@ -569,7 +544,7 @@ export type Endpoint13_6Output = EffectValue<ReturnType<RawClient["server.form"]
export type FormCancelOperation<E = never> = (input: Endpoint13_6Input) => Effect.Effect<Endpoint13_6Output, E> export type FormCancelOperation<E = never> = (input: Endpoint13_6Input) => Effect.Effect<Endpoint13_6Output, E>
export interface FormApi<E = never> { export interface FormApi<E = never> {
readonly request: { readonly list: FormRequestListOperation<E> } readonly listRequests: FormListRequestsOperation<E>
readonly list: FormListOperation<E> readonly list: FormListOperation<E>
readonly create: FormCreateOperation<E> readonly create: FormCreateOperation<E>
readonly get: FormGetOperation<E> readonly get: FormGetOperation<E>
@@ -581,7 +556,7 @@ export interface FormApi<E = never> {
type Endpoint14_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0] type Endpoint14_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0]
export type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] } export type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] }
export type Endpoint14_0Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.request.list"]>> export type Endpoint14_0Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.request.list"]>>
export type PermissionRequestListOperation<E = never> = ( export type PermissionListRequestsOperation<E = never> = (
input?: Endpoint14_0Input, input?: Endpoint14_0Input,
) => Effect.Effect<Endpoint14_0Output, E> ) => Effect.Effect<Endpoint14_0Output, E>
@@ -590,14 +565,14 @@ export type Endpoint14_1Input = { readonly projectID?: Endpoint14_1Request["quer
export type Endpoint14_1Output = EffectValue< export type Endpoint14_1Output = EffectValue<
ReturnType<RawClient["server.permission"]["permission.saved.list"]> ReturnType<RawClient["server.permission"]["permission.saved.list"]>
>["data"] >["data"]
export type PermissionSavedListOperation<E = never> = ( export type PermissionListSavedOperation<E = never> = (
input?: Endpoint14_1Input, input?: Endpoint14_1Input,
) => Effect.Effect<Endpoint14_1Output, E> ) => Effect.Effect<Endpoint14_1Output, E>
type Endpoint14_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0] type Endpoint14_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0]
export type Endpoint14_2Input = { readonly id: Endpoint14_2Request["params"]["id"] } export type Endpoint14_2Input = { readonly id: Endpoint14_2Request["params"]["id"] }
export type Endpoint14_2Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.saved.remove"]>> export type Endpoint14_2Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.saved.remove"]>>
export type PermissionSavedRemoveOperation<E = never> = ( export type PermissionRemoveSavedOperation<E = never> = (
input: Endpoint14_2Input, input: Endpoint14_2Input,
) => Effect.Effect<Endpoint14_2Output, E> ) => Effect.Effect<Endpoint14_2Output, E>
@@ -645,8 +620,9 @@ export type Endpoint14_6Output = EffectValue<ReturnType<RawClient["server.permis
export type PermissionReplyOperation<E = never> = (input: Endpoint14_6Input) => Effect.Effect<Endpoint14_6Output, E> export type PermissionReplyOperation<E = never> = (input: Endpoint14_6Input) => Effect.Effect<Endpoint14_6Output, E>
export interface PermissionApi<E = never> { export interface PermissionApi<E = never> {
readonly request: { readonly list: PermissionRequestListOperation<E> } readonly listRequests: PermissionListRequestsOperation<E>
readonly saved: { readonly list: PermissionSavedListOperation<E>; readonly remove: PermissionSavedRemoveOperation<E> } readonly listSaved: PermissionListSavedOperation<E>
readonly removeSaved: PermissionRemoveSavedOperation<E>
readonly create: PermissionCreateOperation<E> readonly create: PermissionCreateOperation<E>
readonly list: PermissionListOperation<E> readonly list: PermissionListOperation<E>
readonly get: PermissionGetOperation<E> readonly get: PermissionGetOperation<E>
@@ -815,7 +791,7 @@ export interface ShellApi<E = never> {
type Endpoint21_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0] type Endpoint21_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0]
export type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] } export type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] }
export type Endpoint21_0Output = EffectValue<ReturnType<RawClient["server.question"]["question.request.list"]>> export type Endpoint21_0Output = EffectValue<ReturnType<RawClient["server.question"]["question.request.list"]>>
export type QuestionRequestListOperation<E = never> = ( export type QuestionListRequestsOperation<E = never> = (
input?: Endpoint21_0Input, input?: Endpoint21_0Input,
) => Effect.Effect<Endpoint21_0Output, E> ) => Effect.Effect<Endpoint21_0Output, E>
@@ -842,7 +818,7 @@ export type Endpoint21_3Output = EffectValue<ReturnType<RawClient["server.questi
export type QuestionRejectOperation<E = never> = (input: Endpoint21_3Input) => Effect.Effect<Endpoint21_3Output, E> export type QuestionRejectOperation<E = never> = (input: Endpoint21_3Input) => Effect.Effect<Endpoint21_3Output, E>
export interface QuestionApi<E = never> { export interface QuestionApi<E = never> {
readonly request: { readonly list: QuestionRequestListOperation<E> } readonly listRequests: QuestionListRequestsOperation<E>
readonly list: QuestionListOperation<E> readonly list: QuestionListOperation<E>
readonly reply: QuestionReplyOperation<E> readonly reply: QuestionReplyOperation<E>
readonly reject: QuestionRejectOperation<E> readonly reject: QuestionRejectOperation<E>
@@ -912,15 +888,10 @@ export interface VcsApi<E = never> {
} }
export type Endpoint25_0Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location"]>> export type Endpoint25_0Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location"]>>
export type DebugLocationListOperation<E = never> = () => Effect.Effect<Endpoint25_0Output, E> export type DebugLocationOperation<E = never> = () => Effect.Effect<Endpoint25_0Output, E>
type Endpoint25_1Request = Parameters<RawClient["server.debug"]["debug.location.evict"]>[0]
export type Endpoint25_1Input = { readonly location?: Endpoint25_1Request["query"]["location"] }
export type Endpoint25_1Output = EffectValue<ReturnType<RawClient["server.debug"]["debug.location.evict"]>>
export type DebugLocationEvictOperation<E = never> = (input?: Endpoint25_1Input) => Effect.Effect<Endpoint25_1Output, E>
export interface DebugApi<E = never> { export interface DebugApi<E = never> {
readonly location: { readonly list: DebugLocationListOperation<E>; readonly evict: DebugLocationEvictOperation<E> } readonly location: DebugLocationOperation<E>
} }
export interface AppApi<E = never> { export interface AppApi<E = never> {
+121 -149
View File
@@ -141,27 +141,15 @@ const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Inp
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
) )
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.move"]>[0] type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
type Endpoint4_9Input = { type Endpoint4_9Input = {
readonly sessionID: Endpoint4_9Request["params"]["sessionID"] readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
readonly destination: Endpoint4_9Request["payload"]["destination"] readonly id?: Endpoint4_9Request["payload"]["id"]
readonly moveChanges?: Endpoint4_9Request["payload"]["moveChanges"] readonly prompt: Endpoint4_9Request["payload"]["prompt"]
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
readonly resume?: Endpoint4_9Request["payload"]["resume"]
} }
const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) => const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) =>
raw["session.move"]({
params: { sessionID: input["sessionID"] },
payload: { destination: input["destination"], moveChanges: input["moveChanges"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
type Endpoint4_10Input = {
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
readonly id?: Endpoint4_10Request["payload"]["id"]
readonly prompt: Endpoint4_10Request["payload"]["prompt"]
readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
readonly resume?: Endpoint4_10Request["payload"]["resume"]
}
const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) =>
raw["session.prompt"]({ raw["session.prompt"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] }, payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
@@ -170,20 +158,20 @@ const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10I
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.command"]>[0] type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.command"]>[0]
type Endpoint4_11Input = { type Endpoint4_10Input = {
readonly sessionID: Endpoint4_11Request["params"]["sessionID"] readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
readonly id?: Endpoint4_11Request["payload"]["id"] readonly id?: Endpoint4_10Request["payload"]["id"]
readonly command: Endpoint4_11Request["payload"]["command"] readonly command: Endpoint4_10Request["payload"]["command"]
readonly arguments?: Endpoint4_11Request["payload"]["arguments"] readonly arguments?: Endpoint4_10Request["payload"]["arguments"]
readonly agent?: Endpoint4_11Request["payload"]["agent"] readonly agent?: Endpoint4_10Request["payload"]["agent"]
readonly model?: Endpoint4_11Request["payload"]["model"] readonly model?: Endpoint4_10Request["payload"]["model"]
readonly files?: Endpoint4_11Request["payload"]["files"] readonly files?: Endpoint4_10Request["payload"]["files"]
readonly agents?: Endpoint4_11Request["payload"]["agents"] readonly agents?: Endpoint4_10Request["payload"]["agents"]
readonly delivery?: Endpoint4_11Request["payload"]["delivery"] readonly delivery?: Endpoint4_10Request["payload"]["delivery"]
readonly resume?: Endpoint4_11Request["payload"]["resume"] readonly resume?: Endpoint4_10Request["payload"]["resume"]
} }
const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) => const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) =>
raw["session.command"]({ raw["session.command"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { payload: {
@@ -202,73 +190,67 @@ const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11I
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.skill"]>[0] type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
type Endpoint4_12Input = { type Endpoint4_11Input = {
readonly sessionID: Endpoint4_12Request["params"]["sessionID"] readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
readonly id?: Endpoint4_12Request["payload"]["id"] readonly id?: Endpoint4_11Request["payload"]["id"]
readonly skill: Endpoint4_12Request["payload"]["skill"] readonly skill: Endpoint4_11Request["payload"]["skill"]
readonly resume?: Endpoint4_12Request["payload"]["resume"] readonly resume?: Endpoint4_11Request["payload"]["resume"]
} }
const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) => const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) =>
raw["session.skill"]({ raw["session.skill"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { id: input["id"], skill: input["skill"], resume: input["resume"] }, payload: { id: input["id"], skill: input["skill"], resume: input["resume"] },
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0] type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
type Endpoint4_13Input = { type Endpoint4_12Input = {
readonly sessionID: Endpoint4_13Request["params"]["sessionID"] readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
readonly text: Endpoint4_13Request["payload"]["text"] readonly text: Endpoint4_12Request["payload"]["text"]
readonly description?: Endpoint4_13Request["payload"]["description"] readonly description?: Endpoint4_12Request["payload"]["description"]
readonly metadata?: Endpoint4_13Request["payload"]["metadata"] readonly metadata?: Endpoint4_12Request["payload"]["metadata"]
readonly resume?: Endpoint4_13Request["payload"]["resume"]
} }
const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) => const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) =>
raw["session.synthetic"]({ raw["session.synthetic"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { payload: { text: input["text"], description: input["description"], metadata: input["metadata"] },
text: input["text"],
description: input["description"],
metadata: input["metadata"],
resume: input["resume"],
},
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.shell"]>[0] type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
type Endpoint4_14Input = { type Endpoint4_13Input = {
readonly sessionID: Endpoint4_14Request["params"]["sessionID"] readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
readonly id?: Endpoint4_14Request["payload"]["id"] readonly id?: Endpoint4_13Request["payload"]["id"]
readonly command: Endpoint4_14Request["payload"]["command"] readonly command: Endpoint4_13Request["payload"]["command"]
} }
const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) => const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) =>
raw["session.shell"]({ raw["session.shell"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { id: input["id"], command: input["command"] }, payload: { id: input["id"], command: input["command"] },
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.compact"]>[0] type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
type Endpoint4_15Input = { type Endpoint4_14Input = {
readonly sessionID: Endpoint4_15Request["params"]["sessionID"] readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
readonly id?: Endpoint4_15Request["payload"]["id"] readonly id?: Endpoint4_14Request["payload"]["id"]
} }
const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) => const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) =>
raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe( raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.wait"]>[0] type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] } type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] }
const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) => const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) =>
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0] type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
type Endpoint4_17Input = { type Endpoint4_16Input = {
readonly sessionID: Endpoint4_17Request["params"]["sessionID"] readonly sessionID: Endpoint4_16Request["params"]["sessionID"]
readonly messageID: Endpoint4_17Request["payload"]["messageID"] readonly messageID: Endpoint4_16Request["payload"]["messageID"]
readonly files?: Endpoint4_17Request["payload"]["files"] readonly files?: Endpoint4_16Request["payload"]["files"]
} }
const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) => const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) =>
raw["session.revert.stage"]({ raw["session.revert.stage"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
payload: { messageID: input["messageID"], files: input["files"] }, payload: { messageID: input["messageID"], files: input["files"] },
@@ -277,61 +259,61 @@ const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17I
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0] type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] } type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) => const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0] type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) => const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) =>
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context"]>[0] type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context"]>[0]
type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] } type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) => const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) =>
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe( raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0] type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.instructions.entry.list"]>[0]
type Endpoint4_21Input = { readonly sessionID: Endpoint4_21Request["params"]["sessionID"] } type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] }
const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) => const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) =>
raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe( raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
) )
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0] type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.instructions.entry.put"]>[0]
type Endpoint4_22Input = { type Endpoint4_21Input = {
readonly sessionID: Endpoint4_22Request["params"]["sessionID"] readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
readonly key: Endpoint4_22Request["params"]["key"] readonly key: Endpoint4_21Request["params"]["key"]
readonly value: Endpoint4_22Request["payload"]["value"] readonly value: Endpoint4_21Request["payload"]["value"]
} }
const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) => const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) =>
raw["session.instructions.entry.put"]({ raw["session.instructions.entry.put"]({
params: { sessionID: input["sessionID"], key: input["key"] }, params: { sessionID: input["sessionID"], key: input["key"] },
payload: { value: input["value"] }, payload: { value: input["value"] },
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0] type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.instructions.entry.remove"]>[0]
type Endpoint4_23Input = { type Endpoint4_22Input = {
readonly sessionID: Endpoint4_23Request["params"]["sessionID"] readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
readonly key: Endpoint4_23Request["params"]["key"] readonly key: Endpoint4_22Request["params"]["key"]
} }
const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) => const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) =>
raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe( raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
) )
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.log"]>[0] type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.log"]>[0]
type Endpoint4_24Input = { type Endpoint4_23Input = {
readonly sessionID: Endpoint4_24Request["params"]["sessionID"] readonly sessionID: Endpoint4_23Request["params"]["sessionID"]
readonly after?: Endpoint4_24Request["query"]["after"] readonly after?: Endpoint4_23Request["query"]["after"]
readonly follow?: Endpoint4_24Request["query"]["follow"] readonly follow?: Endpoint4_23Request["query"]["follow"]
} }
const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) => const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) =>
Stream.unwrap( Stream.unwrap(
raw["session.log"]({ raw["session.log"]({
params: { sessionID: input["sessionID"] }, params: { sessionID: input["sessionID"] },
@@ -342,22 +324,22 @@ const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24I
), ),
) )
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0] type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] } type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) => const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.background"]>[0] type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.background"]>[0]
type Endpoint4_26Input = { readonly sessionID: Endpoint4_26Request["params"]["sessionID"] } type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] }
const Endpoint4_26 = (raw: RawClient["server.session"]) => (input: Endpoint4_26Input) => const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) =>
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_27Request = Parameters<RawClient["server.session"]["session.message"]>[0] type Endpoint4_26Request = Parameters<RawClient["server.session"]["session.message"]>[0]
type Endpoint4_27Input = { type Endpoint4_26Input = {
readonly sessionID: Endpoint4_27Request["params"]["sessionID"] readonly sessionID: Endpoint4_26Request["params"]["sessionID"]
readonly messageID: Endpoint4_27Request["params"]["messageID"] readonly messageID: Endpoint4_26Request["params"]["messageID"]
} }
const Endpoint4_27 = (raw: RawClient["server.session"]) => (input: Endpoint4_27Input) => const Endpoint4_26 = (raw: RawClient["server.session"]) => (input: Endpoint4_26Input) =>
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe( raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
Effect.mapError(mapClientError), Effect.mapError(mapClientError),
Effect.map((value) => value.data), Effect.map((value) => value.data),
@@ -373,21 +355,22 @@ const adaptGroup4 = (raw: RawClient["server.session"]) => ({
switchAgent: Endpoint4_6(raw), switchAgent: Endpoint4_6(raw),
switchModel: Endpoint4_7(raw), switchModel: Endpoint4_7(raw),
rename: Endpoint4_8(raw), rename: Endpoint4_8(raw),
move: Endpoint4_9(raw), prompt: Endpoint4_9(raw),
prompt: Endpoint4_10(raw), command: Endpoint4_10(raw),
command: Endpoint4_11(raw), skill: Endpoint4_11(raw),
skill: Endpoint4_12(raw), synthetic: Endpoint4_12(raw),
synthetic: Endpoint4_13(raw), shell: Endpoint4_13(raw),
shell: Endpoint4_14(raw), compact: Endpoint4_14(raw),
compact: Endpoint4_15(raw), wait: Endpoint4_15(raw),
wait: Endpoint4_16(raw), revertStage: Endpoint4_16(raw),
revert: { stage: Endpoint4_17(raw), clear: Endpoint4_18(raw), commit: Endpoint4_19(raw) }, revertClear: Endpoint4_17(raw),
context: Endpoint4_20(raw), revertCommit: Endpoint4_18(raw),
instructions: { entry: { list: Endpoint4_21(raw), put: Endpoint4_22(raw), remove: Endpoint4_23(raw) } }, context: Endpoint4_19(raw),
log: Endpoint4_24(raw), instructions: { entry: { list: Endpoint4_20(raw), put: Endpoint4_21(raw), remove: Endpoint4_22(raw) } },
interrupt: Endpoint4_25(raw), log: Endpoint4_23(raw),
background: Endpoint4_26(raw), interrupt: Endpoint4_24(raw),
message: Endpoint4_27(raw), background: Endpoint4_25(raw),
message: Endpoint4_26(raw),
}) })
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0] type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
@@ -534,8 +517,11 @@ const Endpoint9_6 = (raw: RawClient["server.integration"]) => (input: Endpoint9_
const adaptGroup9 = (raw: RawClient["server.integration"]) => ({ const adaptGroup9 = (raw: RawClient["server.integration"]) => ({
list: Endpoint9_0(raw), list: Endpoint9_0(raw),
get: Endpoint9_1(raw), get: Endpoint9_1(raw),
connect: { key: Endpoint9_2(raw), oauth: Endpoint9_3(raw) }, connectKey: Endpoint9_2(raw),
attempt: { status: Endpoint9_4(raw), complete: Endpoint9_5(raw), cancel: Endpoint9_6(raw) }, connectOauth: Endpoint9_3(raw),
attemptStatus: Endpoint9_4(raw),
attemptComplete: Endpoint9_5(raw),
attemptCancel: Endpoint9_6(raw),
}) })
type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0] type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
@@ -543,15 +529,7 @@ type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query"]["loc
const Endpoint10_0 = (raw: RawClient["server.mcp"]) => (input?: Endpoint10_0Input) => const Endpoint10_0 = (raw: RawClient["server.mcp"]) => (input?: Endpoint10_0Input) =>
raw["mcp.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) raw["mcp.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint10_1Request = Parameters<RawClient["server.mcp"]["mcp.resource.catalog"]>[0] const adaptGroup10 = (raw: RawClient["server.mcp"]) => ({ list: Endpoint10_0(raw) })
type Endpoint10_1Input = { readonly location?: Endpoint10_1Request["query"]["location"] }
const Endpoint10_1 = (raw: RawClient["server.mcp"]) => (input?: Endpoint10_1Input) =>
raw["mcp.resource.catalog"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup10 = (raw: RawClient["server.mcp"]) => ({
list: Endpoint10_0(raw),
resource: { catalog: Endpoint10_1(raw) },
})
type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0] type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
type Endpoint11_0Input = { type Endpoint11_0Input = {
@@ -688,7 +666,7 @@ const Endpoint13_6 = (raw: RawClient["server.form"]) => (input: Endpoint13_6Inpu
) )
const adaptGroup13 = (raw: RawClient["server.form"]) => ({ const adaptGroup13 = (raw: RawClient["server.form"]) => ({
request: { list: Endpoint13_0(raw) }, listRequests: Endpoint13_0(raw),
list: Endpoint13_1(raw), list: Endpoint13_1(raw),
create: Endpoint13_2(raw), create: Endpoint13_2(raw),
get: Endpoint13_3(raw), get: Endpoint13_3(raw),
@@ -776,8 +754,9 @@ const Endpoint14_6 = (raw: RawClient["server.permission"]) => (input: Endpoint14
}).pipe(Effect.mapError(mapClientError)) }).pipe(Effect.mapError(mapClientError))
const adaptGroup14 = (raw: RawClient["server.permission"]) => ({ const adaptGroup14 = (raw: RawClient["server.permission"]) => ({
request: { list: Endpoint14_0(raw) }, listRequests: Endpoint14_0(raw),
saved: { list: Endpoint14_1(raw), remove: Endpoint14_2(raw) }, listSaved: Endpoint14_1(raw),
removeSaved: Endpoint14_2(raw),
create: Endpoint14_3(raw), create: Endpoint14_3(raw),
list: Endpoint14_4(raw), list: Endpoint14_4(raw),
get: Endpoint14_5(raw), get: Endpoint14_5(raw),
@@ -1010,7 +989,7 @@ const Endpoint21_3 = (raw: RawClient["server.question"]) => (input: Endpoint21_3
) )
const adaptGroup21 = (raw: RawClient["server.question"]) => ({ const adaptGroup21 = (raw: RawClient["server.question"]) => ({
request: { list: Endpoint21_0(raw) }, listRequests: Endpoint21_0(raw),
list: Endpoint21_1(raw), list: Endpoint21_1(raw),
reply: Endpoint21_2(raw), reply: Endpoint21_2(raw),
reject: Endpoint21_3(raw), reject: Endpoint21_3(raw),
@@ -1090,14 +1069,7 @@ const adaptGroup24 = (raw: RawClient["server.vcs"]) => ({ status: Endpoint24_0(r
const Endpoint25_0 = (raw: RawClient["server.debug"]) => () => const Endpoint25_0 = (raw: RawClient["server.debug"]) => () =>
raw["debug.location"]({}).pipe(Effect.mapError(mapClientError)) raw["debug.location"]({}).pipe(Effect.mapError(mapClientError))
type Endpoint25_1Request = Parameters<RawClient["server.debug"]["debug.location.evict"]>[0] const adaptGroup25 = (raw: RawClient["server.debug"]) => ({ location: Endpoint25_0(raw) })
type Endpoint25_1Input = { readonly location?: Endpoint25_1Request["query"]["location"] }
const Endpoint25_1 = (raw: RawClient["server.debug"]) => (input?: Endpoint25_1Input) =>
raw["debug.location.evict"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup25 = (raw: RawClient["server.debug"]) => ({
location: { list: Endpoint25_0(raw), evict: Endpoint25_1(raw) },
})
const adaptClient = (raw: RawClient) => ({ const adaptClient = (raw: RawClient) => ({
health: adaptGroup0(raw["server.health"]), health: adaptGroup0(raw["server.health"]),
+179 -244
View File
@@ -23,8 +23,6 @@ import type {
SessionSwitchModelOutput, SessionSwitchModelOutput,
SessionRenameInput, SessionRenameInput,
SessionRenameOutput, SessionRenameOutput,
SessionMoveInput,
SessionMoveOutput,
SessionPromptInput, SessionPromptInput,
SessionPromptOutput, SessionPromptOutput,
SessionCommandInput, SessionCommandInput,
@@ -89,8 +87,6 @@ import type {
IntegrationAttemptCancelOutput, IntegrationAttemptCancelOutput,
ServerMcpListInput, ServerMcpListInput,
ServerMcpListOutput, ServerMcpListOutput,
ServerMcpResourceCatalogInput,
ServerMcpResourceCatalogOutput,
CredentialUpdateInput, CredentialUpdateInput,
CredentialUpdateOutput, CredentialUpdateOutput,
CredentialRemoveInput, CredentialRemoveInput,
@@ -100,8 +96,8 @@ import type {
ProjectCurrentOutput, ProjectCurrentOutput,
ProjectDirectoriesInput, ProjectDirectoriesInput,
ProjectDirectoriesOutput, ProjectDirectoriesOutput,
FormRequestListInput, FormListRequestsInput,
FormRequestListOutput, FormListRequestsOutput,
FormListInput, FormListInput,
FormListOutput, FormListOutput,
FormCreateInput, FormCreateInput,
@@ -114,12 +110,12 @@ import type {
FormReplyOutput, FormReplyOutput,
FormCancelInput, FormCancelInput,
FormCancelOutput, FormCancelOutput,
PermissionRequestListInput, PermissionListRequestsInput,
PermissionRequestListOutput, PermissionListRequestsOutput,
PermissionSavedListInput, PermissionListSavedInput,
PermissionSavedListOutput, PermissionListSavedOutput,
PermissionSavedRemoveInput, PermissionRemoveSavedInput,
PermissionSavedRemoveOutput, PermissionRemoveSavedOutput,
PermissionCreateInput, PermissionCreateInput,
PermissionCreateOutput, PermissionCreateOutput,
PermissionListInput, PermissionListInput,
@@ -161,8 +157,8 @@ import type {
ShellOutputOutput, ShellOutputOutput,
ShellRemoveInput, ShellRemoveInput,
ShellRemoveOutput, ShellRemoveOutput,
QuestionRequestListInput, QuestionListRequestsInput,
QuestionRequestListOutput, QuestionListRequestsOutput,
QuestionListInput, QuestionListInput,
QuestionListOutput, QuestionListOutput,
QuestionReplyInput, QuestionReplyInput,
@@ -181,9 +177,7 @@ import type {
VcsStatusOutput, VcsStatusOutput,
VcsDiffInput, VcsDiffInput,
VcsDiffOutput, VcsDiffOutput,
DebugLocationListOutput, DebugLocationOutput,
DebugLocationEvictInput,
DebugLocationEvictOutput,
} from "./types" } from "./types"
import { ClientError } from "./client-error" import { ClientError } from "./client-error"
@@ -491,18 +485,6 @@ export function make(options: ClientOptions) {
}, },
requestOptions, requestOptions,
), ),
move: (input: SessionMoveInput, requestOptions?: RequestOptions) =>
request<SessionMoveOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/move`,
body: { destination: input["destination"], moveChanges: input["moveChanges"] },
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
prompt: (input: SessionPromptInput, requestOptions?: RequestOptions) => prompt: (input: SessionPromptInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionPromptOutput }>( request<{ readonly data: SessionPromptOutput }>(
{ {
@@ -554,12 +536,7 @@ export function make(options: ClientOptions) {
{ {
method: "POST", method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/synthetic`, path: `/api/session/${encodeURIComponent(input.sessionID)}/synthetic`,
body: { body: { text: input["text"], description: input["description"], metadata: input["metadata"] },
text: input["text"],
description: input["description"],
metadata: input["metadata"],
resume: input["resume"],
},
successStatus: 204, successStatus: 204,
declaredStatuses: [404, 400, 401], declaredStatuses: [404, 400, 401],
empty: true, empty: true,
@@ -601,42 +578,40 @@ export function make(options: ClientOptions) {
}, },
requestOptions, requestOptions,
), ),
revert: { revertStage: (input: SessionRevertStageInput, requestOptions?: RequestOptions) =>
stage: (input: SessionRevertStageInput, requestOptions?: RequestOptions) => request<{ readonly data: SessionRevertStageOutput }>(
request<{ readonly data: SessionRevertStageOutput }>( {
{ method: "POST",
method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`,
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`, body: { messageID: input["messageID"], files: input["files"] },
body: { messageID: input["messageID"], files: input["files"] }, successStatus: 200,
successStatus: 200, declaredStatuses: [404, 409, 500, 400, 401],
declaredStatuses: [404, 409, 500, 400, 401], empty: false,
empty: false, },
}, requestOptions,
requestOptions, ).then((value) => value.data),
).then((value) => value.data), revertClear: (input: SessionRevertClearInput, requestOptions?: RequestOptions) =>
clear: (input: SessionRevertClearInput, requestOptions?: RequestOptions) => request<SessionRevertClearOutput>(
request<SessionRevertClearOutput>( {
{ method: "POST",
method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`,
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`, successStatus: 204,
successStatus: 204, declaredStatuses: [404, 409, 500, 400, 401],
declaredStatuses: [404, 409, 500, 400, 401], empty: true,
empty: true, },
}, requestOptions,
requestOptions, ),
), revertCommit: (input: SessionRevertCommitInput, requestOptions?: RequestOptions) =>
commit: (input: SessionRevertCommitInput, requestOptions?: RequestOptions) => request<SessionRevertCommitOutput>(
request<SessionRevertCommitOutput>( {
{ method: "POST",
method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`,
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`, successStatus: 204,
successStatus: 204, declaredStatuses: [404, 409, 400, 401],
declaredStatuses: [404, 409, 400, 401], empty: true,
empty: true, },
}, requestOptions,
requestOptions, ),
),
},
context: (input: SessionContextInput, requestOptions?: RequestOptions) => context: (input: SessionContextInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionContextOutput }>( request<{ readonly data: SessionContextOutput }>(
{ {
@@ -838,73 +813,69 @@ export function make(options: ClientOptions) {
}, },
requestOptions, requestOptions,
), ),
connect: { connectKey: (input: IntegrationConnectKeyInput, requestOptions?: RequestOptions) =>
key: (input: IntegrationConnectKeyInput, requestOptions?: RequestOptions) => request<IntegrationConnectKeyOutput>(
request<IntegrationConnectKeyOutput>( {
{ method: "POST",
method: "POST", path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`,
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`, query: { location: input["location"] },
query: { location: input["location"] }, body: { key: input["key"], label: input["label"] },
body: { key: input["key"], label: input["label"] }, successStatus: 204,
successStatus: 204, declaredStatuses: [400, 401],
declaredStatuses: [400, 401], empty: true,
empty: true, },
}, requestOptions,
requestOptions, ),
), connectOauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) =>
oauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) => request<IntegrationConnectOauthOutput>(
request<IntegrationConnectOauthOutput>( {
{ method: "POST",
method: "POST", path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`,
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`, query: { location: input["location"] },
query: { location: input["location"] }, body: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
body: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] }, successStatus: 200,
successStatus: 200, declaredStatuses: [400, 401],
declaredStatuses: [400, 401], empty: false,
empty: false, },
}, requestOptions,
requestOptions, ),
), attemptStatus: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) =>
}, request<IntegrationAttemptStatusOutput>(
attempt: { {
status: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) => method: "GET",
request<IntegrationAttemptStatusOutput>( path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
{ query: { location: input["location"] },
method: "GET", successStatus: 200,
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, declaredStatuses: [401, 400],
query: { location: input["location"] }, empty: false,
successStatus: 200, },
declaredStatuses: [401, 400], requestOptions,
empty: false, ),
}, attemptComplete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) =>
requestOptions, request<IntegrationAttemptCompleteOutput>(
), {
complete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) => method: "POST",
request<IntegrationAttemptCompleteOutput>( path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`,
{ query: { location: input["location"] },
method: "POST", body: { code: input["code"] },
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`, successStatus: 204,
query: { location: input["location"] }, declaredStatuses: [400, 401],
body: { code: input["code"] }, empty: true,
successStatus: 204, },
declaredStatuses: [400, 401], requestOptions,
empty: true, ),
}, attemptCancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) =>
requestOptions, request<IntegrationAttemptCancelOutput>(
), {
cancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) => method: "DELETE",
request<IntegrationAttemptCancelOutput>( path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
{ query: { location: input["location"] },
method: "DELETE", successStatus: 204,
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, declaredStatuses: [401, 400],
query: { location: input["location"] }, empty: true,
successStatus: 204, },
declaredStatuses: [401, 400], requestOptions,
empty: true, ),
},
requestOptions,
),
},
}, },
"server.mcp": { "server.mcp": {
list: (input?: ServerMcpListInput, requestOptions?: RequestOptions) => list: (input?: ServerMcpListInput, requestOptions?: RequestOptions) =>
@@ -919,20 +890,6 @@ export function make(options: ClientOptions) {
}, },
requestOptions, requestOptions,
), ),
resource: {
catalog: (input?: ServerMcpResourceCatalogInput, requestOptions?: RequestOptions) =>
request<ServerMcpResourceCatalogOutput>(
{
method: "GET",
path: `/api/mcp/resource`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
},
}, },
credential: { credential: {
update: (input: CredentialUpdateInput, requestOptions?: RequestOptions) => update: (input: CredentialUpdateInput, requestOptions?: RequestOptions) =>
@@ -993,20 +950,18 @@ export function make(options: ClientOptions) {
), ),
}, },
form: { form: {
request: { listRequests: (input?: FormListRequestsInput, requestOptions?: RequestOptions) =>
list: (input?: FormRequestListInput, requestOptions?: RequestOptions) => request<FormListRequestsOutput>(
request<FormRequestListOutput>( {
{ method: "GET",
method: "GET", path: `/api/form/request`,
path: `/api/form/request`, query: { location: input?.["location"] },
query: { location: input?.["location"] }, successStatus: 200,
successStatus: 200, declaredStatuses: [401, 400],
declaredStatuses: [401, 400], empty: false,
empty: false, },
}, requestOptions,
requestOptions, ),
),
},
list: (input: FormListInput, requestOptions?: RequestOptions) => list: (input: FormListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: FormListOutput }>( request<{ readonly data: FormListOutput }>(
{ {
@@ -1084,45 +1039,41 @@ export function make(options: ClientOptions) {
), ),
}, },
permission: { permission: {
request: { listRequests: (input?: PermissionListRequestsInput, requestOptions?: RequestOptions) =>
list: (input?: PermissionRequestListInput, requestOptions?: RequestOptions) => request<PermissionListRequestsOutput>(
request<PermissionRequestListOutput>( {
{ method: "GET",
method: "GET", path: `/api/permission/request`,
path: `/api/permission/request`, query: { location: input?.["location"] },
query: { location: input?.["location"] }, successStatus: 200,
successStatus: 200, declaredStatuses: [401, 400],
declaredStatuses: [401, 400], empty: false,
empty: false, },
}, requestOptions,
requestOptions, ),
), listSaved: (input?: PermissionListSavedInput, requestOptions?: RequestOptions) =>
}, request<{ readonly data: PermissionListSavedOutput }>(
saved: { {
list: (input?: PermissionSavedListInput, requestOptions?: RequestOptions) => method: "GET",
request<{ readonly data: PermissionSavedListOutput }>( path: `/api/permission/saved`,
{ query: { projectID: input?.["projectID"] },
method: "GET", successStatus: 200,
path: `/api/permission/saved`, declaredStatuses: [401, 400],
query: { projectID: input?.["projectID"] }, empty: false,
successStatus: 200, },
declaredStatuses: [401, 400], requestOptions,
empty: false, ).then((value) => value.data),
}, removeSaved: (input: PermissionRemoveSavedInput, requestOptions?: RequestOptions) =>
requestOptions, request<PermissionRemoveSavedOutput>(
).then((value) => value.data), {
remove: (input: PermissionSavedRemoveInput, requestOptions?: RequestOptions) => method: "DELETE",
request<PermissionSavedRemoveOutput>( path: `/api/permission/saved/${encodeURIComponent(input.id)}`,
{ successStatus: 204,
method: "DELETE", declaredStatuses: [401, 400],
path: `/api/permission/saved/${encodeURIComponent(input.id)}`, empty: true,
successStatus: 204, },
declaredStatuses: [401, 400], requestOptions,
empty: true, ),
},
requestOptions,
),
},
create: (input: PermissionCreateInput, requestOptions?: RequestOptions) => create: (input: PermissionCreateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionCreateOutput }>( request<{ readonly data: PermissionCreateOutput }>(
{ {
@@ -1404,20 +1355,18 @@ export function make(options: ClientOptions) {
), ),
}, },
question: { question: {
request: { listRequests: (input?: QuestionListRequestsInput, requestOptions?: RequestOptions) =>
list: (input?: QuestionRequestListInput, requestOptions?: RequestOptions) => request<QuestionListRequestsOutput>(
request<QuestionRequestListOutput>( {
{ method: "GET",
method: "GET", path: `/api/question/request`,
path: `/api/question/request`, query: { location: input?.["location"] },
query: { location: input?.["location"] }, successStatus: 200,
successStatus: 200, declaredStatuses: [401, 400],
declaredStatuses: [401, 400], empty: false,
empty: false, },
}, requestOptions,
requestOptions, ),
),
},
list: (input: QuestionListInput, requestOptions?: RequestOptions) => list: (input: QuestionListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: QuestionListOutput }>( request<{ readonly data: QuestionListOutput }>(
{ {
@@ -1534,31 +1483,17 @@ export function make(options: ClientOptions) {
), ),
}, },
debug: { debug: {
location: { location: (requestOptions?: RequestOptions) =>
list: (requestOptions?: RequestOptions) => request<DebugLocationOutput>(
request<DebugLocationListOutput>( {
{ method: "GET",
method: "GET", path: `/api/debug/location`,
path: `/api/debug/location`, successStatus: 200,
successStatus: 200, declaredStatuses: [401, 400],
declaredStatuses: [401, 400], empty: false,
empty: false, },
}, requestOptions,
requestOptions, ),
),
evict: (input?: DebugLocationEvictInput, requestOptions?: RequestOptions) =>
request<DebugLocationEvictOutput>(
{
method: "DELETE",
path: `/api/debug/location`,
query: { location: input?.["location"] },
successStatus: 204,
declaredStatuses: [401, 400],
empty: true,
},
requestOptions,
),
},
}, },
} }
} }
File diff suppressed because it is too large Load Diff
@@ -19,7 +19,7 @@ import { SessionMessage } from "@opencode-ai/schema/session-message"
import { Workspace } from "@opencode-ai/schema/workspace" import { Workspace } from "@opencode-ai/schema/workspace"
import { Api } from "@opencode-ai/server/api" import { Api } from "@opencode-ai/server/api"
import { compile, emitPromise } from "@opencode-ai/httpapi-codegen" import { compile, emitPromise } from "@opencode-ai/httpapi-codegen"
import { ClientApi, groupNames, promiseOmitEndpoints } from "../src/contract" import { ClientApi, endpointNames, groupNames, promiseOmitEndpoints } from "../src/contract"
const Client = await import("../src/effect") const Client = await import("../src/effect")
@@ -38,7 +38,7 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
expect(ProjectV2.Directory).toBe(Project.Directory) expect(ProjectV2.Directory).toBe(Project.Directory)
expect(ProjectV2.Directories).toBe(Project.Directories) expect(ProjectV2.Directories).toBe(Project.Directories)
expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted) expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted)
expect(CoreSessionMessage.Info).toBe(SessionMessage.Info) expect(CoreSessionMessage.Message).toBe(SessionMessage.Message)
expect(Api.groups["server.session"].identifier).toBe("server.session") expect(Api.groups["server.session"].identifier).toBe("server.session")
expect(Api.groups["server.project"].identifier).toBe("server.project") expect(Api.groups["server.project"].identifier).toBe("server.project")
expect(Object.keys(ClientApi.groups)).toEqual(Object.keys(Api.groups)) expect(Object.keys(ClientApi.groups)).toEqual(Object.keys(Api.groups))
@@ -49,8 +49,8 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
}) })
test("client and Server contracts generate identically", () => { test("client and Server contracts generate identically", () => {
const server = compile(Api, { groupNames, omitEndpoints: promiseOmitEndpoints }) const server = compile(Api, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
const client = compile(ClientApi, { groupNames, omitEndpoints: promiseOmitEndpoints }) const client = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
expect(emitPromise(client)).toEqual(emitPromise(server)) expect(emitPromise(client)).toEqual(emitPromise(server))
}) })
+9 -27
View File
@@ -33,11 +33,16 @@ test("exposes every standard HTTP API group", () => {
"debug", "debug",
]) ])
expect(Object.keys(client.debug)).toEqual(["location"]) expect(Object.keys(client.debug)).toEqual(["location"])
expect(Object.keys(client.debug.location)).toEqual(["list", "evict"])
expect(Object.keys(client.message)).toEqual(["list"]) expect(Object.keys(client.message)).toEqual(["list"])
expect(Object.keys(client.integration)).toEqual(["list", "get", "connect", "attempt"]) expect(Object.keys(client.integration)).toEqual([
expect(Object.keys(client.integration.connect)).toEqual(["key", "oauth"]) "list",
expect(Object.keys(client.integration.attempt)).toEqual(["status", "complete", "cancel"]) "get",
"connectKey",
"connectOauth",
"attemptStatus",
"attemptComplete",
"attemptCancel",
])
expect(Object.keys(client.file)).toEqual(["read", "list", "find"]) expect(Object.keys(client.file)).toEqual(["read", "list", "find"])
expect(Object.keys(client.vcs)).toEqual(["status", "diff"]) expect(Object.keys(client.vcs)).toEqual(["status", "diff"])
expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"]) expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"])
@@ -45,29 +50,6 @@ test("exposes every standard HTTP API group", () => {
expect(Object.keys(client.project)).toEqual(["list", "current", "directories"]) expect(Object.keys(client.project)).toEqual(["list", "current", "directories"])
}) })
test("MCP resource catalog uses the public HTTP contract", async () => {
let request: Request | undefined
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input) => {
request = input instanceof Request ? input : new Request(input)
return Response.json({
location: { directory: "/tmp/project", project: { id: "proj_test", directory: "/tmp/project" } },
data: {
resources: [{ server: "docs", name: "Readme", uri: "docs://readme" }],
templates: [{ server: "docs", name: "File", uriTemplate: "docs://{path}" }],
},
})
},
})
const result = await client["server.mcp"].resource.catalog({ location: { directory: "/tmp/project" } })
expect(result.data.resources[0]?.uri).toBe("docs://readme")
expect(request?.method).toBe("GET")
expect(request?.url).toBe("http://localhost:3000/api/mcp/resource?location%5Bdirectory%5D=%2Ftmp%2Fproject")
})
test("file.read returns binary content from the public HTTP contract", async () => { test("file.read returns binary content from the public HTTP contract", async () => {
let request: Request | undefined let request: Request | undefined
const client = OpenCode.make({ const client = OpenCode.make({
+2 -2
View File
@@ -62,7 +62,7 @@ const result =
`result` is always a `CodeMode.Result`. Program, validation, limit, and tool failures are returned as diagnostics rather than failing the Effect. Host interruption remains interruption. `result` is always a `CodeMode.Result`. Program, validation, limit, and tool failures are returned as diagnostics rather than failing the Effect. Host interruption remains interruption.
Successful result values are JSON-safe data. An explicit `return` produces the program result; when it is omitted, the final executable top-level expression is returned as a model-friendly REPL convenience. Otherwise reaching the end produces `null`. Returned `undefined` and nested `undefined` values are normalized to `null` as well. Successful result values are JSON-safe data. A program that returns `undefined`, including by reaching the end without `return`, produces `null`; nested `undefined` values are normalized to `null` as well.
## API ## API
@@ -241,7 +241,7 @@ CodeMode executes a deliberately bounded JavaScript subset. It supports:
- `if`, conditional expressions, `switch`, `for`, `for...of` (arrays, strings, Maps, Sets, including assignment-form destructuring such as `for ([key, value] of entries)`), `for...in` (own keys of plain objects, index strings of arrays, and namespace/tool names of `tools` references - anything else is an error suggesting `for...of` or `Object.keys`, rather than real JS's surprising behavior of indices for strings and zero iterations for Maps/Sets), `while`, and `do...while`. - `if`, conditional expressions, `switch`, `for`, `for...of` (arrays, strings, Maps, Sets, including assignment-form destructuring such as `for ([key, value] of entries)`), `for...in` (own keys of plain objects, index strings of arrays, and namespace/tool names of `tools` references - anything else is an error suggesting `for...of` or `Object.keys`, rather than real JS's surprising behavior of indices for strings and zero iterations for Maps/Sets), `while`, and `do...while`.
- Arrow functions and function declarations with closures, defaults, rest parameters, and destructuring. - Arrow functions and function declarations with closures, defaults, rest parameters, and destructuring.
- Optional chaining, nullish coalescing, templates, spread (arrays, strings, Maps, Sets), and `try`/`catch`. - Optional chaining, nullish coalescing, templates, spread (arrays, strings, Maps, Sets), and `try`/`catch`.
- Common array, string, number, `Object`, `Math`, and `JSON` operations, including primitive-number `valueOf`, the standard non-finite `Number` constants, and host-backed `Math.random`. Mutating array methods include `push`/`pop`/`shift`/`unshift`/`splice` (removes in place and returns the removed elements)/`fill`/`copyWithin`; array `keys`/`values`/`entries` return **arrays** (matching the Map/Set convention) and work with `for...of` and spread. String methods include `localeCompare` (locale/options arguments ignored), `normalize`, and the `trimLeft`/`trimRight` aliases. `Object.keys` also accepts arrays (index strings, as in JS) and tool references: `Object.keys(tools)` lists the top-level namespaces, including `$codemode`, and `Object.keys(tools.ns)` lists the names at that node (a callable tool enumerates as `[]`; an unknown path is an `UnknownTool` diagnostic). `Object.values`/`Object.entries` on a tool reference fail with a pointer at `Object.keys(tools)` and `tools.$codemode.search`. - Common array, string, number, `Object`, `Math`, and `JSON` operations. Mutating array methods include `push`/`pop`/`shift`/`unshift`/`splice` (removes in place and returns the removed elements)/`fill`/`copyWithin`; array `keys`/`values`/`entries` return **arrays** (matching the Map/Set convention) and work with `for...of` and spread. String methods include `localeCompare` (locale/options arguments ignored), `normalize`, and the `trimLeft`/`trimRight` aliases. `Object.keys` also accepts arrays (index strings, as in JS) and tool references: `Object.keys(tools)` lists the top-level namespaces, including `$codemode`, and `Object.keys(tools.ns)` lists the names at that node (a callable tool enumerates as `[]`; an unknown path is an `UnknownTool` diagnostic). `Object.values`/`Object.entries` on a tool reference fail with a pointer at `Object.keys(tools)` and `tools.$codemode.search`.
- `Date` - `Date.now()`/`Date.parse()`/`Date.UTC()`, `new Date(...)`, the getter methods, and date arithmetic/comparison via the time value. Dates stringify as ISO (`toString` included, for determinism across host timezones). - `Date` - `Date.now()`/`Date.parse()`/`Date.UTC()`, `new Date(...)`, the getter methods, and date arithmetic/comparison via the time value. Dates stringify as ISO (`toString` included, for determinism across host timezones).
- Regular expressions - `/literals/` and `new RegExp(...)` with `test`/`exec` (stateful `lastIndex` for `g`), plus string `match`/`matchAll`/`replace`/`replaceAll`/`split`/`search` with patterns. Match results are arrays carrying `index` and named `groups` as own properties (`input` is omitted). `replace` and `replaceAll` accept function replacers with captures, offset, input, and named groups; callbacks run sequentially, may await tool calls, and have their results coerced to strings. Invalid patterns, invalid flags, and missing-`g` calls fail with catchable errors that say what was wrong and how to fix it (escaping hints, the exact `/pattern/g` to write). Patterns run on the host engine, so pathological backtracking is bounded only by the execution timeout. - Regular expressions - `/literals/` and `new RegExp(...)` with `test`/`exec` (stateful `lastIndex` for `g`), plus string `match`/`matchAll`/`replace`/`replaceAll`/`split`/`search` with patterns. Match results are arrays carrying `index` and named `groups` as own properties (`input` is omitted). `replace` and `replaceAll` accept function replacers with captures, offset, input, and named groups; callbacks run sequentially, may await tool calls, and have their results coerced to strings. Invalid patterns, invalid flags, and missing-`g` calls fail with catchable errors that say what was wrong and how to fix it (escaping hints, the exact `/pattern/g` to write). Patterns run on the host engine, so pathological backtracking is bounded only by the execution timeout.
- `Map` and `Set` - construction from entries/arrays/strings, `get`/`set`/`add`/`has`/`delete`/`clear`/`size`/`forEach`, and `keys`/`values`/`entries` returning **arrays** (not iterators). - `Map` and `Set` - construction from entries/arrays/strings, `get`/`set`/`add`/`has`/`delete`/`clear`/`size`/`forEach`, and `keys`/`values`/`entries` returning **arrays** (not iterators).
+1 -1
View File
@@ -158,7 +158,7 @@ current omissions to implement, not intentional product boundaries.
- [ ] Add `Object.is` after runtime method and tool references have stable identity semantics. - [ ] Add `Object.is` after runtime method and tool references have stable identity semantics.
- [ ] Add deterministic modern collection conveniences where they improve orchestration: `Object.groupBy`, Set - [ ] Add deterministic modern collection conveniences where they improve orchestration: `Object.groupBy`, Set
composition methods, and `Array.prototype.toSpliced`. composition methods, and `Array.prototype.toSpliced`.
- [ ] Decide whether iterable `Math.sumPrecise` belongs in the runtime. - [ ] Decide whether nondeterministic `Math.random` and iterable `Math.sumPrecise` belong in the runtime.
- [ ] Refine diagnostics so user throws, expected tool failures, unexpected host/tool defects, and genuine interpreter - [ ] Refine diagnostics so user throws, expected tool failures, unexpected host/tool defects, and genuine interpreter
defects are distinguishable without leaking private causes. defects are distinguishable without leaking private causes.
@@ -30,6 +30,7 @@ export type Binding = {
export type StatementResult = export type StatementResult =
| { kind: "none" } | { kind: "none" }
| { kind: "value"; value: unknown }
| { kind: "return"; value: unknown } | { kind: "return"; value: unknown }
| { kind: "break" } | { kind: "break" }
| { kind: "continue" } | { kind: "continue" }
+37 -24
View File
@@ -530,29 +530,17 @@ const invokeArrayStatic = (name: string, args: Array<unknown>, node: AstNode): u
if (args[0] instanceof SandboxURLSearchParams) { if (args[0] instanceof SandboxURLSearchParams) {
return Array.from(args[0].params.entries(), ([key, value]) => [key, value]) return Array.from(args[0].params.entries(), ([key, value]) => [key, value])
} }
const source = args[0] const source = boundedData(args[0], "Array.from input")
if (source instanceof SandboxPromise) {
throw new InterpreterRuntimeError(
"Array.from received an un-awaited Promise; await it before creating the array.",
node,
"InvalidDataValue",
)
}
if (typeof source === "string") return Array.from(source) if (typeof source === "string") return Array.from(source)
if (Array.isArray(source)) return [...source] if (Array.isArray(source)) return [...source]
if ( if (
source !== null && source !== null &&
typeof source === "object" && typeof source === "object" &&
(Object.getPrototypeOf(source) === Object.prototype || Object.getPrototypeOf(source) === null) &&
typeof (source as { length?: unknown }).length === "number" typeof (source as { length?: unknown }).length === "number"
) { ) {
return Array.from(source as ArrayLike<unknown>) return Array.from(source as ArrayLike<unknown>)
} }
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError("Array.from expects an array, string, Map, Set, or array-like value.", node)
"Array.from expects an array, string, Map, Set, or array-like value.",
node,
"InvalidDataValue",
)
} }
default: default:
throw new InterpreterRuntimeError(`Array.${name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Array.${name} is not available in CodeMode.`, node)
@@ -618,6 +606,7 @@ class Interpreter<R> {
// ToolRuntime.make like invokeTool: the interpreter never holds the tree itself. // ToolRuntime.make like invokeTool: the interpreter never holds the tree itself.
private readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string> private readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>
private readonly logs: Array<string> private readonly logs: Array<string>
private lastValue: unknown
// Caps how many eagerly forked tool calls run at once (the parallel-call concurrency cap). // Caps how many eagerly forked tool calls run at once (the parallel-call concurrency cap).
private readonly callPermits: Semaphore.Semaphore private readonly callPermits: Semaphore.Semaphore
// Fiber-backed promises whose settlement no program construct has observed yet. Successful // Fiber-backed promises whose settlement no program construct has observed yet. Successful
@@ -636,6 +625,7 @@ class Interpreter<R> {
this.invokeTool = invokeTool this.invokeTool = invokeTool
this.toolKeys = toolKeys this.toolKeys = toolKeys
this.logs = logs this.logs = logs
this.lastValue = undefined
this.callPermits = shared?.callPermits ?? Semaphore.makeUnsafe(TOOL_CALL_CONCURRENCY) this.callPermits = shared?.callPermits ?? Semaphore.makeUnsafe(TOOL_CALL_CONCURRENCY)
this.pendingSettlements = shared?.pendingSettlements ?? new Set<SandboxPromise>() this.pendingSettlements = shared?.pendingSettlements ?? new Set<SandboxPromise>()
globalScope.set("tools", { mutable: false, value: new ToolReference([]) }) globalScope.set("tools", { mutable: false, value: new ToolReference([]) })
@@ -681,15 +671,13 @@ class Interpreter<R> {
return Effect.gen(function* () { return Effect.gen(function* () {
self.hoistFunctions(program.body) self.hoistFunctions(program.body)
let value: unknown = undefined let value: unknown = undefined
for (const [index, statement] of program.body.entries()) { let returned = false
if (index === program.body.length - 1 && statement.type === "ExpressionStatement") { for (const statement of program.body) {
value = yield* self.evaluateExpression(getNode(statement, "expression"))
break
}
const result = yield* self.evaluateStatement(statement) const result = yield* self.evaluateStatement(statement)
if (result.kind === "return") { if (result.kind === "return") {
value = result.value value = result.value
returned = true
break break
} }
@@ -697,7 +685,11 @@ class Interpreter<R> {
throw new InterpreterRuntimeError(`Unexpected '${result.kind}' outside of a loop.`, statement) throw new InterpreterRuntimeError(`Unexpected '${result.kind}' outside of a loop.`, statement)
} }
if (result.kind === "value") {
self.lastValue = result.value
}
} }
if (!returned) value = self.lastValue
// The program body runs inside an implicit async function, so a returned promise // The program body runs inside an implicit async function, so a returned promise
// resolves before crossing the data boundary - `return tools.ns.tool(...)` works // resolves before crossing the data boundary - `return tools.ns.tool(...)` works
@@ -788,7 +780,7 @@ class Interpreter<R> {
private evaluateStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> { private evaluateStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
switch (node.type) { switch (node.type) {
case "ExpressionStatement": case "ExpressionStatement":
return Effect.as(this.evaluateExpression(getNode(node, "expression")), { kind: "none" }) return Effect.map(this.evaluateExpression(getNode(node, "expression")), (value) => ({ kind: "value", value }))
case "VariableDeclaration": case "VariableDeclaration":
return Effect.map(this.evaluateVariableDeclaration(node), () => ({ kind: "none" })) return Effect.map(this.evaluateVariableDeclaration(node), () => ({ kind: "none" }))
case "ReturnStatement": { case "ReturnStatement": {
@@ -841,6 +833,11 @@ class Interpreter<R> {
const statement = asNode(statementValue, "body") const statement = asNode(statementValue, "body")
const result = yield* self.evaluateStatement(statement) const result = yield* self.evaluateStatement(statement)
if (result.kind === "value") {
self.lastValue = result.value
continue
}
if (result.kind !== "none") { if (result.kind !== "none") {
return result return result
} }
@@ -932,6 +929,7 @@ class Interpreter<R> {
const result = yield* self.evaluateStatement(asNode(statementValue, "consequent")) const result = yield* self.evaluateStatement(asNode(statementValue, "consequent"))
if (result.kind === "break") return { kind: "none" } satisfies StatementResult if (result.kind === "break") return { kind: "none" } satisfies StatementResult
if (result.kind === "return" || result.kind === "continue") return result if (result.kind === "return" || result.kind === "continue") return result
if (result.kind === "value") self.lastValue = result.value
} }
} }
return { kind: "none" } satisfies StatementResult return { kind: "none" } satisfies StatementResult
@@ -959,6 +957,9 @@ class Interpreter<R> {
return result return result
} }
if (result.kind === "value") {
self.lastValue = result.value
}
} }
return { kind: "none" } satisfies StatementResult return { kind: "none" } satisfies StatementResult
@@ -986,6 +987,9 @@ class Interpreter<R> {
return result return result
} }
if (result.kind === "value") {
self.lastValue = result.value
}
} while (yield* self.evaluateExpression(testNode)) } while (yield* self.evaluateExpression(testNode))
return { kind: "none" } satisfies StatementResult return { kind: "none" } satisfies StatementResult
@@ -1041,6 +1045,10 @@ class Interpreter<R> {
return { kind: "none" } satisfies StatementResult return { kind: "none" } satisfies StatementResult
} }
if (result.kind === "value") {
self.lastValue = result.value
}
if (iterationScope) { if (iterationScope) {
const loopScope = self.currentScope() const loopScope = self.currentScope()
for (const name of perIterationBindings) { for (const name of perIterationBindings) {
@@ -1125,6 +1133,10 @@ class Interpreter<R> {
return { kind: "none" } return { kind: "none" }
} }
if (result.kind === "value") {
self.lastValue = result.value
}
if (result.kind === "continue") { if (result.kind === "continue") {
continue continue
} }
@@ -1214,6 +1226,10 @@ class Interpreter<R> {
return { kind: "none" } return { kind: "none" }
} }
if (result.kind === "value") {
self.lastValue = result.value
}
if (result.kind === "continue") { if (result.kind === "continue") {
continue continue
} }
@@ -2017,9 +2033,6 @@ class Interpreter<R> {
if (callable.namespace === "Object" && objectMethodsPreservingIdentity.has(callable.name)) { if (callable.namespace === "Object" && objectMethodsPreservingIdentity.has(callable.name)) {
return invokeGlobalMethod(callable, args, node) return invokeGlobalMethod(callable, args, node)
} }
if (callable.namespace === "Array" && (callable.name === "from" || callable.name === "of")) {
return invokeGlobalMethod(callable, args, node)
}
return boundedData(invokeGlobalMethod(callable, args, node), `${callable.namespace}.${callable.name} result`) return boundedData(invokeGlobalMethod(callable, args, node), `${callable.namespace}.${callable.name} result`)
} }
if (callable instanceof CoercionFunction) { if (callable instanceof CoercionFunction) {
@@ -2359,7 +2372,7 @@ class Interpreter<R> {
if (fn.body.type === "BlockStatement") { if (fn.body.type === "BlockStatement") {
const result = yield* invocation.evaluateStatement(fn.body) const result = yield* invocation.evaluateStatement(fn.body)
return result.kind === "return" ? result.value : undefined return result.kind === "return" || result.kind === "value" ? result.value : undefined
} }
return yield* invocation.evaluateExpression(fn.body) return yield* invocation.evaluateExpression(fn.body)
-2
View File
@@ -1,7 +1,6 @@
export const mathConstants = new Set(["PI", "E", "LN2", "LN10", "LOG2E", "LOG10E", "SQRT2", "SQRT1_2"]) export const mathConstants = new Set(["PI", "E", "LN2", "LN10", "LOG2E", "LOG10E", "SQRT2", "SQRT1_2"])
export const mathMethods = new Set([ export const mathMethods = new Set([
"random",
"max", "max",
"min", "min",
"abs", "abs",
@@ -41,7 +40,6 @@ export const mathMethods = new Set([
export const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNode): number => { export const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNode): number => {
if (!mathMethods.has(name)) throw new InterpreterRuntimeError(`Math.${name} is not available in CodeMode.`, node) if (!mathMethods.has(name)) throw new InterpreterRuntimeError(`Math.${name} is not available in CodeMode.`, node)
if (name === "random") return Math.random()
const nums = args.map((arg) => { const nums = args.map((arg) => {
if (typeof arg !== "number") throw new InterpreterRuntimeError(`Math.${name} expects number arguments.`, node) if (typeof arg !== "number") throw new InterpreterRuntimeError(`Math.${name} expects number arguments.`, node)
return arg return arg
+2 -14
View File
@@ -1,15 +1,6 @@
export const numberMethods = new Set(["toFixed", "toPrecision", "toExponential", "toString", "valueOf"]) export const numberMethods = new Set(["toFixed", "toPrecision", "toExponential", "toString"])
export const numberConstants = new Set([ export const numberConstants = new Set(["MAX_SAFE_INTEGER", "MIN_SAFE_INTEGER", "MAX_VALUE", "MIN_VALUE", "EPSILON"])
"MAX_SAFE_INTEGER",
"MIN_SAFE_INTEGER",
"MAX_VALUE",
"MIN_VALUE",
"EPSILON",
"NaN",
"POSITIVE_INFINITY",
"NEGATIVE_INFINITY",
])
export const numberStatics = new Set(["isInteger", "isFinite", "isNaN", "isSafeInteger", "parseInt", "parseFloat"]) export const numberStatics = new Set(["isInteger", "isFinite", "isNaN", "isSafeInteger", "parseInt", "parseFloat"])
@@ -41,9 +32,6 @@ export const invokeNumberMethod = (value: number, name: string, args: Array<unkn
result = value.toString(radix) result = value.toString(radix)
break break
} }
case "valueOf":
result = value
break
default: default:
throw new InterpreterRuntimeError(`Number method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Number method '${name}' is not available in CodeMode.`, node)
} }
+15 -18
View File
@@ -1,6 +1,6 @@
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js" import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
import { isBlockedMember } from "../tool-runtime.js" import { isBlockedMember } from "../tool-runtime.js"
import { isSandboxValue, SandboxMap, SandboxPromise, SandboxSet, SandboxURLSearchParams } from "../values.js" import { isSandboxValue, SandboxMap, SandboxSet, SandboxURLSearchParams } from "../values.js"
import { boundedData, coerceToString } from "./value.js" import { boundedData, coerceToString } from "./value.js"
export const objectStatics = new Set(["keys", "values", "entries", "hasOwn", "assign", "fromEntries"]) export const objectStatics = new Set(["keys", "values", "entries", "hasOwn", "assign", "fromEntries"])
@@ -10,23 +10,13 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
if (!objectStatics.has(name)) throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node) if (!objectStatics.has(name)) throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
const requireObject = (): Record<string, unknown> => { const requireObject = (): Record<string, unknown> => {
const input = args[0] const input = args[0]
const value = boundedData(args[0], `Object.${name} input`)
if (Array.isArray(input)) return input as unknown as Record<string, unknown> if (Array.isArray(input)) return input as unknown as Record<string, unknown>
if (isSandboxValue(input)) return {} if (isSandboxValue(value)) return {}
if (input instanceof SandboxPromise) { if (value === null || typeof value !== "object") {
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node)
`Object.${name} received an un-awaited Promise; await it before inspecting the result.`,
node,
"InvalidDataValue",
)
} }
if (input === null || typeof input !== "object") { return value as Record<string, unknown>
throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node, "InvalidDataValue")
}
const prototype = Object.getPrototypeOf(input)
if (prototype !== null && prototype !== Object.prototype) {
throw new InterpreterRuntimeError(`Object.${name} expects a data object or array.`, node, "InvalidDataValue")
}
return input as Record<string, unknown>
} }
const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => { const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => {
if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node) if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node)
@@ -38,8 +28,15 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
guardedSet(out, coerceToString(key), item) guardedSet(out, coerceToString(key), item)
} }
switch (name) { switch (name) {
case "keys": case "keys": {
return Object.keys(requireObject()) const value = boundedData(args[0], "Object.keys input")
if (isSandboxValue(value)) return []
if (Array.isArray(value)) return Object.keys(value)
if (value === null || typeof value !== "object") {
throw new InterpreterRuntimeError("Object.keys expects a data object or array.", node)
}
return Object.keys(value)
}
case "values": case "values":
return Object.values(requireObject()) return Object.values(requireObject())
case "entries": case "entries":
-1
View File
@@ -617,7 +617,6 @@ export const prepare = <R>(tools: HostTools<R>, catalogBudget = defaultCatalogBu
"", "",
"Use common JavaScript data operations, functions, control flow, selected standard-library methods, and awaited tool calls. Built-ins include Date, RegExp, Map, Set, URL, URLSearchParams, and URI encoding helpers.", "Use common JavaScript data operations, functions, control flow, selected standard-library methods, and awaited tool calls. Built-ins include Date, RegExp, Map, Set, URL, URLSearchParams, and URI encoding helpers.",
"Modules/imports, classes, generators, timers, fetch, eval, prototype access, unlisted methods, and promise chaining are unavailable. Use Code Mode tools for external operations. Use await with try/catch.", "Modules/imports, classes, generators, timers, fetch, eval, prototype access, unlisted methods, and promise chaining are unavailable. Use Code Mode tools for external operations. Use await with try/catch.",
"Prefer explicit `return`; otherwise only the final top-level expression becomes the result.",
"Dates and URLs serialize to strings at data boundaries; Map/Set/RegExp/URLSearchParams serialize to `{}`.", "Dates and URLs serialize to strings at data boundaries; Map/Set/RegExp/URLSearchParams serialize to `{}`.",
] ]
-21
View File
@@ -658,9 +658,6 @@ describe("CodeMode public contract", () => {
expect(instructions).toContain("URL, URLSearchParams, and URI encoding helpers") expect(instructions).toContain("URL, URLSearchParams, and URI encoding helpers")
expect(instructions).not.toContain("host globals") expect(instructions).not.toContain("host globals")
expect(instructions).toContain("Use Code Mode tools for external operations") expect(instructions).toContain("Use Code Mode tools for external operations")
expect(instructions).toContain(
"Prefer explicit `return`; otherwise only the final top-level expression becomes the result.",
)
expect(instructions).toContain( expect(instructions).toContain(
"Dates and URLs serialize to strings at data boundaries; Map/Set/RegExp/URLSearchParams serialize to `{}`.", "Dates and URLs serialize to strings at data boundaries; Map/Set/RegExp/URLSearchParams serialize to `{}`.",
) )
@@ -1084,24 +1081,6 @@ describe("CodeMode public contract", () => {
expect(Schema.decodeUnknownSync(CodeMode.Result)(JSON.parse(JSON.stringify(result)))).toStrictEqual(result) expect(Schema.decodeUnknownSync(CodeMode.Result)(JSON.parse(JSON.stringify(result)))).toStrictEqual(result)
}) })
test("returns the final top-level expression when return is omitted", async () => {
const result = await Effect.runPromise(CodeMode.execute({ code: `1; 2` }))
expect(result).toStrictEqual({ ok: true, value: 2, toolCalls: [] })
})
test("does not implicitly return expressions nested in control flow", async () => {
const result = await Effect.runPromise(CodeMode.execute({ code: `if (true) { 2 }` }))
expect(result).toStrictEqual({ ok: true, value: null, toolCalls: [] })
})
test("returns null when the final top-level statement is not an expression", async () => {
const result = await Effect.runPromise(CodeMode.execute({ code: `1; const value = 2` }))
expect(result).toStrictEqual({ ok: true, value: null, toolCalls: [] })
})
test("rejects invalid configuration and discovery limits", async () => { test("rejects invalid configuration and discovery limits", async () => {
expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: 0 } })).toThrow(RangeError) expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: 0 } })).toThrow(RangeError)
expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: Number.POSITIVE_INFINITY } })).toThrow( expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: Number.POSITIVE_INFINITY } })).toThrow(
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -177,13 +177,13 @@ describe("OpenAPI.fromSpec", () => {
const spec = await opencodeSpec() const spec = await opencodeSpec()
const result = OpenAPI.fromSpec({ spec, baseUrl }) const result = OpenAPI.fromSpec({ spec, baseUrl })
expect(result.skipped).toHaveLength(4) expect(result.skipped).toHaveLength(5)
expect(result.skipped).toContainEqual({ expect(result.skipped).toContainEqual({
method: "GET", method: "GET",
path: "/api/pty/{ptyID}/connect", path: "/api/pty/{ptyID}/connect",
reason: "WebSocket operations are not supported", reason: "WebSocket operations are not supported",
}) })
expect(result.skipped.filter((item) => item.reason === "SSE operations are not supported")).toHaveLength(2) expect(result.skipped.filter((item) => item.reason === "SSE operations are not supported")).toHaveLength(3)
expect(result.skipped).toContainEqual({ expect(result.skipped).toContainEqual({
method: "GET", method: "GET",
path: "/api/fs/read/*", path: "/api/fs/read/*",
@@ -210,11 +210,11 @@ describe("OpenAPI.fromSpec", () => {
if (!Tool.isDefinition(instructionPut)) throw new Error("v2.session.instructions.entry.put was not generated") if (!Tool.isDefinition(instructionPut)) throw new Error("v2.session.instructions.entry.put was not generated")
expect(inputTypeScript(instructionPut)).toBe("{ sessionID: string; key: string; value: unknown }") expect(inputTypeScript(instructionPut)).toBe("{ sessionID: string; key: string; value: unknown }")
expect(toolAt(result.tools, "v2_session_instructions_entry_put_2")).toBeUndefined() expect(toolAt(result.tools, "v2_session_instructions_entry_put_2")).toBeUndefined()
expect(Tool.isDefinition(toolAt(result.tools, "v2.pty.connect"))).toBe(false) expect(toolAt(result.tools, "v2.pty.connect")).toBeUndefined()
expect(toolAt(result.tools, "v2.session.log")).toBeUndefined() expect(toolAt(result.tools, "v2.session.log")).toBeUndefined()
expect(toolAt(result.tools, "v2.event.subscribe")).toBeUndefined() expect(toolAt(result.tools, "v2.event.subscribe")).toBeUndefined()
expect(toolAt(result.tools, "v2.fs.read")).toBeUndefined() expect(toolAt(result.tools, "v2.fs.read")).toBeUndefined()
expect(toolAt(result.tools, "v2.pty.connect.token")).not.toBeUndefined() expect(toolAt(result.tools, "v2.pty.connectToken")).not.toBeUndefined()
}) })
test("preserves operation path sanitization and collision handling", () => { test("preserves operation path sanitization and collision handling", () => {
-6
View File
@@ -245,12 +245,6 @@ describe("promises at data boundaries", () => {
expect(diagnostic.message).toContain("await tools.ns.tool(...)") expect(diagnostic.message).toContain("await tools.ns.tool(...)")
}) })
test("collection helpers do not let un-awaited promises cross the result boundary", async () => {
const diagnostic = await error(`return Array.from([Promise.resolve(1)])`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("un-awaited Promise")
})
test("passing an un-awaited promise as a tool argument is a clear diagnostic", async () => { test("passing an un-awaited promise as a tool argument is a clear diagnostic", async () => {
const diagnostic = await error(`return await tools.host.sleepy({ id: tools.host.sleepy({ id: 1 }) })`) const diagnostic = await error(`return await tools.host.sleepy({ id: tools.host.sleepy({ id: 1 }) })`)
expect(diagnostic.kind).toBe("InvalidDataValue") expect(diagnostic.kind).toBe("InvalidDataValue")
-106
View File
@@ -19,28 +19,6 @@ const error = async (code: string) => {
return result.error return result.error
} }
describe("Number and Math", () => {
test("Math.random returns a number in [0, 1)", async () => {
expect(await value(`const n = Math.random(); return typeof n === "number" && n >= 0 && n < 1`)).toBe(true)
})
test("Number exposes native non-finite constants", async () => {
expect(
await value(
`return [Number.isNaN(Number.NaN), Number.POSITIVE_INFINITY === Infinity, Number.NEGATIVE_INFINITY === -Infinity]`,
),
).toEqual([true, true, true])
})
test("Number valueOf returns its primitive receiver", async () => {
expect(await value(`return (42).valueOf()`)).toBe(42)
})
test("Number valueOf does not enable boxed numbers", async () => {
expect((await error(`return new Number(42)`)).kind).toBe("UnsupportedSyntax")
})
})
describe("Date", () => { describe("Date", () => {
test("Date.now() returns a number", async () => { test("Date.now() returns a number", async () => {
expect(await value(`return typeof Date.now()`)).toBe("number") expect(await value(`return typeof Date.now()`)).toBe("number")
@@ -773,43 +751,6 @@ describe("sandbox values at intra-sandbox checkpoints", () => {
) )
}) })
test("Object.values/entries preserve nested object identity", async () => {
expect(
await value(`
const child = { selected: false }
const rows = { a: child }
Object.values(rows)[0].selected = true
return child.selected
`),
).toBe(true)
expect(
await value(`
const child = { selected: false }
const rows = { a: child }
Object.entries(rows)[0][1].selected = true
return child.selected
`),
).toBe(true)
})
test("Object enumeration preserves promises and callable references", async () => {
expect(
await value(`
const pending = Promise.resolve(1)
const source = { pending }
return [Object.keys(source), Object.hasOwn(source, "pending"), await Object.values(source)[0], await Object.entries(source)[0][1]]
`),
).toEqual([["pending"], true, 1, 1])
expect(await value(`return Object.values({ max: Math.max })[0](1, 2)`)).toBe(2)
})
test("Object enumeration rejects invalid receivers and gives promises an await hint", async () => {
const diagnostic = await error(`return Object.keys(Promise.resolve({ a: 1 }))`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("await")
expect((await error(`return Object.keys(Math)`)).kind).toBe("InvalidDataValue")
})
test("Object.assign keeps Maps usable", async () => { test("Object.assign keeps Maps usable", async () => {
expect(await value(`const merged = Object.assign({}, { m: new Map([["a", 1]]) }); return merged.m.get("a")`)).toBe( expect(await value(`const merged = Object.assign({}, { m: new Map([["a", 1]]) }); return merged.m.get("a")`)).toBe(
1, 1,
@@ -832,53 +773,6 @@ describe("sandbox values at intra-sandbox checkpoints", () => {
expect(await value(`return Array.from([new Date(5)])[0].getTime()`)).toBe(5) expect(await value(`return Array.from([new Date(5)])[0].getTime()`)).toBe(5)
}) })
test("Array.from and Array.of preserve nested object identity", async () => {
expect(
await value(`
const child = { selected: false }
Array.from([child])[0].selected = true
return child.selected
`),
).toBe(true)
expect(
await value(`
const child = { selected: false }
Array.of(child)[0].selected = true
return child.selected
`),
).toBe(true)
})
test("Array.from and Array.of preserve promises and callable references", async () => {
expect(
await value(`
const pending = Promise.resolve(1)
return [await Array.from([pending])[0], await Array.of(pending)[0]]
`),
).toEqual([1, 1])
expect(await value(`return [Array.from([Math.max])[0](1, 2), Array.of(Math.max)[0](3, 4)]`)).toEqual([2, 4])
})
test("Array.from preserves identity across supported collection shapes", async () => {
expect(
await value(`
const child = { selected: false }
const fromArrayLike = Array.from({ 0: child, length: 1 })
const fromMap = Array.from(new Map([["child", child]]))
const fromSet = Array.from(new Set([child]))
fromArrayLike[0].selected = true
return [fromMap[0][1] === child, fromSet[0] === child, child.selected]
`),
).toEqual([true, true, true])
})
test("Array.from rejects invalid receivers and gives promises an await hint", async () => {
const diagnostic = await error(`return Array.from(Promise.resolve([1]))`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("await")
expect((await error(`return Array.from(() => 1)`)).kind).toBe("InvalidDataValue")
})
test("regexes stay callable through Object.values", async () => { test("regexes stay callable through Object.values", async () => {
expect(await value(`return Object.values({ r: /ab+/ })[0].test("abb")`)).toBe(true) expect(await value(`return Object.values({ r: /ab+/ })[0].test("abb")`)).toBe(true)
}) })
-2
View File
@@ -8,8 +8,6 @@ import { State } from "./state"
export const ID = Agent.ID export const ID = Agent.ID
export type ID = typeof ID.Type export type ID = typeof ID.Type
export const Name = Agent.Name
export type Name = Agent.Name
export const defaultID = ID.make("build") export const defaultID = ID.make("build")
export const Color = Agent.Color export const Color = Agent.Color
+3 -7
View File
@@ -6,7 +6,6 @@ import type {
JSONValue, JSONValue,
LanguageModelV3, LanguageModelV3,
LanguageModelV3CallOptions, LanguageModelV3CallOptions,
LanguageModelV3FinishReason,
LanguageModelV3FunctionTool, LanguageModelV3FunctionTool,
LanguageModelV3Message, LanguageModelV3Message,
LanguageModelV3Prompt, LanguageModelV3Prompt,
@@ -305,7 +304,6 @@ function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) {
const route: AnyRoute = { const route: AnyRoute = {
id: `ai-sdk:${ProviderV2.packageName(info.package) ?? "unknown"}`, id: `ai-sdk:${ProviderV2.packageName(info.package) ?? "unknown"}`,
provider: ProviderID.make(info.providerID), provider: ProviderID.make(info.providerID),
providerMetadataKey: optionKey,
protocol: "ai-sdk", protocol: "ai-sdk",
endpoint: Endpoint.path("/", { baseURL: "https://ai-sdk.local" }), endpoint: Endpoint.path("/", { baseURL: "https://ai-sdk.local" }),
auth: Auth.none, auth: Auth.none,
@@ -418,7 +416,7 @@ function assistantPart(part: ContentPart): AssistantContent {
case "media": case "media":
return [{ type: "file", mediaType: part.mediaType, data: part.data, filename: part.filename }] return [{ type: "file", mediaType: part.mediaType, data: part.data, filename: part.filename }]
case "reasoning": case "reasoning":
return [{ type: "reasoning", text: part.text, providerOptions: providerOptions(part.providerMetadata) }] return [{ type: "reasoning", text: part.text }]
case "tool-call": case "tool-call":
return [ return [
{ {
@@ -427,7 +425,6 @@ function assistantPart(part: ContentPart): AssistantContent {
toolName: part.name, toolName: part.name,
input: part.input, input: part.input,
providerExecuted: part.providerExecuted, providerExecuted: part.providerExecuted,
providerOptions: providerOptions(part.providerMetadata),
}, },
] ]
case "tool-result": case "tool-result":
@@ -443,7 +440,6 @@ function toolResultPart(part: ContentPart): ToolResultContent[] {
toolCallId: part.id, toolCallId: part.id,
toolName: part.name, toolName: part.name,
output: toolOutput(part.result), output: toolOutput(part.result),
providerOptions: providerOptions(part.providerMetadata),
}, },
] ]
} }
@@ -628,8 +624,8 @@ function usage(input: Extract<LanguageModelV3StreamPart, { type: "finish" }>["us
return Object.values(output).some((value) => value !== undefined) ? output : undefined return Object.values(output).some((value) => value !== undefined) ? output : undefined
} }
function finishReason(value: LanguageModelV3FinishReason): FinishReason { function finishReason(value: unknown): FinishReason {
return value.unified === "other" ? "unknown" : value.unified return Schema.is(FinishReason)(value) ? value : "unknown"
} }
function providerMetadata(value: unknown) { function providerMetadata(value: unknown) {
+2 -3
View File
@@ -1,7 +1,6 @@
export * as ConfigProviderPlugin from "./provider" export * as ConfigProviderPlugin from "./provider"
import { define } from "@opencode-ai/plugin/v2/effect/plugin" import { define } from "@opencode-ai/plugin/v2/effect/plugin"
import { Money } from "@opencode-ai/schema/money"
import { Effect, Stream } from "effect" import { Effect, Stream } from "effect"
import { Config } from "../../config" import { Config } from "../../config"
import { ModelV2 } from "../../model" import { ModelV2 } from "../../model"
@@ -92,8 +91,8 @@ export const Plugin = define({
input: cost.input, input: cost.input,
output: cost.output, output: cost.output,
cache: { cache: {
read: cost.cache?.read ?? Money.USDPerMillionTokens.zero, read: cost.cache?.read ?? 0,
write: cost.cache?.write ?? Money.USDPerMillionTokens.zero, write: cost.cache?.write ?? 0,
}, },
})) }))
} }
+4 -5
View File
@@ -1,7 +1,6 @@
export * as ConfigProvider from "./provider" export * as ConfigProvider from "./provider"
import { Schema } from "effect" import { Schema } from "effect"
import { Money } from "@opencode-ai/schema/money"
import { ModelV2 } from "../model" import { ModelV2 } from "../model"
const JsonRecord = Schema.Record(Schema.String, Schema.Json) const JsonRecord = Schema.Record(Schema.String, Schema.Json)
@@ -18,8 +17,8 @@ export class Request extends Schema.Class<Request>("ConfigV2.Provider.Request")(
}) {} }) {}
class Cache extends Schema.Class<Cache>("ConfigV2.Model.Cost.Cache")({ class Cache extends Schema.Class<Cache>("ConfigV2.Model.Cost.Cache")({
read: Money.USDPerMillionTokens.pipe(Schema.optional), read: Schema.Finite.pipe(Schema.optional),
write: Money.USDPerMillionTokens.pipe(Schema.optional), write: Schema.Finite.pipe(Schema.optional),
}) {} }) {}
class Cost extends Schema.Class<Cost>("ConfigV2.Model.Cost")({ class Cost extends Schema.Class<Cost>("ConfigV2.Model.Cost")({
@@ -27,8 +26,8 @@ class Cost extends Schema.Class<Cost>("ConfigV2.Model.Cost")({
type: Schema.Literal("context"), type: Schema.Literal("context"),
size: Schema.Int, size: Schema.Int,
}).pipe(Schema.optional), }).pipe(Schema.optional),
input: Money.USDPerMillionTokens, input: Schema.Finite,
output: Money.USDPerMillionTokens, output: Schema.Finite,
cache: Cache.pipe(Schema.optional), cache: Cache.pipe(Schema.optional),
}) {} }) {}
-1
View File
@@ -48,6 +48,5 @@ export const migrations = (
import("./migration/20260705180000_rename_instructions"), import("./migration/20260705180000_rename_instructions"),
import("./migration/20260706223930_add-session-fork"), import("./migration/20260706223930_add-session-fork"),
import("./migration/20260707010146_durable_session_inbox"), import("./migration/20260707010146_durable_session_inbox"),
import("./migration/20260707120000_migrate_prelaunch_v2_state"),
]) ])
).map((module) => module.default) satisfies DatabaseMigration.Migration[] ).map((module) => module.default) satisfies DatabaseMigration.Migration[]
@@ -1,227 +0,0 @@
import { sql } from "drizzle-orm"
import { Effect, Schema } from "effect"
import type { DatabaseMigration } from "../migration"
const decodeJson = Schema.decodeUnknownSync(Schema.UnknownFromJsonString)
const isObject = Schema.is(Schema.Record(Schema.String, Schema.Unknown))
export default {
id: "20260707120000_migrate_prelaunch_v2_state",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(
sql`DELETE FROM session_message WHERE type = 'compaction' AND json_extract(data, '$.status') = 'queued'`,
)
const messages = yield* tx.all<{ id: string; type: string; data: string }>(
sql`SELECT id, type, data FROM session_message WHERE type IN ('skill', 'shell', 'assistant', 'compaction', 'synthetic')`,
)
for (const row of messages) {
const data = object(decodeJson(row.data))
yield* tx.run(
sql`UPDATE session_message SET data = ${JSON.stringify(messageData(row.type, data))} WHERE id = ${row.id}`,
)
}
yield* tx.run(sql`DELETE FROM event WHERE type = 'session.compaction.delta.1'`)
const events = yield* tx.all<{ id: string; aggregateID: string; seq: number; type: string; data: string }>(sql`
SELECT id, aggregate_id as aggregateID, seq, type, data
FROM event
WHERE type IN (
'session.skill.activated.1',
'session.skill.activated.2',
'session.compaction.started.1',
'session.compaction.started.2',
'session.compaction.ended.1',
'session.compaction.failed.1',
'session.compaction.failed.2',
'session.revert.staged.1',
'session.revert.staged.2'
)
ORDER BY aggregate_id, seq
`)
const compactionReasons = new Map<string, "auto" | "manual">()
for (const row of events) {
const data = object(decodeJson(row.data))
if (row.type.startsWith("session.compaction.ended.")) {
compactionReasons.delete(row.aggregateID)
continue
}
const event = eventData(row.type, data, compactionReasons.get(row.aggregateID))
if (row.type.startsWith("session.compaction.started."))
compactionReasons.set(row.aggregateID, event.data.reason === "auto" ? "auto" : "manual")
if (row.type.startsWith("session.compaction.failed.")) compactionReasons.delete(row.aggregateID)
yield* tx.run(
sql`UPDATE event SET type = ${event.type}, data = ${JSON.stringify(event.data)} WHERE id = ${row.id}`,
)
}
})
},
} satisfies DatabaseMigration.Migration
function messageData(type: string, data: Record<string, unknown>) {
if (type === "skill")
return defined({
metadata: data.metadata,
time: data.time,
skill: data.skill ?? data.id ?? data.name,
name: data.name,
text: data.text,
})
if (type === "shell") {
const shell = object(data.shell)
return defined({
metadata: data.metadata,
time: data.time,
shellID: data.shellID ?? shell.id,
command: data.command ?? shell.command,
status: data.status ?? shell.status,
exit: data.exit ?? shell.exit,
output: data.output,
})
}
if (type === "assistant")
return defined({
metadata: data.metadata,
time: data.time,
agent: data.agent,
model: data.model,
content: Array.isArray(data.content) ? data.content.map(assistantContent) : data.content,
snapshot: data.snapshot,
finish: data.finish,
cost: data.cost,
tokens: data.tokens,
error: data.error,
retry: data.retry,
})
if (type === "compaction") {
if (data.status === "failed")
return defined({
metadata: data.metadata,
time: data.time,
status: data.status,
reason: data.reason,
error: data.error ?? genericCompactionError,
})
return defined({
metadata: data.metadata,
time: data.time,
status: data.status,
reason: data.reason,
summary: data.summary,
recent: data.recent,
})
}
if (type === "synthetic")
return defined({ metadata: data.metadata, time: data.time, text: data.text, description: data.description })
const { sessionID: _, ...current } = data
return current
}
function assistantContent(value: unknown) {
const content = object(value)
if (content.type === "text") return defined({ type: content.type, text: content.text })
if (content.type === "reasoning")
return defined({ type: content.type, text: content.text, state: content.state, time: content.time })
if (content.type !== "tool") return content
return defined({
type: content.type,
id: content.id,
name: content.name,
executed: content.executed,
providerState: content.providerState,
providerResultState: content.providerResultState,
state: toolState(content.state),
time: content.time,
})
}
function toolState(value: unknown) {
const state = object(value)
if (state.status === "pending" || state.status === "streaming")
return defined({ status: "streaming", input: state.input })
if (state.status === "running")
return defined({ status: state.status, input: state.input, structured: state.structured, content: state.content })
if (state.status === "completed")
return defined({
status: state.status,
input: state.input,
structured: state.structured,
content: state.content,
result: state.result,
})
if (state.status === "error")
return defined({
status: state.status,
input: state.input,
structured: state.structured,
content: state.content,
error: state.error,
result: state.result,
})
return state
}
function eventData(type: string, data: Record<string, unknown>, compactionReason?: "auto" | "manual") {
if (type.startsWith("session.skill.activated."))
return {
type: "session.skill.activated.1",
data: defined({ sessionID: data.sessionID, id: data.id ?? data.name, name: data.name, text: data.text }),
}
if (type.startsWith("session.compaction.started."))
return {
type: "session.compaction.started.1",
data: defined({
sessionID: data.sessionID,
reason: data.reason,
recent: data.recent ?? "",
inputID: data.inputID,
}),
}
if (type.startsWith("session.compaction.failed."))
return {
type: "session.compaction.failed.1",
data: defined({
sessionID: data.sessionID,
reason: data.reason ?? compactionReason ?? "manual",
error: data.error ?? genericCompactionError,
inputID: data.inputID,
}),
}
const revert = object(data.revert)
return {
type: "session.revert.staged.1",
data: defined({
sessionID: data.sessionID,
revert: defined({
messageID: revert.messageID,
partID: revert.partID,
snapshot: revert.snapshot,
files: Array.isArray(revert.files)
? revert.files.map((value) => {
const file = object(value)
return defined({
file: file.file ?? file.path,
patch: file.patch,
additions: file.additions,
deletions: file.deletions,
status: file.status,
})
})
: undefined,
}),
}),
}
}
const genericCompactionError = {
type: "compaction.failed",
message: "Compaction failed before recording an error",
}
function object(value: unknown): Record<string, unknown> {
return isObject(value) ? value : {}
}
function defined(value: Record<string, unknown>) {
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined))
}
+1 -1
View File
@@ -200,6 +200,6 @@ export const node = makeLocationNode({ service: Service, layer, deps: [FSUtil.no
// TODO: Publish watcher/file-edit events after V2 watcher integration exists. // TODO: Publish watcher/file-edit events after V2 watcher integration exists.
// TODO: Add snapshots / undo after V2 snapshot design exists. // TODO: Add snapshots / undo after V2 snapshot design exists.
// TODO: Notify LSP and collect diagnostics after V2 LSP runtime exists. // TODO: Notify LSP and collect diagnostics after V2 LSP runtime exists.
// TODO: Design multi-file transactions / rollback if patch needs atomic edits. // TODO: Design multi-file transactions / rollback if apply_patch needs atomic edits.
// Until then, edits are sequential and report partial application. // Until then, edits are sequential and report partial application.
// TODO: Define crash recovery and idempotency for side effects between Tool.Called and durable settlement. // TODO: Define crash recovery and idempotency for side effects between Tool.Called and durable settlement.
+2 -2
View File
@@ -1,6 +1,6 @@
export * as File from "./file" export * as File from "./file"
import { FileDiff } from "@opencode-ai/schema/file-diff" import { Revert } from "@opencode-ai/schema/revert"
export const Diff = FileDiff.Info export const Diff = Revert.FileDiff
export type Diff = typeof Diff.Type export type Diff = typeof Diff.Type
@@ -46,7 +46,7 @@ const layer = Layer.effect(
.flatMap((item) => item.info.watcher?.ignore ?? []) .flatMap((item) => item.info.watcher?.ignore ?? [])
const home = path.resolve(location.directory) === path.resolve(os.homedir()) const home = path.resolve(location.directory) === path.resolve(os.homedir())
if (!home && location.vcs) { if (!home) {
yield* watcher yield* watcher
.subscribe({ .subscribe({
path: location.directory, path: location.directory,
+1 -1
View File
@@ -606,7 +606,7 @@ const layer = Layer.effect(
file, file,
])).text ])).text
return { return {
file, path: file,
status, status,
additions: binary ? 0 : Number(stats[0] ?? 0), additions: binary ? 0 : Number(stats[0] ?? 0),
deletions: binary ? 0 : Number(stats[1] ?? 0), deletions: binary ? 0 : Number(stats[1] ?? 0),
+22 -35
View File
@@ -150,44 +150,31 @@ export type LocationError = LayerNode.Error<typeof locationServices>
export function buildLocationServiceMap( export function buildLocationServiceMap(
replacements: LayerNode.Replacements = [], replacements: LayerNode.Replacements = [],
): Layer.Layer<LocationServiceMap.Service> { ): Layer.Layer<LocationServiceMap.Service> {
// Structural Equal is own-key-set sensitive, so `{ directory }` (schema-decoded
// payloads omit optional keys) and `{ directory, workspaceID: undefined }` are
// different RcMap keys. The RcMap caches by the raw key before the build
// callback runs, so canonicalize at the map boundary to the key-present shape.
const canonical = (ref: Location.Ref) => Location.Ref.make({ directory: ref.directory, workspaceID: ref.workspaceID })
return Layer.effect( return Layer.effect(
LocationServiceMap.Service, LocationServiceMap.Service,
Effect.map( LayerMap.make(
LayerMap.make( (ref: Location.Ref) => {
(ref: Location.Ref) => { const startedAt = performance.now()
const startedAt = performance.now() const allReplacements = replacements.concat([[Location.node, Location.boundNode(ref)]])
const allReplacements = replacements.concat([[Location.node, Location.boundNode(ref)]]) // Apply replacements during hoist, not afterward: replacements can
// Apply replacements during hoist, not afterward: replacements can // introduce new tagged dependencies (Location.boundNode depends on
// introduce new tagged dependencies (Location.boundNode depends on // Project), and the hoist walk is the only pass that can still slice
// Project), and the hoist walk is the only pass that can still slice // those back out.
// those back out. const location = LayerNode.hoist(locationServices, Node.tags.values.global, allReplacements)
const location = LayerNode.hoist(locationServices, Node.tags.values.global, allReplacements)
return LayerNode.compile(location.node).pipe( return LayerNode.compile(location.node).pipe(
Layer.fresh, Layer.fresh,
Layer.tap(() => Layer.tap(() =>
Effect.logInfo("location services booted", { Effect.logInfo("location services booted", {
directory: ref.directory, directory: ref.directory,
workspaceID: ref.workspaceID, workspaceID: ref.workspaceID,
durationMs: Math.round(performance.now() - startedAt), durationMs: Math.round(performance.now() - startedAt),
}), }),
), ),
Layer.provide(LayerNode.compile(location.hoisted)), Layer.provide(LayerNode.compile(location.hoisted)),
) )
}, },
{ idleTimeToLive: "60 minutes" }, { idleTimeToLive: "60 minutes" },
),
(inner) => ({
...inner,
get: (ref: Location.Ref) => inner.get(canonical(ref)),
contextEffect: (ref: Location.Ref) => inner.contextEffect(canonical(ref)),
invalidate: (ref: Location.Ref) => inner.invalidate(canonical(ref)),
}),
), ),
) )
} }
+16 -118
View File
@@ -3,7 +3,7 @@ export * as MCPClient from "./client"
import path from "node:path" import path from "node:path"
import { execFile } from "node:child_process" import { execFile } from "node:child_process"
import { pathToFileURL } from "node:url" import { pathToFileURL } from "node:url"
import { Client } from "@modelcontextprotocol/sdk/client/index.js" import { Client, type ClientOptions } from "@modelcontextprotocol/sdk/client/index.js"
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js" import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { UnauthorizedError, type OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js" import { UnauthorizedError, type OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js"
@@ -21,7 +21,6 @@ import {
ListToolsResultSchema, ListToolsResultSchema,
PromptListChangedNotificationSchema, PromptListChangedNotificationSchema,
PromptSchema, PromptSchema,
ResourceListChangedNotificationSchema,
type LoggingMessageNotification, type LoggingMessageNotification,
LoggingMessageNotificationSchema, LoggingMessageNotificationSchema,
ToolListChangedNotificationSchema, ToolListChangedNotificationSchema,
@@ -69,13 +68,11 @@ export interface ToolDefinition {
export interface PromptDefinition { export interface PromptDefinition {
readonly name: string readonly name: string
readonly description: string | undefined readonly description: string | undefined
readonly arguments: readonly arguments: ReadonlyArray<{
| ReadonlyArray<{ readonly name: string
readonly name: string readonly description: string | undefined
readonly description: string | undefined readonly required: boolean | undefined
readonly required: boolean | undefined }> | undefined
}>
| undefined
} }
export interface PromptMessage { export interface PromptMessage {
@@ -87,28 +84,6 @@ export interface PromptResult {
readonly messages: ReadonlyArray<PromptMessage> readonly messages: ReadonlyArray<PromptMessage>
} }
export interface ResourceDefinition {
readonly name: string
readonly uri: string
readonly description: string | undefined
readonly mimeType: string | undefined
}
export interface ResourceTemplateDefinition {
readonly name: string
readonly uriTemplate: string
readonly description: string | undefined
readonly mimeType: string | undefined
}
export type ResourceContentPart =
| { readonly type: "text"; readonly uri: string; readonly text: string; readonly mimeType: string | undefined }
| { readonly type: "blob"; readonly uri: string; readonly blob: string; readonly mimeType: string | undefined }
export interface ReadResourceResult {
readonly contents: ReadonlyArray<ResourceContentPart>
}
export type CallToolContent = export type CallToolContent =
| { readonly type: "text"; readonly text: string } | { readonly type: "text"; readonly text: string }
| { readonly type: "media"; readonly data: string; readonly mimeType: string } | { readonly type: "media"; readonly data: string; readonly mimeType: string }
@@ -149,12 +124,6 @@ export interface Connection {
readonly tools: () => Effect.Effect<ToolDefinition[], Error> readonly tools: () => Effect.Effect<ToolDefinition[], Error>
/** Lists the server's prompts; returns [] when the server doesn't advertise prompt support, fails on a transport error. */ /** Lists the server's prompts; returns [] when the server doesn't advertise prompt support, fails on a transport error. */
readonly prompts: () => Effect.Effect<PromptDefinition[], Error> readonly prompts: () => Effect.Effect<PromptDefinition[], Error>
/** Lists the server's resources; returns [] when the server doesn't advertise resource support. */
readonly resources: () => Effect.Effect<ResourceDefinition[], Error>
/** Lists the server's resource templates; returns [] when the server doesn't advertise resource support. */
readonly resourceTemplates: () => Effect.Effect<ResourceTemplateDefinition[], Error>
/** Reads one resource; returns undefined when the server doesn't advertise resource support. */
readonly readResource: (input: { readonly uri: string }) => Effect.Effect<ReadResourceResult | undefined, Error>
/** Invokes a prompt on the server. Interruption aborts the in-flight request. */ /** Invokes a prompt on the server. Interruption aborts the in-flight request. */
readonly prompt: (input: { readonly prompt: (input: {
readonly name: string readonly name: string
@@ -172,8 +141,6 @@ export interface Connection {
readonly onToolsChanged: (callback: () => void) => void readonly onToolsChanged: (callback: () => void) => void
/** Registers a callback fired when the server announces its prompt list changed; no-op if unsupported. */ /** Registers a callback fired when the server announces its prompt list changed; no-op if unsupported. */
readonly onPromptsChanged: (callback: () => void) => void readonly onPromptsChanged: (callback: () => void) => void
/** Registers a callback fired when the server announces its resource catalog changed. */
readonly onResourcesChanged: (callback: () => void) => void
} }
/** Connects an MCP server; closing the calling scope tears down the transport and any spawned process. */ /** Connects an MCP server; closing the calling scope tears down the transport and any spawned process. */
@@ -201,8 +168,7 @@ export const connect = Effect.fnUntraced(function* (
}, },
}) })
} }
if (!URL.canParse(config.url)) if (!URL.canParse(config.url)) return yield* new ConnectError({ server, message: `Invalid MCP URL for "${server}"` })
return yield* new ConnectError({ server, message: `Invalid MCP URL for "${server}"` })
return new StreamableHTTPClientTransport(new URL(config.url), { return new StreamableHTTPClientTransport(new URL(config.url), {
requestInit: config.headers ? { headers: config.headers } : undefined, requestInit: config.headers ? { headers: config.headers } : undefined,
authProvider, authProvider,
@@ -236,7 +202,10 @@ export const connect = Effect.fnUntraced(function* (
}).pipe(Effect.exit) }).pipe(Effect.exit)
if (Exit.isSuccess(exit)) { if (Exit.isSuccess(exit)) {
yield* Effect.addFinalizer(() => yield* Effect.addFinalizer(() =>
cleanupStdioDescendants(transport).pipe(Effect.andThen(Effect.promise(() => client.close())), Effect.ignore), cleanupStdioDescendants(transport).pipe(
Effect.andThen(Effect.promise(() => client.close())),
Effect.ignore,
),
) )
const catalogTimeout = config.timeout?.catalog ?? DEFAULT_CATALOG_TIMEOUT const catalogTimeout = config.timeout?.catalog ?? DEFAULT_CATALOG_TIMEOUT
const executionTimeout = config.timeout?.execution ?? DEFAULT_EXECUTION_TIMEOUT const executionTimeout = config.timeout?.execution ?? DEFAULT_EXECUTION_TIMEOUT
@@ -288,9 +257,7 @@ export const connect = Effect.fnUntraced(function* (
), ),
catch: (error) => (error instanceof Error ? error : new Error(String(error))), catch: (error) => (error instanceof Error ? error : new Error(String(error))),
}).pipe( }).pipe(
Effect.tapError((error) => Effect.tapError((error) => Effect.logWarning("failed to list MCP prompts", { server, error: error.message })),
Effect.logWarning("failed to list MCP prompts", { server, error: error.message }),
),
) )
return prompts.map((prompt) => ({ return prompts.map((prompt) => ({
name: prompt.name, name: prompt.name,
@@ -302,74 +269,6 @@ export const connect = Effect.fnUntraced(function* (
})), })),
})) }))
}), }),
resources: () =>
Effect.gen(function* () {
if (!client.getServerCapabilities()?.resources) return []
const resources = yield* Effect.tryPromise({
try: () =>
paginate(
(cursor) =>
client.listResources(cursor === undefined ? undefined : { cursor }, { timeout: catalogTimeout }),
(result) => result.resources,
),
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
}).pipe(
Effect.tapError((error) =>
Effect.logWarning("failed to list MCP resources", { server, error: error.message }),
),
)
return resources.map((resource) => ({
name: resource.name,
uri: resource.uri,
description: resource.description,
mimeType: resource.mimeType,
}))
}),
resourceTemplates: () =>
Effect.gen(function* () {
if (!client.getServerCapabilities()?.resources) return []
const templates = yield* Effect.tryPromise({
try: () =>
paginate(
(cursor) =>
client.listResourceTemplates(cursor === undefined ? undefined : { cursor }, {
timeout: catalogTimeout,
}),
(result) => result.resourceTemplates,
),
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
}).pipe(
Effect.tapError((error) =>
Effect.logWarning("failed to list MCP resource templates", { server, error: error.message }),
),
)
return templates.map((template) => ({
name: template.name,
uriTemplate: template.uriTemplate,
description: template.description,
mimeType: template.mimeType,
}))
}),
readResource: (input) =>
Effect.gen(function* () {
if (!client.getServerCapabilities()?.resources) return undefined
const result = yield* Effect.tryPromise({
try: (signal) => client.readResource({ uri: input.uri }, { signal, timeout: executionTimeout }),
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
}).pipe(
Effect.tapError((error) =>
Effect.logWarning("failed to read MCP resource", { server, uri: input.uri, error: error.message }),
),
)
return {
contents: result.contents.map(
(part): ResourceContentPart =>
"text" in part
? { type: "text", uri: part.uri, text: part.text, mimeType: part.mimeType }
: { type: "blob", uri: part.uri, blob: part.blob, mimeType: part.mimeType },
),
}
}),
prompt: (input) => prompt: (input) =>
Effect.tryPromise({ Effect.tryPromise({
try: (signal) => try: (signal) =>
@@ -429,14 +328,13 @@ export const connect = Effect.fnUntraced(function* (
if (!client.getServerCapabilities()?.prompts?.listChanged) return if (!client.getServerCapabilities()?.prompts?.listChanged) return
client.setNotificationHandler(PromptListChangedNotificationSchema, async () => callback()) client.setNotificationHandler(PromptListChangedNotificationSchema, async () => callback())
}, },
onResourcesChanged: (callback) => {
if (!client.getServerCapabilities()?.resources?.listChanged) return
client.setNotificationHandler(ResourceListChangedNotificationSchema, async () => callback())
},
} satisfies Connection } satisfies Connection
} }
yield* cleanupStdioDescendants(transport).pipe(Effect.andThen(Effect.promise(() => transport.close())), Effect.ignore) yield* cleanupStdioDescendants(transport).pipe(
Effect.andThen(Effect.promise(() => transport.close())),
Effect.ignore,
)
const error = Cause.squash(exit.cause) const error = Cause.squash(exit.cause)
if (error instanceof UnauthorizedError) return yield* new NeedsAuthError({ server }) if (error instanceof UnauthorizedError) return yield* new NeedsAuthError({ server })
return yield* new ConnectError({ server, message: error instanceof Error ? error.message : String(error) }) return yield* new ConnectError({ server, message: error instanceof Error ? error.message : String(error) })
+50 -80
View File
@@ -83,16 +83,48 @@ export class PromptResult extends Schema.Class<PromptResult>("MCP.PromptResult")
messages: Schema.Array(PromptMessage), messages: Schema.Array(PromptMessage),
}) {} }) {}
export const Resource = Mcp.Resource export class Resource extends Schema.Class<Resource>("MCP.Resource")({
export type Resource = Mcp.Resource server: ServerName,
export const ResourceTemplate = Mcp.ResourceTemplate name: Schema.String,
export type ResourceTemplate = Mcp.ResourceTemplate uri: Schema.String,
export const ResourceCatalog = Mcp.ResourceCatalog description: Schema.String.pipe(Schema.optional),
export type ResourceCatalog = Mcp.ResourceCatalog mimeType: Schema.String.pipe(Schema.optional),
export const ResourceContentPart = Mcp.ResourceContentPart }) {}
export type ResourceContentPart = Mcp.ResourceContentPart
export const ResourceContent = Mcp.ResourceContent export class ResourceTemplate extends Schema.Class<ResourceTemplate>("MCP.ResourceTemplate")({
export type ResourceContent = Mcp.ResourceContent server: ServerName,
name: Schema.String,
uriTemplate: Schema.String,
description: Schema.String.pipe(Schema.optional),
mimeType: Schema.String.pipe(Schema.optional),
}) {}
export class ResourceCatalog extends Schema.Class<ResourceCatalog>("MCP.ResourceCatalog")({
resources: Schema.Array(Resource),
templates: Schema.Array(ResourceTemplate),
}) {}
export const ResourceContentPart = Schema.Union([
Schema.Struct({
type: Schema.Literal("text"),
uri: Schema.String,
text: Schema.String,
mimeType: Schema.String.pipe(Schema.optional),
}),
Schema.Struct({
type: Schema.Literal("blob"),
uri: Schema.String,
blob: Schema.String,
mimeType: Schema.String.pipe(Schema.optional),
}),
]).pipe(Schema.toTaggedUnion("type"))
export type ResourceContentPart = typeof ResourceContentPart.Type
export class ResourceContent extends Schema.Class<ResourceContent>("MCP.ResourceContent")({
server: ServerName,
uri: Schema.String,
contents: Schema.Array(ResourceContentPart),
}) {}
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("MCP.NotFoundError", { export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("MCP.NotFoundError", {
server: ServerName, server: ServerName,
@@ -383,24 +415,6 @@ export const layer = Layer.effect(
), ),
}) })
const toResource = (server: ServerName, def: MCPClient.ResourceDefinition) =>
Resource.make({
server,
name: def.name,
uri: def.uri,
description: def.description,
mimeType: def.mimeType,
})
const toResourceTemplate = (server: ServerName, def: MCPClient.ResourceTemplateDefinition) =>
ResourceTemplate.make({
server,
name: def.name,
uriTemplate: def.uriTemplate,
description: def.description,
mimeType: def.mimeType,
})
const refreshTools = (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) => const refreshTools = (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) =>
connection.tools().pipe( connection.tools().pipe(
Effect.map((defs) => { Effect.map((defs) => {
@@ -429,7 +443,6 @@ export const layer = Layer.effect(
entry.prompts = undefined entry.prompts = undefined
entry.status = { status: "failed", error: "Connection closed" } entry.status = { status: "failed", error: "Connection closed" }
fork(events.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore)) fork(events.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore))
fork(events.publish(McpEvent.ResourcesChanged, { server: name }).pipe(Effect.ignore))
fork(events.publish(Command.Event.Updated, {}).pipe(Effect.ignore)) fork(events.publish(Command.Event.Updated, {}).pipe(Effect.ignore))
fork(events.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)) fork(events.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore))
}) })
@@ -445,10 +458,6 @@ export const layer = Layer.effect(
connection.onPromptsChanged(() => { connection.onPromptsChanged(() => {
fork(refreshPrompts(name, entry, connection).pipe(Effect.ignore)) fork(refreshPrompts(name, entry, connection).pipe(Effect.ignore))
}) })
connection.onResourcesChanged(() => {
if (entry.client !== connection) return
fork(events.publish(McpEvent.ResourcesChanged, { server: name }).pipe(Effect.ignore))
})
} }
const serverLog = (server: ServerName, message: MCPClient.LogMessage) => { const serverLog = (server: ServerName, message: MCPClient.LogMessage) => {
@@ -492,7 +501,6 @@ export const layer = Layer.effect(
// after the initial registration sweep and emits no list-changed notification would otherwise // after the initial registration sweep and emits no list-changed notification would otherwise
// stay invisible to the model. // stay invisible to the model.
yield* events.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore) yield* events.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore)
yield* events.publish(McpEvent.ResourcesChanged, { server: name }).pipe(Effect.ignore)
yield* events.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore) yield* events.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
fork(refreshPrompts(name, entry, result.value.connection).pipe(Effect.ignore)) fork(refreshPrompts(name, entry, result.value.connection).pipe(Effect.ignore))
return return
@@ -549,6 +557,11 @@ export const layer = Layer.effect(
concurrency: "unbounded", concurrency: "unbounded",
discard: true, discard: true,
}) })
const gate = Effect.fnUntraced(function* (server: ServerName | string) {
const target = yield* requireServer(server)
yield* Deferred.await(target.entry.startup)
})
return Service.of({ return Service.of({
servers: Effect.fn("MCP.servers")(function* () { servers: Effect.fn("MCP.servers")(function* () {
const entries = Array.from(runtime).toSorted(([a], [b]) => a.localeCompare(b)) const entries = Array.from(runtime).toSorted(([a], [b]) => a.localeCompare(b))
@@ -624,54 +637,11 @@ export const layer = Layer.effect(
}), }),
resourceCatalog: Effect.fn("MCP.resourceCatalog")(function* () { resourceCatalog: Effect.fn("MCP.resourceCatalog")(function* () {
yield* whenAllReady yield* whenAllReady
const catalogs = yield* Effect.forEach( return new ResourceCatalog({ resources: [], templates: [] })
Array.from(runtime),
([name, entry]) => {
if (!entry.client) return Effect.succeed({ resources: [], templates: [] })
return Effect.all(
{
resources: entry.client.resources().pipe(Effect.catch(() => Effect.succeed([]))),
templates: entry.client.resourceTemplates().pipe(Effect.catch(() => Effect.succeed([]))),
},
{ concurrency: "unbounded" },
).pipe(
Effect.map((catalog) => ({
resources: catalog.resources.map((def) => toResource(name, def)),
templates: catalog.templates.map((def) => toResourceTemplate(name, def)),
})),
)
},
{ concurrency: "unbounded" },
)
return ResourceCatalog.make({
resources: catalogs
.flatMap((catalog) => catalog.resources)
.toSorted(
(a, b) => a.server.localeCompare(b.server) || a.name.localeCompare(b.name) || a.uri.localeCompare(b.uri),
),
templates: catalogs
.flatMap((catalog) => catalog.templates)
.toSorted(
(a, b) =>
a.server.localeCompare(b.server) ||
a.name.localeCompare(b.name) ||
a.uriTemplate.localeCompare(b.uriTemplate),
),
})
}), }),
readResource: Effect.fn("MCP.readResource")(function* (input) { readResource: Effect.fn("MCP.readResource")(function* (input) {
const target = yield* requireServer(input.server) yield* gate(input.server)
yield* Deferred.await(target.entry.startup) return undefined
if (!target.entry.client) return undefined
const result = yield* target.entry.client
.readResource({ uri: input.uri })
.pipe(Effect.catch(() => Effect.succeed(undefined)))
if (!result) return undefined
return ResourceContent.make({
server: target.name,
uri: input.uri,
contents: result.contents,
})
}), }),
}) })
}), }),
+12 -13
View File
@@ -2,7 +2,6 @@ import path from "path"
import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect" import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect"
import { HttpClient, HttpClientRequest } from "effect/unstable/http" import { HttpClient, HttpClientRequest } from "effect/unstable/http"
import { ModelsDev } from "@opencode-ai/schema/models-dev" import { ModelsDev } from "@opencode-ai/schema/models-dev"
import { Money } from "@opencode-ai/schema/money"
import { Global } from "./global" import { Global } from "./global"
import { Flag } from "./flag/flag" import { Flag } from "./flag/flag"
import { Flock } from "./util/flock" import { Flock } from "./util/flock"
@@ -19,10 +18,10 @@ export type CatalogModelStatus = typeof CatalogModelStatus.Type
const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}` const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}`
const CostTier = Schema.Struct({ const CostTier = Schema.Struct({
input: Money.USDPerMillionTokens, input: Schema.Finite,
output: Money.USDPerMillionTokens, output: Schema.Finite,
cache_read: Schema.optional(Money.USDPerMillionTokens), cache_read: Schema.optional(Schema.Finite),
cache_write: Schema.optional(Money.USDPerMillionTokens), cache_write: Schema.optional(Schema.Finite),
tier: Schema.Struct({ tier: Schema.Struct({
type: Schema.Literal("context"), type: Schema.Literal("context"),
size: Schema.Finite, size: Schema.Finite,
@@ -30,17 +29,17 @@ const CostTier = Schema.Struct({
}) })
const Cost = Schema.Struct({ const Cost = Schema.Struct({
input: Money.USDPerMillionTokens, input: Schema.Finite,
output: Money.USDPerMillionTokens, output: Schema.Finite,
cache_read: Schema.optional(Money.USDPerMillionTokens), cache_read: Schema.optional(Schema.Finite),
cache_write: Schema.optional(Money.USDPerMillionTokens), cache_write: Schema.optional(Schema.Finite),
tiers: Schema.optional(Schema.Array(CostTier)), tiers: Schema.optional(Schema.Array(CostTier)),
context_over_200k: Schema.optional( context_over_200k: Schema.optional(
Schema.Struct({ Schema.Struct({
input: Money.USDPerMillionTokens, input: Schema.Finite,
output: Money.USDPerMillionTokens, output: Schema.Finite,
cache_read: Schema.optional(Money.USDPerMillionTokens), cache_read: Schema.optional(Schema.Finite),
cache_write: Schema.optional(Money.USDPerMillionTokens), cache_write: Schema.optional(Schema.Finite),
}), }),
), ),
}) })
+6 -13
View File
@@ -1,6 +1,6 @@
export * as PluginV2 from "./plugin" export * as PluginV2 from "./plugin"
import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin" import type { Plugin } from "@opencode-ai/plugin/v2/effect"
import { Event, ID, type Info } from "@opencode-ai/schema/plugin" import { Event, ID, type Info } from "@opencode-ai/schema/plugin"
import { makeLocationNode } from "./effect/app-node" import { makeLocationNode } from "./effect/app-node"
import { Context, Effect, Exit, Layer, Scope, Semaphore } from "effect" import { Context, Effect, Exit, Layer, Scope, Semaphore } from "effect"
@@ -18,7 +18,6 @@ import { SkillV2 } from "./skill"
import { State } from "./state" import { State } from "./state"
import { ToolRegistry } from "./tool/registry" import { ToolRegistry } from "./tool/registry"
import { ToolHooks } from "./tool/hooks" import { ToolHooks } from "./tool/hooks"
import { PluginHooks } from "./plugin/hooks"
export interface Interface { export interface Interface {
readonly activate: (plugins: readonly { readonly plugin: Plugin; readonly version?: string }[]) => Effect.Effect<void> readonly activate: (plugins: readonly { readonly plugin: Plugin; readonly version?: string }[]) => Effect.Effect<void>
@@ -58,13 +57,12 @@ const layer = Layer.effect(
generation.length === definitions.length && generation.length === definitions.length &&
generation.every( generation.every(
(plugin, index) => plugin.id === definitions[index]?.id && plugin.version === definitions[index]?.version, (plugin, index) => plugin.id === definitions[index]?.id && plugin.version === definitions[index]?.version,
) && )
definitions.every((definition) => active.has(definition.id))
) { ) {
return return
} }
generation = undefined generation = undefined
yield* State.batch( const exit = yield* State.batch(
Effect.gen(function* () { Effect.gen(function* () {
const scopes = Array.from(active.values()).toReversed() const scopes = Array.from(active.values()).toReversed()
active.clear() active.clear()
@@ -83,17 +81,13 @@ const layer = Layer.effect(
Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(child, exit) : Effect.void)), Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(child, exit) : Effect.void)),
Effect.exit, Effect.exit,
) )
if (Exit.isFailure(loaded)) { if (Exit.isFailure(loaded)) return loaded
yield* Effect.logWarning("failed to load plugin", {
"plugin.id": definition.id,
cause: loaded.cause,
})
continue
}
active.set(definition.id, child) active.set(definition.id, child)
} }
return Exit.void
}), }),
) )
if (Exit.isFailure(exit)) return yield* exit
generation = definitions.map((definition) => ({ generation = definitions.map((definition) => ({
id: definition.id, id: definition.id,
...(definition.version === undefined ? {} : { version: definition.version }), ...(definition.version === undefined ? {} : { version: definition.version }),
@@ -137,7 +131,6 @@ export const node = makeLocationNode({
SkillV2.node, SkillV2.node,
ToolRegistry.toolsNode, ToolRegistry.toolsNode,
ToolHooks.node, ToolHooks.node,
PluginHooks.node,
PluginRuntime.node, PluginRuntime.node,
], ],
}) })
-7
View File
@@ -125,7 +125,6 @@ export const Plugin = define({
yield* ctx.agent.transform((draft) => { yield* ctx.agent.transform((draft) => {
draft.update(AgentV2.defaultID, (item) => { draft.update(AgentV2.defaultID, (item) => {
item.name = AgentV2.Name.make("Build")
item.description = "The default agent. Executes tools based on configured permissions." item.description = "The default agent. Executes tools based on configured permissions."
item.mode = "primary" item.mode = "primary"
item.permissions.push( item.permissions.push(
@@ -137,7 +136,6 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("plan"), (item) => { draft.update(AgentV2.ID.make("plan"), (item) => {
item.name = AgentV2.Name.make("Plan")
item.description = "Plan mode. Disallows all edit tools." item.description = "Plan mode. Disallows all edit tools."
item.mode = "primary" item.mode = "primary"
item.permissions.push( item.permissions.push(
@@ -157,7 +155,6 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("general"), (item) => { draft.update(AgentV2.ID.make("general"), (item) => {
item.name = AgentV2.Name.make("General")
item.description = item.description =
"General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel." "General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel."
item.mode = "subagent" item.mode = "subagent"
@@ -170,7 +167,6 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("explore"), (item) => { draft.update(AgentV2.ID.make("explore"), (item) => {
item.name = AgentV2.Name.make("Explore")
item.description = item.description =
'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.' 'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.'
item.system = PROMPT_EXPLORE item.system = PROMPT_EXPLORE
@@ -193,7 +189,6 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("compaction"), (item) => { draft.update(AgentV2.ID.make("compaction"), (item) => {
item.name = AgentV2.Name.make("Compaction")
item.mode = "primary" item.mode = "primary"
item.hidden = true item.hidden = true
item.system = PROMPT_COMPACTION item.system = PROMPT_COMPACTION
@@ -201,7 +196,6 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("title"), (item) => { draft.update(AgentV2.ID.make("title"), (item) => {
item.name = AgentV2.Name.make("Title")
item.mode = "primary" item.mode = "primary"
item.hidden = true item.hidden = true
item.system = PROMPT_TITLE item.system = PROMPT_TITLE
@@ -209,7 +203,6 @@ export const Plugin = define({
}) })
draft.update(AgentV2.ID.make("summary"), (item) => { draft.update(AgentV2.ID.make("summary"), (item) => {
item.name = AgentV2.Name.make("Summary")
item.mode = "primary" item.mode = "primary"
item.hidden = true item.hidden = true
item.system = PROMPT_SUMMARY item.system = PROMPT_SUMMARY
-67
View File
@@ -1,67 +0,0 @@
export * as PluginHooks from "./hooks"
import type { AISDKHooks } from "@opencode-ai/plugin/v2/effect/aisdk"
import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
import type { ToolHooks } from "@opencode-ai/plugin/v2/effect/tool"
import { Context, Effect, Layer, Scope } from "effect"
import { makeLocationNode } from "../effect/app-node"
import { State } from "../state"
export interface Domains {
readonly aisdk: AISDKHooks
readonly session: SessionHooks
readonly tool: ToolHooks
}
type Callback<Event> = (event: Event) => Effect.Effect<void>
export interface Interface {
readonly register: <Domain extends keyof Domains, Name extends keyof Domains[Domain]>(
domain: Domain,
name: Name,
callback: Callback<Domains[Domain][Name]>,
) => Effect.Effect<State.Registration, never, Scope.Scope>
readonly trigger: <Domain extends keyof Domains, Name extends keyof Domains[Domain]>(
domain: Domain,
name: Name,
event: Domains[Domain][Name],
) => Effect.Effect<Domains[Domain][Name]>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/PluginHooks") {}
const layer = Layer.effect(
Service,
Effect.gen(function* () {
const callbacks = new Map<string, Function[]>()
const key = (domain: keyof Domains, name: PropertyKey) => `${domain}.${String(name)}`
const register: Interface["register"] = Effect.fn("PluginHooks.register")(function* (domain, name, callback) {
const scope = yield* Scope.Scope
const id = key(domain, name)
let active = true
callbacks.set(id, [...(callbacks.get(id) ?? []), callback])
const dispose = Effect.sync(() => {
if (!active) return
active = false
const next = (callbacks.get(id) ?? []).filter((item) => item !== callback)
if (next.length === 0) callbacks.delete(id)
else callbacks.set(id, next)
})
yield* Scope.addFinalizer(scope, dispose)
return { dispose }
})
const trigger: Interface["trigger"] = Effect.fn("PluginHooks.trigger")(function* (domain, name, event) {
for (const callback of callbacks.get(key(domain, name)) ?? []) {
const result: Effect.Effect<void> = Reflect.apply(callback, undefined, [event])
yield* result
}
return event
})
return Service.of({ register, trigger })
}),
)
export const node = makeLocationNode({ service: Service, layer, deps: [] })
+68 -74
View File
@@ -1,6 +1,6 @@
export * as PluginHost from "./host" export * as PluginHost from "./host"
import type { Plugin } from "@opencode-ai/plugin/v2/effect" import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
import { EventManifest } from "@opencode-ai/schema/event-manifest" import { EventManifest } from "@opencode-ai/schema/event-manifest"
import { Effect, Schema, Stream } from "effect" import { Effect, Schema, Stream } from "effect"
import { AgentV2 } from "../agent" import { AgentV2 } from "../agent"
@@ -22,7 +22,6 @@ import { Tool } from "../tool/tool"
import { Tools } from "../tool/tools" import { Tools } from "../tool/tools"
import { ToolHooks } from "../tool/hooks" import { ToolHooks } from "../tool/hooks"
import { WorkspaceV2 } from "../workspace" import { WorkspaceV2 } from "../workspace"
import { PluginHooks } from "./hooks"
const mutable = <T>(value: T) => value as DeepMutable<T> const mutable = <T>(value: T) => value as DeepMutable<T>
export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) { export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) {
@@ -37,7 +36,6 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
const skill = yield* SkillV2.Service const skill = yield* SkillV2.Service
const tools = yield* Tools.Service const tools = yield* Tools.Service
const toolHooks = yield* ToolHooks.Service const toolHooks = yield* ToolHooks.Service
const hooks = yield* PluginHooks.Service
const runtime = yield* PluginRuntime.Service const runtime = yield* PluginRuntime.Service
const locationInfo = () => const locationInfo = () =>
new Location.Info({ new Location.Info({
@@ -45,7 +43,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
workspaceID: location.workspaceID, workspaceID: location.workspaceID,
project: location.project, project: location.project,
}) })
const locationRef = (input?: Parameters<Plugin.Context["agent"]["list"]>[0]) => const locationRef = (input?: Parameters<PluginContext["agent"]["list"]>[0]) =>
input?.location === undefined input?.location === undefined
? undefined ? undefined
: Location.Ref.make({ : Location.Ref.make({
@@ -81,32 +79,32 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
}), }),
}, },
aisdk: { aisdk: {
hook: (name, callback) => { sdk: (callback) =>
if (name === "sdk") { aisdk.hook.sdk((event) => {
return aisdk.hook.sdk((event) => {
const output = {
model: mutable(event.model),
package: event.package,
options: event.options,
sdk: event.sdk,
}
return Reflect.apply(callback, undefined, [output]).pipe(
Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))),
)
})
}
return aisdk.hook.language((event) => {
const output = { const output = {
model: mutable(event.model), model: mutable(event.model),
package: event.package,
options: event.options, options: event.options,
sdk: event.sdk, sdk: event.sdk,
}
const result = callback(output)
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))),
)
}),
language: (callback) =>
aisdk.hook.language((event) => {
const output = {
model: mutable(event.model),
sdk: event.sdk,
options: event.options,
language: event.language, language: event.language,
} }
return Reflect.apply(callback, undefined, [output]).pipe( const result = callback(output)
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.tap(() => Effect.sync(() => (event.language = output.language))), Effect.tap(() => Effect.sync(() => (event.language = output.language))),
) )
}) }),
},
}, },
catalog: { catalog: {
provider: { provider: {
@@ -166,29 +164,25 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
integration: { integration: {
list: () => response(integration.list()), list: () => response(integration.list()),
get: (input) => response(integration.get(Integration.ID.make(input.integrationID))), get: (input) => response(integration.get(Integration.ID.make(input.integrationID))),
connect: { connectKey: (input) =>
key: (input) => integration.connection.key({
integration.connection.key({ integrationID: Integration.ID.make(input.integrationID),
key: input.key,
label: input.label,
}),
connectOauth: (input) =>
response(
integration.connection.oauth({
integrationID: Integration.ID.make(input.integrationID), integrationID: Integration.ID.make(input.integrationID),
key: input.key, methodID: Integration.MethodID.make(input.methodID),
inputs: input.inputs,
label: input.label, label: input.label,
}), }),
oauth: (input) => ),
response( attemptStatus: (input) => response(integration.attempt.status(Integration.AttemptID.make(input.attemptID))),
integration.connection.oauth({ attemptComplete: (input) =>
integrationID: Integration.ID.make(input.integrationID), integration.attempt.complete({ attemptID: Integration.AttemptID.make(input.attemptID), code: input.code }),
methodID: Integration.MethodID.make(input.methodID), attemptCancel: (input) => integration.attempt.cancel(Integration.AttemptID.make(input.attemptID)),
inputs: input.inputs,
label: input.label,
}),
),
},
attempt: {
status: (input) => response(integration.attempt.status(Integration.AttemptID.make(input.attemptID))),
complete: (input) =>
integration.attempt.complete({ attemptID: Integration.AttemptID.make(input.attemptID), code: input.code }),
cancel: (input) => integration.attempt.cancel(Integration.AttemptID.make(input.attemptID)),
},
reload: integration.reload, reload: integration.reload,
connection: { connection: {
active: (id) => integration.connection.active(Integration.ID.make(id)), active: (id) => integration.connection.active(Integration.ID.make(id)),
@@ -323,12 +317,11 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
registrations, registrations,
(registration) => tools.register({ [registration.name]: registration.tool }, registration.options), (registration) => tools.register({ [registration.name]: registration.tool }, registration.options),
{ discard: true }, { discard: true },
).pipe(Effect.orDie) )
return { dispose: Effect.void }
}), }),
hook: (name, callback) => { execute: {
if (name === "execute.before") { before: (callback) =>
return toolHooks.hook.before((event) => { toolHooks.hook.before((event) => {
const output = { const output = {
tool: event.tool, tool: event.tool,
sessionID: event.sessionID, sessionID: event.sessionID,
@@ -337,37 +330,38 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
toolCallID: event.toolCallID, toolCallID: event.toolCallID,
input: event.input, input: event.input,
} }
return Reflect.apply(callback, undefined, [output]).pipe( const result = callback(output)
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.tap(() => Effect.sync(() => (event.input = output.input))), Effect.tap(() => Effect.sync(() => (event.input = output.input))),
) )
}) }),
} after: (callback) =>
return toolHooks.hook.after((event) => { toolHooks.hook.after((event) => {
const output = { const output = {
tool: event.tool, tool: event.tool,
sessionID: event.sessionID, sessionID: event.sessionID,
agent: event.agent, agent: event.agent,
assistantMessageID: event.assistantMessageID, assistantMessageID: event.assistantMessageID,
toolCallID: event.toolCallID, toolCallID: event.toolCallID,
input: event.input, input: event.input,
result: event.result, result: event.result,
output: event.output, output: event.output,
outputPaths: event.outputPaths, outputPaths: event.outputPaths,
} }
return Reflect.apply(callback, undefined, [output]).pipe( const result = callback(output)
Effect.tap(() => return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.sync(() => { Effect.tap(() =>
event.result = output.result Effect.sync(() => {
event.output = output.output event.result = output.result
event.outputPaths = output.outputPaths event.output = output.output
}), event.outputPaths = output.outputPaths
), }),
) ),
}) )
}),
}, },
}, },
session: { session: {
hook: (name, callback) => hooks.register("session", name, callback),
create: (input) => create: (input) =>
runtime.session.create({ runtime.session.create({
id: input?.id, id: input?.id,
@@ -381,5 +375,5 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
command: runtime.session.command, command: runtime.session.command,
interrupt: (input) => runtime.session.interrupt(input.sessionID), interrupt: (input) => runtime.session.interrupt(input.sessionID),
}, },
} satisfies Plugin.Context } satisfies PluginContext
}) })
+3 -3
View File
@@ -1,6 +1,6 @@
export * as PluginInternal from "./internal" export * as PluginInternal from "./internal"
import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin" import type { Plugin } from "@opencode-ai/plugin/v2/effect"
import { Context, Effect, Scope } from "effect" import { Context, Effect, Scope } from "effect"
import { HttpClient } from "effect/unstable/http" import { HttpClient } from "effect/unstable/http"
import { AgentV2 } from "../agent" import { AgentV2 } from "../agent"
@@ -31,7 +31,7 @@ import { SessionInstructions } from "../session/instructions"
import { SessionTodo } from "../session/todo" import { SessionTodo } from "../session/todo"
import { Shell } from "../shell" import { Shell } from "../shell"
import { SkillV2 } from "../skill" import { SkillV2 } from "../skill"
import { PatchTool } from "../tool/patch" import { ApplyPatchTool } from "../tool/apply-patch"
import { EditTool } from "../tool/edit" import { EditTool } from "../tool/edit"
import { GlobTool } from "../tool/glob" import { GlobTool } from "../tool/glob"
import { GrepTool } from "../tool/grep" import { GrepTool } from "../tool/grep"
@@ -127,7 +127,7 @@ const pre = [
SkillPlugin.Plugin, SkillPlugin.Plugin,
ModelsDevPlugin, ModelsDevPlugin,
...ProviderPlugins, ...ProviderPlugins,
PatchTool.Plugin, ApplyPatchTool.Plugin,
EditTool.Plugin, EditTool.Plugin,
GlobTool.Plugin, GlobTool.Plugin,
GrepTool.Plugin, GrepTool.Plugin,
+20 -34
View File
@@ -1,6 +1,5 @@
import { define } from "@opencode-ai/plugin/v2/effect/plugin" import { define } from "@opencode-ai/plugin/v2/effect/plugin"
import { Money } from "@opencode-ai/schema/money" import type { ModelV2Info } from "@opencode-ai/sdk/v2/types"
import type { ModelInfo } from "@opencode-ai/sdk/v2/types"
import { Effect, Stream } from "effect" import { Effect, Stream } from "effect"
import { EventV2 } from "../event" import { EventV2 } from "../event"
import { ModelV2 } from "../model" import { ModelV2 } from "../model"
@@ -12,13 +11,13 @@ function released(date: string) {
return Number.isFinite(time) ? time : 0 return Number.isFinite(time) ? time : 0
} }
function cost(input: ModelsDev.Model["cost"]): ModelInfo["cost"] { function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] {
const base = { const base = {
input: input?.input ?? Money.USDPerMillionTokens.zero, input: input?.input ?? 0,
output: input?.output ?? Money.USDPerMillionTokens.zero, output: input?.output ?? 0,
cache: { cache: {
read: input?.cache_read ?? Money.USDPerMillionTokens.zero, read: input?.cache_read ?? 0,
write: input?.cache_write ?? Money.USDPerMillionTokens.zero, write: input?.cache_write ?? 0,
}, },
} }
return [ return [
@@ -28,8 +27,8 @@ function cost(input: ModelsDev.Model["cost"]): ModelInfo["cost"] {
input: item.input, input: item.input,
output: item.output, output: item.output,
cache: { cache: {
read: item.cache_read ?? Money.USDPerMillionTokens.zero, read: item.cache_read ?? 0,
write: item.cache_write ?? Money.USDPerMillionTokens.zero, write: item.cache_write ?? 0,
}, },
})) ?? []), })) ?? []),
...(input?.context_over_200k ...(input?.context_over_200k
@@ -42,8 +41,8 @@ function cost(input: ModelsDev.Model["cost"]): ModelInfo["cost"] {
input: input.context_over_200k.input, input: input.context_over_200k.input,
output: input.context_over_200k.output, output: input.context_over_200k.output,
cache: { cache: {
read: input.context_over_200k.cache_read ?? Money.USDPerMillionTokens.zero, read: input.context_over_200k.cache_read ?? 0,
write: input.context_over_200k.cache_write ?? Money.USDPerMillionTokens.zero, write: input.context_over_200k.cache_write ?? 0,
}, },
}, },
] ]
@@ -51,13 +50,13 @@ function cost(input: ModelsDev.Model["cost"]): ModelInfo["cost"] {
] ]
} }
function mergeCost(base: ModelInfo["cost"], override: ModelsDev.Model["cost"] | undefined) { function mergeCost(base: ModelV2Info["cost"], override: ModelsDev.Model["cost"] | undefined) {
if (!override) return base if (!override) return base
const next = cost(override) const next = cost(override)
const [baseDefault, ...baseTiers] = base const [baseDefault, ...baseTiers] = base
const [nextDefault, ...nextTiers] = next const [nextDefault, ...nextTiers] = next
const tierKey = (item: ModelInfo["cost"][number]) => `${item.tier?.type ?? "base"}:${item.tier?.size ?? 0}` const tierKey = (item: ModelV2Info["cost"][number]) => `${item.tier?.type ?? "base"}:${item.tier?.size ?? 0}`
const merge = (left: ModelInfo["cost"][number], right: ModelInfo["cost"][number]) => ({ const merge = (left: ModelV2Info["cost"][number], right: ModelV2Info["cost"][number]) => ({
...left, ...left,
...right, ...right,
tier: right.tier ?? left.tier, tier: right.tier ?? left.tier,
@@ -68,25 +67,12 @@ function mergeCost(base: ModelInfo["cost"], override: ModelsDev.Model["cost"] |
const current = tiers.get(tierKey(item)) const current = tiers.get(tierKey(item))
tiers.set(tierKey(item), current ? merge(current, item) : item) tiers.set(tierKey(item), current ? merge(current, item) : item)
} }
return [ return [merge(baseDefault ?? { input: 0, output: 0, cache: { read: 0, write: 0 } }, nextDefault), ...tiers.values()]
merge(
baseDefault ?? {
input: Money.USDPerMillionTokens.zero,
output: Money.USDPerMillionTokens.zero,
cache: {
read: Money.USDPerMillionTokens.zero,
write: Money.USDPerMillionTokens.zero,
},
},
nextDefault,
),
...tiers.values(),
]
} }
const OPENAI_INCLUDE_ENCRYPTED_REASONING = ["reasoning.encrypted_content"] const OPENAI_INCLUDE_ENCRYPTED_REASONING = ["reasoning.encrypted_content"]
function reasoningVariants(provider: ModelsDev.Provider, model: ModelsDev.Model): NonNullable<ModelInfo["variants"]> { function reasoningVariants(provider: ModelsDev.Provider, model: ModelsDev.Model): NonNullable<ModelV2Info["variants"]> {
const npm = model.provider?.npm ?? provider.npm const npm = model.provider?.npm ?? provider.npm
const options = model.reasoning_options ?? [] const options = model.reasoning_options ?? []
const effort = options.find((option) => option.type === "effort") const effort = options.find((option) => option.type === "effort")
@@ -131,7 +117,7 @@ function settingsForEffort(npm: string | undefined, effort: string): ProviderV2.
function budgetVariants( function budgetVariants(
npm: string | undefined, npm: string | undefined,
option: Extract<NonNullable<ModelsDev.Model["reasoning_options"]>[number], { type: "budget_tokens" }>, option: Extract<NonNullable<ModelsDev.Model["reasoning_options"]>[number], { type: "budget_tokens" }>,
): NonNullable<ModelInfo["variants"]> { ): NonNullable<ModelV2Info["variants"]> {
const max = option.max const max = option.max
const high = const high =
option.max === undefined option.max === undefined
@@ -160,7 +146,7 @@ function modeName(model: ModelsDev.Model, mode: string) {
return `${model.name} ${mode.charAt(0).toUpperCase()}${mode.slice(1)}` return `${model.name} ${mode.charAt(0).toUpperCase()}${mode.slice(1)}`
} }
function mergeVariants(model: ModelInfo, next: NonNullable<ModelInfo["variants"]>) { function mergeVariants(model: ModelV2Info, next: NonNullable<ModelV2Info["variants"]>) {
const variants = model.variants ?? [] const variants = model.variants ?? []
const existing = new Map(variants.map((variant) => [variant.id, variant])) const existing = new Map(variants.map((variant) => [variant.id, variant]))
const nextIDs = new Set(next.map((variant) => variant.id)) const nextIDs = new Set(next.map((variant) => variant.id))
@@ -171,13 +157,13 @@ function mergeVariants(model: ModelInfo, next: NonNullable<ModelInfo["variants"]
} }
function applyModel( function applyModel(
draft: ModelInfo, draft: ModelV2Info,
model: ModelsDev.Model, model: ModelsDev.Model,
input: { input: {
readonly name?: string readonly name?: string
readonly cost?: ModelInfo["cost"] readonly cost?: ModelV2Info["cost"]
readonly request?: NonNullable<NonNullable<ModelsDev.Model["experimental"]>["modes"]>[string]["provider"] readonly request?: NonNullable<NonNullable<ModelsDev.Model["experimental"]>["modes"]>[string]["provider"]
readonly variants?: NonNullable<ModelInfo["variants"]> readonly variants?: NonNullable<ModelV2Info["variants"]>
} = {}, } = {},
) { ) {
draft.name = input.name ?? model.name draft.name = input.name ?? model.name
+14 -24
View File
@@ -1,12 +1,11 @@
export * as PluginPromise from "./promise" export * as PluginPromise from "./promise"
import { Plugin } from "@opencode-ai/plugin/v2/effect" import { define } from "@opencode-ai/plugin/v2/effect"
import type { Plugin, PluginContext } from "@opencode-ai/plugin/v2/promise"
import { Effect, Scope, Stream } from "effect" import { Effect, Scope, Stream } from "effect"
type HostRegistration = { readonly dispose: Effect.Effect<void> } type HostRegistration = { readonly dispose: Effect.Effect<void> }
type Registration = { readonly dispose: () => Promise<void> } type Registration = { readonly dispose: () => Promise<void> }
type PromisePlugin = import("@opencode-ai/plugin/v2/plugin").Plugin
type PromisePluginContext = import("@opencode-ai/plugin/v2/plugin").Context
/** /**
* Adapts a Promise plugin into an Effect plugin so the existing Effect-only * Adapts a Promise plugin into an Effect plugin so the existing Effect-only
@@ -17,8 +16,8 @@ type PromisePluginContext = import("@opencode-ai/plugin/v2/plugin").Context
* preserves boot-time batching, so Promise-plugin transforms still coalesce * preserves boot-time batching, so Promise-plugin transforms still coalesce
* into one reload per domain. * into one reload per domain.
*/ */
export function fromPromise(plugin: PromisePlugin) { export function fromPromise(plugin: Plugin) {
return Plugin.define({ return define({
id: plugin.id, id: plugin.id,
effect: (host) => effect: (host) =>
Effect.gen(function* () { Effect.gen(function* () {
@@ -44,7 +43,7 @@ export function fromPromise(plugin: PromisePlugin) {
}), }),
) )
const context2: PromisePluginContext = { const context2: PluginContext = {
options: host.options, options: host.options,
agent: { agent: {
list: (input) => run(host.agent.list(input)), list: (input) => run(host.agent.list(input)),
@@ -52,8 +51,10 @@ export function fromPromise(plugin: PromisePlugin) {
reload: () => run(host.agent.reload()), reload: () => run(host.agent.reload()),
}, },
aisdk: { aisdk: {
hook: (name, callback) => sdk: (callback) =>
register(host.aisdk.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))), register(host.aisdk.sdk((event) => Effect.promise(() => Promise.resolve(callback(event))))),
language: (callback) =>
register(host.aisdk.language((event) => Effect.promise(() => Promise.resolve(callback(event))))),
}, },
catalog: { catalog: {
provider: { provider: {
@@ -78,15 +79,11 @@ export function fromPromise(plugin: PromisePlugin) {
integration: { integration: {
list: (input) => run(host.integration.list(input)), list: (input) => run(host.integration.list(input)),
get: (input) => run(host.integration.get(input)), get: (input) => run(host.integration.get(input)),
connect: { connectKey: (input) => run(host.integration.connectKey(input)),
key: (input) => run(host.integration.connect.key(input)), connectOauth: (input) => run(host.integration.connectOauth(input)),
oauth: (input) => run(host.integration.connect.oauth(input)), attemptStatus: (input) => run(host.integration.attemptStatus(input)),
}, attemptComplete: (input) => run(host.integration.attemptComplete(input)),
attempt: { attemptCancel: (input) => run(host.integration.attemptCancel(input)),
status: (input) => run(host.integration.attempt.status(input)),
complete: (input) => run(host.integration.attempt.complete(input)),
cancel: (input) => run(host.integration.attempt.cancel(input)),
},
transform: transform(host.integration), transform: transform(host.integration),
reload: () => run(host.integration.reload()), reload: () => run(host.integration.reload()),
connection: { connection: {
@@ -107,19 +104,12 @@ export function fromPromise(plugin: PromisePlugin) {
transform: transform(host.skill), transform: transform(host.skill),
reload: () => run(host.skill.reload()), reload: () => run(host.skill.reload()),
}, },
tool: {
transform: transform(host.tool),
hook: (name, callback) =>
register(host.tool.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
},
session: { session: {
create: (input) => run(host.session.create(input)), create: (input) => run(host.session.create(input)),
get: (input) => run(host.session.get(input)), get: (input) => run(host.session.get(input)),
prompt: (input) => run(host.session.prompt(input)), prompt: (input) => run(host.session.prompt(input)),
command: (input) => run(host.session.command(input)), command: (input) => run(host.session.command(input)),
interrupt: (input) => run(host.session.interrupt(input)), interrupt: (input) => run(host.session.interrupt(input)),
hook: (name, callback) =>
register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
}, },
} }
+1 -2
View File
@@ -4,8 +4,7 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const AlibabaPlugin = define({ export const AlibabaPlugin = define({
id: "opencode.provider.alibaba", id: "opencode.provider.alibaba",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/alibaba") return if (evt.package !== "@ai-sdk/alibaba") return
const mod = yield* Effect.promise(() => import("@ai-sdk/alibaba")) const mod = yield* Effect.promise(() => import("@ai-sdk/alibaba"))
@@ -75,8 +75,7 @@ export const AmazonBedrockPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (!["@ai-sdk/amazon-bedrock", "@ai-sdk/amazon-bedrock/mantle"].includes(evt.package)) return if (!["@ai-sdk/amazon-bedrock", "@ai-sdk/amazon-bedrock/mantle"].includes(evt.package)) return
const options = { ...evt.options } const options = { ...evt.options }
@@ -109,8 +108,7 @@ export const AmazonBedrockPlugin = define({
evt.sdk = mod.createAmazonBedrock(options) evt.sdk = mod.createAmazonBedrock(options)
}), }),
) )
yield* ctx.aisdk.hook( yield* ctx.aisdk.language(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return
if ( if (
@@ -17,8 +17,7 @@ export const AnthropicPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/anthropic") return if (evt.package !== "@ai-sdk/anthropic") return
const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic")) const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic"))
+3 -6
View File
@@ -26,8 +26,7 @@ export const AzurePlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/azure") return if (evt.package !== "@ai-sdk/azure") return
if (evt.model.providerID === ProviderV2.ID.azure) { if (evt.model.providerID === ProviderV2.ID.azure) {
@@ -45,8 +44,7 @@ export const AzurePlugin = define({
evt.sdk = mod.createAzure(evt.options) evt.sdk = mod.createAzure(evt.options)
}), }),
) )
yield* ctx.aisdk.hook( yield* ctx.aisdk.language(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.azure) return if (evt.model.providerID !== ProviderV2.ID.azure) return
evt.language = selectLanguage( evt.language = selectLanguage(
@@ -77,8 +75,7 @@ export const AzureCognitiveServicesPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.language(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return
evt.language = selectLanguage( evt.language = selectLanguage(
@@ -14,8 +14,7 @@ export const CerebrasPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/cerebras") return if (evt.package !== "@ai-sdk/cerebras") return
const mod = yield* Effect.promise(() => import("@ai-sdk/cerebras")) const mod = yield* Effect.promise(() => import("@ai-sdk/cerebras"))
@@ -6,8 +6,7 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const CloudflareAIGatewayPlugin = define({ export const CloudflareAIGatewayPlugin = define({
id: "opencode.provider.cloudflare-ai-gateway", id: "opencode.provider.cloudflare-ai-gateway",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "ai-gateway-provider") return if (evt.package !== "ai-gateway-provider") return
if (evt.options.baseURL) return if (evt.options.baseURL) return
@@ -19,8 +19,7 @@ export const CloudflareWorkersAIPlugin = define({
if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) } if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) }
}) })
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== providerID) return if (evt.model.providerID !== providerID) return
if (evt.package !== "@ai-sdk/openai-compatible") return if (evt.package !== "@ai-sdk/openai-compatible") return
@@ -36,8 +35,7 @@ export const CloudflareWorkersAIPlugin = define({
) )
}), }),
) )
yield* ctx.aisdk.hook( yield* ctx.aisdk.language(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== providerID) return if (evt.model.providerID !== providerID) return
evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id) evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id)
+1 -2
View File
@@ -4,8 +4,7 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const CoherePlugin = define({ export const CoherePlugin = define({
id: "opencode.provider.cohere", id: "opencode.provider.cohere",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/cohere") return if (evt.package !== "@ai-sdk/cohere") return
const mod = yield* Effect.promise(() => import("@ai-sdk/cohere")) const mod = yield* Effect.promise(() => import("@ai-sdk/cohere"))
@@ -4,8 +4,7 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const DeepInfraPlugin = define({ export const DeepInfraPlugin = define({
id: "opencode.provider.deepinfra", id: "opencode.provider.deepinfra",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/deepinfra") return if (evt.package !== "@ai-sdk/deepinfra") return
const mod = yield* Effect.promise(() => import("@ai-sdk/deepinfra")) const mod = yield* Effect.promise(() => import("@ai-sdk/deepinfra"))
+1 -2
View File
@@ -7,8 +7,7 @@ export const DynamicProviderPlugin = define({
id: "opencode.provider.dynamic", id: "opencode.provider.dynamic",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
const npm = yield* Npm.Service const npm = yield* Npm.Service
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.sdk) return if (evt.sdk) return
+1 -2
View File
@@ -4,8 +4,7 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const GatewayPlugin = define({ export const GatewayPlugin = define({
id: "opencode.provider.gateway", id: "opencode.provider.gateway",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/gateway") return if (evt.package !== "@ai-sdk/gateway") return
const mod = yield* Effect.promise(() => import("@ai-sdk/gateway")) const mod = yield* Effect.promise(() => import("@ai-sdk/gateway"))
@@ -23,16 +23,14 @@ export const GithubCopilotPlugin = define({
model.enabled = false model.enabled = false
}) })
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/github-copilot") return if (evt.package !== "@ai-sdk/github-copilot") return
const mod = yield* Effect.promise(() => import("../../github-copilot/copilot-provider")) const mod = yield* Effect.promise(() => import("../../github-copilot/copilot-provider"))
evt.sdk = mod.createOpenaiCompatible(evt.options) evt.sdk = mod.createOpenaiCompatible(evt.options)
}), }),
) )
yield* ctx.aisdk.hook( yield* ctx.aisdk.language(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return
if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) { if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) {
+2 -4
View File
@@ -7,8 +7,7 @@ import { ProviderV2 } from "../../provider"
export const GitLabPlugin = define({ export const GitLabPlugin = define({
id: "opencode.provider.gitlab", id: "opencode.provider.gitlab",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "gitlab-ai-provider") return if (evt.package !== "gitlab-ai-provider") return
const mod = yield* Effect.promise(() => import("gitlab-ai-provider")) const mod = yield* Effect.promise(() => import("gitlab-ai-provider"))
@@ -32,8 +31,7 @@ export const GitLabPlugin = define({
}) })
}), }),
) )
yield* ctx.aisdk.hook( yield* ctx.aisdk.language(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.gitlab) return if (evt.model.providerID !== ProviderV2.ID.gitlab) return
const featureFlags = const featureFlags =
@@ -85,8 +85,7 @@ export const GoogleVertexPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID === ProviderV2.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) { if (evt.model.providerID === ProviderV2.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) {
evt.options.fetch = authFetch(evt.options.fetch) evt.options.fetch = authFetch(evt.options.fetch)
@@ -105,8 +104,7 @@ export const GoogleVertexPlugin = define({
}) })
}), }),
) )
yield* ctx.aisdk.hook( yield* ctx.aisdk.language(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.googleVertex) return if (evt.model.providerID !== ProviderV2.ID.googleVertex) return
evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim()) evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
@@ -137,8 +135,7 @@ export const GoogleVertexAnthropicPlugin = define({
}) })
} }
}) })
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/google-vertex/anthropic") return if (evt.package !== "@ai-sdk/google-vertex/anthropic") return
const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic")) const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic"))
@@ -164,8 +161,7 @@ export const GoogleVertexAnthropicPlugin = define({
}) })
}), }),
) )
yield* ctx.aisdk.hook( yield* ctx.aisdk.language(
"language",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return
evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim()) evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
+1 -2
View File
@@ -4,8 +4,7 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const GooglePlugin = define({ export const GooglePlugin = define({
id: "opencode.provider.google", id: "opencode.provider.google",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/google") return if (evt.package !== "@ai-sdk/google") return
const mod = yield* Effect.promise(() => import("@ai-sdk/google")) const mod = yield* Effect.promise(() => import("@ai-sdk/google"))
+1 -2
View File
@@ -4,8 +4,7 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
export const GroqPlugin = define({ export const GroqPlugin = define({
id: "opencode.provider.groq", id: "opencode.provider.groq",
effect: Effect.fn(function* (ctx) { effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook( yield* ctx.aisdk.sdk(
"sdk",
Effect.fn(function* (evt) { Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/groq") return if (evt.package !== "@ai-sdk/groq") return
const mod = yield* Effect.promise(() => import("@ai-sdk/groq")) const mod = yield* Effect.promise(() => import("@ai-sdk/groq"))

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