Compare commits

..

1 Commits

Author SHA1 Message Date
Aiden Cline c1faad5d62 feat(codemode): support JSON callbacks 2026-07-07 11:55:52 -05:00
22 changed files with 515 additions and 598 deletions
+5 -116
View File
@@ -873,7 +873,6 @@
"name": "@opencode-ai/simulation",
"version": "1.17.13",
"dependencies": {
"@napi-rs/canvas": "1.0.2",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/llm": "workspace:*",
"@opentui/core": "catalog:",
@@ -1612,11 +1611,11 @@
"@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=="],
@@ -1984,30 +1983,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=="],
"@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=="],
"@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
@@ -2302,7 +2277,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-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=="],
@@ -2594,37 +2569,7 @@
"@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/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=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@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 +3111,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-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/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
@@ -3240,12 +3183,6 @@
"@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=="],
"@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 +4987,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=="],
"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=="],
"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 +5401,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=="],
"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=="],
"rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
@@ -6088,12 +6021,8 @@
"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-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=="],
"xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
@@ -6690,10 +6619,6 @@
"@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/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
@@ -6714,8 +6639,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=="],
"@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=="],
"@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 +6747,6 @@
"@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/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 +7043,6 @@
"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-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@@ -7794,10 +7707,6 @@
"@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/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
@@ -7806,8 +7715,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=="],
"@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/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
@@ -7878,22 +7785,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=="],
"@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/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
@@ -8460,8 +8351,6 @@
"@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/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
Executable → Regular
View File
+9
View File
@@ -7,6 +7,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Global } from "@opencode-ai/core/global"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { AppProcess } from "@opencode-ai/core/process"
import { Flock } from "@opencode-ai/core/util/flock"
import { start } from "@opencode-ai/server/process"
import { randomBytes, randomUUID } from "node:crypto"
import path from "node:path"
@@ -36,6 +37,14 @@ const processEffect = Effect.fnUntraced(function* (options: Options) {
if (options.mode === "service") yield* Effect.sync(() => process.chdir(Global.Path.home))
return yield* Effect.scoped(
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
// Keep the lease credential out of the environment inherited by tools.
if (options.mode === "stdio") {
+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.
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
@@ -237,7 +237,7 @@ A host cannot define its own `$codemode` top-level namespace.
CodeMode executes a deliberately bounded JavaScript subset. It supports:
- Plain data literals, property access, assignment, property deletion, destructuring, and sequence expressions (the comma operator, evaluated left to right with the final value returned).
- Plain data literals, property access, assignment, destructuring, and sequence expressions (the comma operator, evaluated left to right with the final value returned).
- `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.
- Optional chaining, nullish coalescing, templates, spread (arrays, strings, Maps, Sets), and `try`/`catch`.
+1 -2
View File
@@ -153,8 +153,7 @@ current omissions to implement, not intentional product boundaries.
shims. Consider `Promise.any` in the same pass.
- [ ] Support async iteration and `for await...of`. Define behavior first for the runtime's supported promise and
collection values, then extend it to bounded host streams when a stream boundary exists.
- [ ] Support callback-bearing standard-library variants that models commonly generate: the mapper argument to
`Array.from(...)` and replacers for `JSON.stringify(...)`, including Effect-aware callbacks where needed.
- [ ] Support the mapper argument to `Array.from(...)`, including Effect-aware callbacks.
- [ ] 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
composition methods, and `Array.prototype.toSpliced`.
@@ -30,6 +30,7 @@ export type Binding = {
export type StatementResult =
| { kind: "none" }
| { kind: "value"; value: unknown }
| { kind: "return"; value: unknown }
| { kind: "break" }
| { kind: "continue" }
+178 -42
View File
@@ -5,6 +5,7 @@ import {
copyIn,
copyOut,
isBlockedMember,
MAX_VALUE_DEPTH,
ToolReference,
ToolRuntime,
ToolRuntimeError,
@@ -606,6 +607,7 @@ class Interpreter<R> {
// ToolRuntime.make like invokeTool: the interpreter never holds the tree itself.
private readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>
private readonly logs: Array<string>
private lastValue: unknown
// Caps how many eagerly forked tool calls run at once (the parallel-call concurrency cap).
private readonly callPermits: Semaphore.Semaphore
// Fiber-backed promises whose settlement no program construct has observed yet. Successful
@@ -624,6 +626,7 @@ class Interpreter<R> {
this.invokeTool = invokeTool
this.toolKeys = toolKeys
this.logs = logs
this.lastValue = undefined
this.callPermits = shared?.callPermits ?? Semaphore.makeUnsafe(TOOL_CALL_CONCURRENCY)
this.pendingSettlements = shared?.pendingSettlements ?? new Set<SandboxPromise>()
globalScope.set("tools", { mutable: false, value: new ToolReference([]) })
@@ -669,15 +672,13 @@ class Interpreter<R> {
return Effect.gen(function* () {
self.hoistFunctions(program.body)
let value: unknown = undefined
for (const [index, statement] of program.body.entries()) {
if (index === program.body.length - 1 && statement.type === "ExpressionStatement") {
value = yield* self.evaluateExpression(getNode(statement, "expression"))
break
}
let returned = false
for (const statement of program.body) {
const result = yield* self.evaluateStatement(statement)
if (result.kind === "return") {
value = result.value
returned = true
break
}
@@ -685,7 +686,11 @@ class Interpreter<R> {
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
// resolves before crossing the data boundary - `return tools.ns.tool(...)` works
@@ -776,7 +781,7 @@ class Interpreter<R> {
private evaluateStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
switch (node.type) {
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":
return Effect.map(this.evaluateVariableDeclaration(node), () => ({ kind: "none" }))
case "ReturnStatement": {
@@ -829,6 +834,11 @@ class Interpreter<R> {
const statement = asNode(statementValue, "body")
const result = yield* self.evaluateStatement(statement)
if (result.kind === "value") {
self.lastValue = result.value
continue
}
if (result.kind !== "none") {
return result
}
@@ -920,6 +930,7 @@ class Interpreter<R> {
const result = yield* self.evaluateStatement(asNode(statementValue, "consequent"))
if (result.kind === "break") return { kind: "none" } satisfies StatementResult
if (result.kind === "return" || result.kind === "continue") return result
if (result.kind === "value") self.lastValue = result.value
}
}
return { kind: "none" } satisfies StatementResult
@@ -947,6 +958,9 @@ class Interpreter<R> {
return result
}
if (result.kind === "value") {
self.lastValue = result.value
}
}
return { kind: "none" } satisfies StatementResult
@@ -974,6 +988,9 @@ class Interpreter<R> {
return result
}
if (result.kind === "value") {
self.lastValue = result.value
}
} while (yield* self.evaluateExpression(testNode))
return { kind: "none" } satisfies StatementResult
@@ -1029,6 +1046,10 @@ class Interpreter<R> {
return { kind: "none" } satisfies StatementResult
}
if (result.kind === "value") {
self.lastValue = result.value
}
if (iterationScope) {
const loopScope = self.currentScope()
for (const name of perIterationBindings) {
@@ -1113,6 +1134,10 @@ class Interpreter<R> {
return { kind: "none" }
}
if (result.kind === "value") {
self.lastValue = result.value
}
if (result.kind === "continue") {
continue
}
@@ -1202,6 +1227,10 @@ class Interpreter<R> {
return { kind: "none" }
}
if (result.kind === "value") {
self.lastValue = result.value
}
if (result.kind === "continue") {
continue
}
@@ -1823,13 +1852,6 @@ class Interpreter<R> {
private evaluateUnaryExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
const operator = getString(node, "operator")
const argument = getNode(node, "argument")
if (operator === "delete") {
const target = argument.type === "ChainExpression" ? getNode(argument, "expression") : argument
if (target.type !== "MemberExpression") {
throw new InterpreterRuntimeError("Delete target must be a data property in CodeMode.", argument)
}
return this.deleteMember(target)
}
// `typeof undeclaredIdentifier` is `"undefined"` in JS (never a ReferenceError), so
// feature-detection guards like `typeof x !== "undefined"` don't crash. Short-circuit before
// evaluating the argument; a declared-but-TDZ binding still falls through to the normal throw.
@@ -2006,6 +2028,7 @@ class Interpreter<R> {
}
if (callable instanceof GlobalMethodReference) {
if (callable.namespace === "console") return self.invokeConsole(callable.name, args, node)
if (callable.namespace === "JSON") return yield* self.invokeJsonMethod(callable.name, args, node)
if (callable.namespace === "Object" && args[0] instanceof ToolReference) {
return self.invokeObjectMethodOnTools(callable.name, args[0], node)
}
@@ -2351,7 +2374,7 @@ class Interpreter<R> {
if (fn.body.type === "BlockStatement") {
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)
@@ -2471,6 +2494,129 @@ class Interpreter<R> {
})
}
private invokeJsonMethod(
name: string,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> {
const callback = args[1]
const callable =
callback instanceof CodeModeFunction ||
callback instanceof CoercionFunction ||
callback instanceof GlobalMethodReference ||
callback instanceof UriFunction
if (name === "parse" && callable) return this.parseJsonWithReviver(args, node)
if (name === "stringify" && (callable || Array.isArray(callback))) {
return this.stringifyJsonWithReplacer(args, node)
}
return Effect.sync(() => invokeJsonMethod(name, args, node))
}
private parseJsonWithReviver(args: Array<unknown>, node: AstNode): Effect.Effect<unknown, unknown, R> {
const apply = this.applySettledCollectionCallback(args[1], "JSON.parse", node)
const visit = (key: string, item: unknown): Effect.Effect<unknown, unknown, R> =>
Effect.gen(function* () {
if (Array.isArray(item)) {
for (let index = 0; index < item.length; index += 1) {
if (!(index in item)) continue
const revived = yield* visit(String(index), item[index])
if (revived === undefined) {
delete item[index]
continue
}
item[index] = revived
}
return yield* apply([key, item])
}
if (item !== null && typeof item === "object" && !isSandboxValue(item)) {
const object = item as SafeObject
for (const childKey of Object.keys(object)) {
const revived = yield* visit(childKey, object[childKey])
if (revived === undefined) {
delete object[childKey]
continue
}
object[childKey] = revived
}
}
return yield* apply([key, item])
})
return visit("", invokeJsonMethod("parse", [args[0]], node))
}
private stringifyJsonWithReplacer(args: Array<unknown>, node: AstNode): Effect.Effect<unknown, unknown, R> {
const callback = args[1]
const apply = Array.isArray(callback)
? undefined
: this.applySettledCollectionCallback(callback, "JSON.stringify", node)
const ancestors = new Set<object>()
const propertyList = Array.isArray(callback)
? Array.from(
new Set(
callback
.filter((item): item is string | number => typeof item === "string" || typeof item === "number")
.map(String),
),
)
: undefined
const visit = (key: string, item: unknown, depth: number): Effect.Effect<unknown, unknown, R> =>
Effect.gen(function* () {
const resolved = yield* (() => {
if (apply === undefined) return Effect.succeed(item)
return apply([
key,
item instanceof SandboxDate || item instanceof SandboxURL
? copyIn(item, "JSON.stringify value")
: item,
])
})()
if (typeofValue(resolved) === "function") return undefined
if (resolved === null || typeof resolved !== "object") return resolved
if (isSandboxValue(resolved)) {
return apply === undefined
? copyIn(resolved, "JSON.stringify replacer result")
: (Object.create(null) as SafeObject)
}
if (!Array.isArray(resolved)) {
const prototype = Object.getPrototypeOf(resolved)
if (prototype !== Object.prototype && prototype !== null) {
return copyIn(resolved, "JSON.stringify replacer result")
}
}
if (depth > MAX_VALUE_DEPTH) {
throw new ToolRuntimeError(
"InvalidDataValue",
`JSON.stringify replacer result exceeds the maximum value depth of ${MAX_VALUE_DEPTH}.`,
)
}
if (ancestors.has(resolved)) {
throw new ToolRuntimeError("InvalidDataValue", "JSON.stringify replacer result contains a circular value.")
}
ancestors.add(resolved)
return yield* Effect.gen(function* () {
if (Array.isArray(resolved)) {
const output: Array<unknown> = []
const length = resolved.length
for (let index = 0; index < length; index += 1) {
output[index] = yield* visit(String(index), resolved[index], depth + 1)
}
return output
}
const output: SafeObject = Object.create(null) as SafeObject
for (const childKey of propertyList ?? Object.keys(resolved)) {
if (isBlockedMember(childKey)) continue
if (apply === undefined && !Object.hasOwn(resolved, childKey)) continue
const child = yield* visit(childKey, (resolved as SafeObject)[childKey], depth + 1)
if (child !== undefined) output[childKey] = child
}
return output
}).pipe(Effect.ensuring(Effect.sync(() => ancestors.delete(resolved))))
})
return Effect.map(visit("", args[0], 0), (value) =>
invokeJsonMethod("stringify", [value, propertyList, args[2]], node),
)
}
// Runs a collection callback accepting a user function or supported builtin callable,
// mirroring the array-method callback contract.
private applyCollectionCallback(
@@ -2481,6 +2627,7 @@ class Interpreter<R> {
if (
!(callback instanceof CodeModeFunction) &&
!(callback instanceof CoercionFunction) &&
!(callback instanceof GlobalMethodReference) &&
!(callback instanceof UriFunction)
) {
throw new InterpreterRuntimeError(`${name} expects a function callback.`, node)
@@ -2488,9 +2635,23 @@ class Interpreter<R> {
return (callbackArgs) =>
callback instanceof CoercionFunction
? Effect.succeed(invokeCoercion(callback, callbackArgs, node))
: callback instanceof UriFunction
? Effect.succeed(invokeUriFunction(callback, callbackArgs, node))
: this.invokeFunction(callback, callbackArgs)
: callback instanceof GlobalMethodReference
? Effect.succeed(invokeGlobalMethod(callback, callbackArgs, node))
: callback instanceof UriFunction
? Effect.succeed(invokeUriFunction(callback, callbackArgs, node))
: this.invokeFunction(callback, callbackArgs)
}
private applySettledCollectionCallback(
callback: unknown,
name: string,
node: AstNode,
): (args: Array<unknown>) => Effect.Effect<unknown, unknown, R> {
const apply = this.applyCollectionCallback(callback, name, node)
return (args) =>
Effect.flatMap(apply(args), (value) =>
value instanceof SandboxPromise ? this.settlePromise(value, node) : Effect.succeed(value),
)
}
private invokeMapMethod(
@@ -3074,7 +3235,6 @@ class Interpreter<R> {
private getMemberReference(
node: AstNode,
options?: { readonly allowUnknownArrayProperty: boolean },
): Effect.Effect<
| MemberReference
| ToolReference
@@ -3241,7 +3401,6 @@ class Interpreter<R> {
typeof key !== "number" &&
!/^\d+$/.test(key)
) {
if (options?.allowUnknownArrayProperty === true) return { target: objectValue, key }
// Own non-index properties read through (match results carry index/groups); like JS,
// they are readable in place and dropped by JSON at data boundaries.
if (typeof key === "string" && Object.hasOwn(objectValue, key)) {
@@ -3287,29 +3446,6 @@ class Interpreter<R> {
return this.modifyMember(node, () => Effect.succeed({ write: true, next: value, result: value }))
}
private deleteMember(node: AstNode): Effect.Effect<boolean, unknown, R> {
return Effect.map(this.getMemberReference(node, { allowUnknownArrayProperty: true }), (reference) => {
if (reference === OptionalShortCircuit) return true
if (
reference instanceof ComputedValue ||
reference === undefined ||
reference instanceof ToolReference ||
reference instanceof PromiseMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference ||
reference.target instanceof SandboxURL
) {
throw new InterpreterRuntimeError("Only data properties may be deleted in CodeMode.", node)
}
if (Array.isArray(reference.target)) {
if (reference.key === "length" || (typeof reference.key === "string" && arrayMethods.has(reference.key))) {
throw new InterpreterRuntimeError("Array length and methods cannot be deleted in CodeMode.", node)
}
}
return Reflect.deleteProperty(reference.target, reference.key)
})
}
// Resolves the member reference EXACTLY ONCE (so a side-effecting object/key expression
// runs once), then lets `compute` decide whether to write - enabling compound assignment,
// updates, plain writes, and short-circuiting logical assignment to share one safe path.
+6 -16
View File
@@ -1,9 +1,4 @@
import {
type AstNode,
CodeModeFunction,
InterpreterRuntimeError,
supportedSyntaxMessage,
} from "../interpreter/model.js"
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
import { copyIn, copyOut } from "../tool-runtime.js"
export const jsonStatics = new Set(["stringify", "parse"])
@@ -12,18 +7,13 @@ export const invokeJsonMethod = (name: string, args: Array<unknown>, node: AstNo
if (!jsonStatics.has(name)) throw new InterpreterRuntimeError(`JSON.${name} is not available in CodeMode.`, node)
switch (name) {
case "stringify": {
const replacer = args[1]
if (Array.isArray(replacer) || replacer instanceof CodeModeFunction) {
throw new InterpreterRuntimeError(
"JSON.stringify replacers are not supported in CodeMode.",
node,
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
}
const space = args[2]
const indent = typeof space === "number" || typeof space === "string" ? space : undefined
return JSON.stringify(copyOut(copyIn(args[0], "JSON.stringify value")), null, indent)
return JSON.stringify(
copyOut(copyIn(args[0], "JSON.stringify value")),
Array.isArray(args[1]) ? args[1] : null,
indent,
)
}
case "parse": {
const text = args[0]
+1 -1
View File
@@ -119,7 +119,7 @@ export class ToolReference {
* limit) purely because it produces a clearer diagnostic than a native stack-overflow
* RangeError would.
*/
const MAX_VALUE_DEPTH = 32
export const MAX_VALUE_DEPTH = 32
export class ToolRuntimeError extends Error {
constructor(
-18
View File
@@ -1081,24 +1081,6 @@ describe("CodeMode public contract", () => {
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 () => {
expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: 0 } })).toThrow(RangeError)
expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: Number.POSITIVE_INFINITY } })).toThrow(
-63
View File
@@ -89,69 +89,6 @@ describe("H6: object spread of null/undefined is a no-op", () => {
})
})
describe("delete data properties", () => {
test("removes an object property and reports success", async () => {
expect(await value(`const item = { keep: 1, remove: 2 }; const removed = delete item.remove; return { item, removed }`)).toEqual({
item: { keep: 1 },
removed: true,
})
})
test("evaluates a computed deletion key once", async () => {
expect(
await value(`
const item = { a: 1, b: 2 }
let calls = 0
const key = () => { calls += 1; return "b" }
const removed = delete item[key()]
return { item, calls, removed }
`),
).toEqual({ item: { a: 1 }, calls: 1, removed: true })
})
test("deleting an array element leaves a hole without changing length", async () => {
expect(
await value(`
const items = ["a", "b", "c"]
const removed = delete items[1]
return { removed, length: items.length, hasIndex: 1 in items, keys: Object.keys(items) }
`),
).toEqual({ removed: true, length: 3, hasIndex: false, keys: ["0", "2"] })
})
test("deleting an absent property succeeds", async () => {
expect(await value(`const item = { keep: 1 }; return delete item.missing`)).toBe(true)
})
test("optional deletion of a nullish receiver succeeds", async () => {
expect(
await value(`let calls = 0; const item = null; const removed = delete item?.[calls++]; return { calls, removed }`),
).toEqual({ calls: 0, removed: true })
})
test("deleting an absent or non-canonical array key does not remove an element", async () => {
expect(
await value(`
const items = ["a", "b"]
const absent = delete items.missing
const nonCanonical = delete items["01"]
return { absent, nonCanonical, items, hasIndex: 1 in items }
`),
).toEqual({ absent: true, nonCanonical: true, items: ["a", "b"], hasIndex: true })
})
test("rejects deletion of runtime and protected members", async () => {
for (const code of [
`delete tools.missing`,
`delete new URL("https://example.test").pathname`,
`delete [1].length`,
`delete [1].map`,
]) {
expect((await error(code)).message).toContain("deleted")
}
})
})
describe("H4: typeof on an undeclared identifier is 'undefined'", () => {
test("feature-detection guard does not throw", async () => {
expect(await value(`return typeof foo === "undefined" ? "safe" : "no"`)).toBe("safe")
+155
View File
@@ -19,6 +19,161 @@ const error = async (code: string) => {
return result.error
}
describe("JSON", () => {
test("parse revivers run post-order including the root", async () => {
expect(
await value(`
const order = []
const parsed = JSON.parse('{"item":{"count":2}}', (key, item) => {
order.push(key)
if (key === "count") return item + 1
if (key === "") return { parsed: item }
return item
})
return { parsed, order }
`),
).toEqual({ parsed: { parsed: { item: { count: 3 } } }, order: ["count", "item", ""] })
expect(await value(`return JSON.parse("1", (key, value) => undefined) === undefined`)).toBe(true)
})
test("parse reviver deletion removes properties and creates array holes", async () => {
expect(
await value(`
const parsed = JSON.parse('{"keep":1,"drop":2,"items":[1,2,3]}', (key, item) =>
key === "drop" || key === "1" ? undefined : item
)
return { json: JSON.stringify(parsed), hasSecond: 1 in parsed.items, length: parsed.items.length }
`),
).toEqual({ json: '{"keep":1,"items":[1,null,3]}', hasSecond: false, length: 3 })
})
test("stringify function replacers run root-first and apply JSON deletion rules", async () => {
expect(
await value(`
const order = []
const json = JSON.stringify({ keep: 1, drop: 2, items: [1, 2] }, (key, item) => {
order.push(key)
if (key === "drop" || key === "1") return undefined
return item
})
return { json, order }
`),
).toEqual({ json: '{"keep":1,"items":[1,null]}', order: ["", "keep", "drop", "items", "0", "1"] })
expect(
await value(`
return [
JSON.stringify(1, (key, item) => key === "" ? new Date(0) : item),
JSON.stringify(1, (key, item) => key === "" ? new URL("https://example.test") : item),
]
`),
).toEqual(["{}", "{}"])
})
test("stringify function replacers observe live mutations and sandbox values", async () => {
expect(
await value(`
const item = { a: 1, b: 2 }
const visited = []
const mutated = JSON.stringify(item, (key, value) => {
visited.push(key)
if (key === "a") item.b = 3
return value
})
const mapped = JSON.stringify(new Map([["x", 1]]), (key, value) =>
value instanceof Map ? Object.fromEntries(value) : value
)
const removed = { a: 1, b: 2 }
const deleted = JSON.stringify(removed, (key, value) => {
if (key === "a") removed.b = undefined
if (key === "b") visited.push(value === undefined ? "missing" : "present")
return value
})
return { mutated, mapped, deleted, visited }
`),
).toEqual({
mutated: '{"a":1,"b":3}',
mapped: '{"x":1}',
deleted: '{"a":1}',
visited: ["", "a", "b", "missing"],
})
})
test("stringify replacer arrays coerce, dedupe, and recursively filter object keys", async () => {
expect(
await value(`
return JSON.stringify(
{ 1: "one", keep: { keep: 2, drop: 3 }, drop: 4, list: [{ keep: 5, drop: 6 }] },
["keep", 1, "keep", null, {}, "list"],
)
`),
).toBe('{"keep":{"keep":2},"1":"one","list":[{"keep":5}]}')
})
test("JSON callbacks settle async tool calls sequentially", async () => {
const calls: Array<number> = []
const transform = Tool.make({
description: "Transform a number",
input: Schema.Number,
output: Schema.Number,
run: (input) =>
Effect.sync(() => {
calls.push(input)
return input * 10
}),
})
const parse = await Effect.runPromise(
CodeMode.execute({
tools: { host: { transform } },
code: `return JSON.parse("[1,2]", async (key, item) => key === "" ? item : await tools.host.transform(item))`,
}),
)
expect(parse.ok && parse.value).toEqual([10, 20])
const stringify = await Effect.runPromise(
CodeMode.execute({
tools: { host: { transform } },
code: `return JSON.stringify([1,2], async (key, item) => key === "" ? item : await tools.host.transform(item))`,
}),
)
expect(stringify.ok && stringify.value).toBe("[10,20]")
expect(calls).toEqual([1, 2, 1, 2])
})
test("non-callable JSON callback arguments are ignored", async () => {
expect(await value(`return [JSON.parse("{\\"a\\":1}", 42), JSON.stringify({ a: 1 }, { nope: true })]`)).toEqual([
{ a: 1 },
'{"a":1}',
])
expect(await value(`return [JSON.parse("1", Boolean), JSON.stringify({ a: 1 }, String)]`)).toEqual([false, '""'])
})
test("supported global methods work as JSON callbacks", async () => {
expect(
await value(`return [JSON.parse("[1,2]", Number.isFinite), JSON.stringify({ a: 1 }, Number.isFinite)]`),
).toEqual([false, "false"])
})
test("stringify replacers can prune values before the depth limit", async () => {
expect(
await value(`
let item = 1
for (let index = 0; index < 33; index++) item = { x: item }
let calls = 0
return JSON.stringify(item, (key, value) => ++calls === 34 ? undefined : value)
`),
).toBe(`${'{"x":'.repeat(32)}{}${"}".repeat(32)}`)
})
test("stringify omits callable replacer results", async () => {
expect(
await value(`
return JSON.stringify({ object: 1, array: [1] }, (key, value) =>
key === "object" || key === "0" ? Number.isFinite : value
)
`),
).toBe('{"array":[null]}')
})
})
describe("Date", () => {
test("Date.now() returns a number", async () => {
expect(await value(`return typeof Date.now()`)).toBe("number")
+22 -35
View File
@@ -150,44 +150,31 @@ export type LocationError = LayerNode.Error<typeof locationServices>
export function buildLocationServiceMap(
replacements: LayerNode.Replacements = [],
): 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(
LocationServiceMap.Service,
Effect.map(
LayerMap.make(
(ref: Location.Ref) => {
const startedAt = performance.now()
const allReplacements = replacements.concat([[Location.node, Location.boundNode(ref)]])
// Apply replacements during hoist, not afterward: replacements can
// introduce new tagged dependencies (Location.boundNode depends on
// Project), and the hoist walk is the only pass that can still slice
// those back out.
const location = LayerNode.hoist(locationServices, Node.tags.values.global, allReplacements)
LayerMap.make(
(ref: Location.Ref) => {
const startedAt = performance.now()
const allReplacements = replacements.concat([[Location.node, Location.boundNode(ref)]])
// Apply replacements during hoist, not afterward: replacements can
// introduce new tagged dependencies (Location.boundNode depends on
// Project), and the hoist walk is the only pass that can still slice
// those back out.
const location = LayerNode.hoist(locationServices, Node.tags.values.global, allReplacements)
return LayerNode.compile(location.node).pipe(
Layer.fresh,
Layer.tap(() =>
Effect.logInfo("location services booted", {
directory: ref.directory,
workspaceID: ref.workspaceID,
durationMs: Math.round(performance.now() - startedAt),
}),
),
Layer.provide(LayerNode.compile(location.hoisted)),
)
},
{ 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)),
}),
return LayerNode.compile(location.node).pipe(
Layer.fresh,
Layer.tap(() =>
Effect.logInfo("location services booted", {
directory: ref.directory,
workspaceID: ref.workspaceID,
durationMs: Math.round(performance.now() - startedAt),
}),
),
Layer.provide(LayerNode.compile(location.hoisted)),
)
},
{ idleTimeToLive: "60 minutes" },
),
)
}
+1 -31
View File
@@ -3,7 +3,7 @@ import path from "path"
import { describe, expect } from "bun:test"
import { Config } from "@opencode-ai/schema/config"
import { Plugin } from "@opencode-ai/schema/plugin"
import { Context, DateTime, Effect, Equal, Hash, RcMap, Schema, Stream } from "effect"
import { Context, DateTime, Effect, Equal, Hash, Schema, Stream } from "effect"
import { define } from "@opencode-ai/plugin/v2/effect"
import { AgentV2 } from "@opencode-ai/core/agent"
import { Catalog } from "@opencode-ai/core/catalog"
@@ -208,36 +208,6 @@ describe("LocationServiceMap", () => {
),
)
it.live("normalizes ref key shapes to one cached location graph", () =>
Effect.acquireRelease(
Effect.promise(() => tmpdir()),
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
).pipe(
Effect.flatMap((dir) =>
Effect.scoped(
Effect.gen(function* () {
const locations = yield* LocationServiceMap.Service
const directory = AbsolutePath.make(dir.path)
const absent = Location.Ref.make({ directory })
const present = Location.Ref.make({ directory, workspaceID: undefined })
// The two shapes are not structurally Equal: own-key sets differ.
expect(Object.keys(absent)).toEqual(["directory"])
expect(Object.keys(present)).toEqual(["directory", "workspaceID"])
expect(Equal.equals(absent, present)).toBe(false)
const first = yield* locations.contextEffect(absent)
expect(yield* locations.contextEffect(present)).toBe(first)
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toHaveLength(1)
// Invalidating with the shape opposite to the one that booted must evict.
yield* locations.invalidate(present)
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toHaveLength(0)
}),
),
),
),
)
it.live("isolates catalog state by location", () =>
Effect.acquireRelease(
Effect.promise(() => Promise.all([tmpdir(), tmpdir()])),
+14 -6
View File
@@ -17,12 +17,7 @@
"tabs": [
{
"tab": "Docs",
"groups": [
{
"group": "Get started",
"pages": ["index", "config", "plugins", "troubleshooting"]
}
]
"pages": ["index", "config", "plugins", "troubleshooting"]
},
{
"tab": "SDK",
@@ -44,6 +39,19 @@
],
"global": {}
},
"navbar": {
"links": [
{
"label": "Discord",
"href": "https://opencode.ai/discord"
}
],
"primary": {
"type": "button",
"label": "GitHub",
"href": "https://github.com/anomalyco/opencode"
}
},
"contextual": {
"options": ["copy", "view", "chatgpt", "claude", "mcp", "cursor", "vscode"]
},
-1
View File
@@ -16,7 +16,6 @@
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@napi-rs/canvas": "1.0.2",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/llm": "workspace:*",
"@opentui/core": "catalog:",
@@ -1,6 +1,7 @@
import { Effect } from "effect"
import { SimulationProtocol } from "../protocol"
import { SimulationLLMExchange } from "./llm-exchange"
import { SimulationNetwork } from "./network"
/**
* Backend-hosted simulation control WebSocket.
@@ -18,6 +19,7 @@ import { SimulationLLMExchange } from "./llm-exchange"
* - `llm.finish` { id, reason? } finish an exchange
* - `llm.disconnect` { id } abruptly terminate an exchange without a finish
* - `llm.pending` list open exchanges
* - `network.log` simulated network request log
*/
type ControlSocket = Bun.ServerWebSocket<{ unsubscribe?: () => void }>
@@ -56,6 +58,8 @@ async function handle(socket: ControlSocket, request: SimulationProtocol.Backend
}
case "llm.pending":
return { exchanges: SimulationLLMExchange.pending() }
case "network.log":
return { entries: SimulationNetwork.log() }
}
}
+31 -72
View File
@@ -1,11 +1,8 @@
import { mkdtemp } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import type { CapturedFrame, CliRenderer, Renderable } from "@opentui/core"
import type { CliRenderer, Renderable } from "@opentui/core"
import { createMockKeys, createMockMouse, type MockInput, type MockMouse } from "@opentui/core/testing"
import type { SimulationProtocol } from "../protocol"
import { SimulationRenderer } from "./renderer"
import { SimulationPng } from "./png"
import { SimulationTrace } from "./trace"
export type Action = SimulationProtocol.Frontend.Action
export type Element = SimulationProtocol.Frontend.Element
@@ -94,95 +91,57 @@ export function elements(renderer: CliRenderer): Element[] {
.filter((element) => element.focusable || element.clickable || element.editor)
}
export function actions(renderer: CliRenderer, options: { text?: string } = {}): Action[] {
const items = elements(renderer)
return [
...(renderer.currentFocusedEditor
? ([{ type: "typeText", text: options.text ?? "hello" }, { type: "pressEnter" }] satisfies Action[])
: []),
...items.filter((item) => item.focusable && !item.focused).map((item) => ({ type: "focus" as const, target: item.num })),
...items
.filter((item) => item.clickable)
.map((item) => ({
type: "click" as const,
target: item.num,
x: Math.floor(item.x + item.width / 2),
y: Math.floor(item.y + item.height / 2),
})),
{ type: "pressArrow", direction: "down" },
{ type: "pressArrow", direction: "up" },
]
}
export function state(harness: Harness) {
return {
screen: harness.screen(),
focused: {
renderable: harness.renderer.currentFocusedRenderable?.num,
editor: Boolean(harness.renderer.currentFocusedEditor),
},
elements: elements(harness.renderer),
actions: actions(harness.renderer),
}
}
export async function screenshot(harness: Harness) {
await harness.renderOnce()
const image = SimulationPng.screenshot(harness.renderer)
const path = join(await mkdtemp(join(tmpdir(), "opencode-drive-")), "screenshot.png")
await Bun.write(path, image.data)
return path
}
export function frame(harness: Harness): CapturedFrame {
const buffer = harness.renderer.currentRenderBuffer
return {
cols: buffer.width,
rows: buffer.height,
cursor: [0, 0],
lines: buffer.getSpanLines().map((line) => ({
spans: line.spans.map((span) => ({
text: span.text,
fg: span.fg,
bg: span.bg,
attributes: span.attributes,
width: span.width,
})),
})),
}
}
export async function video(frames: CapturedFrame[]) {
const directory = await mkdtemp(join(tmpdir(), "opencode-drive-recording-"))
await Promise.all(
frames.map((frame, index) =>
Bun.write(
join(directory, `frame-${index.toString().padStart(6, "0")}.png`),
SimulationPng.screenshotFrame(frame).data,
),
),
)
const path = join(directory, "recording.mp4")
const process = Bun.spawn(
[
"ffmpeg",
"-loglevel",
"error",
"-framerate",
"10",
"-i",
join(directory, "frame-%06d.png"),
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-movflags",
"+faststart",
"-y",
path,
],
{ stderr: "pipe" },
)
if ((await process.exited) !== 0) throw new Error(`ffmpeg failed: ${await new Response(process.stderr).text()}`)
return path
}
export async function execute(harness: Harness, action: Action) {
SimulationTrace.add("ui.action", { action })
switch (action.type) {
case "ui.type":
case "typeText":
await harness.mockInput.typeText(action.text)
break
case "ui.press":
case "pressKey":
harness.mockInput.pressKey(action.key, action.modifiers)
break
case "ui.enter":
case "pressEnter":
harness.mockInput.pressEnter()
break
case "ui.arrow":
case "pressArrow":
harness.mockInput.pressArrow(action.direction)
break
case "ui.focus":
case "focus":
all(harness.renderer.root).find((item) => item.num === action.target)?.focus()
break
case "ui.click":
case "click":
await harness.mockMouse.click(action.x, action.y)
break
}
-84
View File
@@ -1,84 +0,0 @@
import { fileURLToPath } from "node:url"
import { GlobalFonts, createCanvas } from "@napi-rs/canvas"
import { TextAttributes, type CapturedFrame, type CliRenderer, type RGBA } from "@opentui/core"
const CellWidth = 10
const CellHeight = 20
const FontSize = 16
const FontFamily = "OpenCode Screenshot"
GlobalFonts.registerFromPath(
fileURLToPath(new URL("../../../ui/src/assets/fonts/JetBrainsMonoNerdFontMono-Regular.woff2", import.meta.url)),
FontFamily,
)
export function screenshot(renderer: CliRenderer) {
return screenshotFrame({
cols: renderer.currentRenderBuffer.width,
rows: renderer.currentRenderBuffer.height,
cursor: [0, 0],
lines: renderer.currentRenderBuffer.getSpanLines(),
})
}
export function screenshotFrame(frame: CapturedFrame) {
const canvas = createCanvas(frame.cols * CellWidth, frame.rows * CellHeight)
const context = canvas.getContext("2d")
context.fillStyle = "#080808"
context.fillRect(0, 0, canvas.width, canvas.height)
context.textBaseline = "top"
frame.lines.forEach((line, row) => {
let column = 0
line.spans.forEach((span) => {
const attributes = span.attributes & 0xff
const inverse = Boolean(attributes & TextAttributes.INVERSE)
const hidden = Boolean(attributes & TextAttributes.HIDDEN)
const foreground = inverse ? span.bg : span.fg
const background = inverse ? span.fg : span.bg
const chars = [...span.text]
let remaining = span.width
chars.forEach((char, index) => {
const cells = Math.max(1, remaining - (chars.length - index - 1))
if (background.a) {
context.fillStyle = color(background)
context.fillRect(column * CellWidth, row * CellHeight, cells * CellWidth, CellHeight)
}
if (!hidden && char.codePointAt(0) !== 0x0a00) {
context.fillStyle = color(foreground, attributes & TextAttributes.DIM ? 0.55 : 1)
context.font = `${attributes & TextAttributes.ITALIC ? "italic " : ""}${attributes & TextAttributes.BOLD ? "bold " : ""}${FontSize}px "${FontFamily}"`
context.fillText(char, column * CellWidth, row * CellHeight + 1)
if (attributes & TextAttributes.UNDERLINE) {
context.fillRect(column * CellWidth, row * CellHeight + 17, cells * CellWidth, 1)
}
if (attributes & TextAttributes.STRIKETHROUGH) {
context.fillRect(column * CellWidth, row * CellHeight + 10, cells * CellWidth, 1)
}
}
column += cells
remaining -= cells
})
while (remaining-- > 0) {
if (background.a) {
context.fillStyle = color(background)
context.fillRect(column * CellWidth, row * CellHeight, CellWidth, CellHeight)
}
column++
}
})
})
return {
width: canvas.width,
height: canvas.height,
data: canvas.toBuffer("image/png"),
}
}
function color(value: RGBA, opacity = 1) {
const [red, green, blue, alpha] = value.toInts()
return `rgba(${red}, ${green}, ${blue}, ${(alpha / 255) * opacity})`
}
export * as SimulationPng from "./png"
+25 -67
View File
@@ -1,6 +1,6 @@
import type { CapturedFrame } from "@opentui/core"
import { SimulationProtocol } from "../protocol"
import { SimulationActions, type Harness } from "./actions"
import { SimulationTrace } from "./trace"
export interface Server {
readonly url: string
@@ -11,90 +11,47 @@ function parseRequest(input: string | Buffer) {
return SimulationProtocol.Frontend.decodeRequest(JSON.parse(typeof input === "string" ? input : input.toString()))
}
interface Recording {
readonly frames: CapturedFrame[]
readonly timer: ReturnType<typeof setInterval>
pending: Promise<void>
}
async function handle(
harness: Harness,
request: SimulationProtocol.Frontend.Request,
recording: { current?: Recording },
headless: boolean,
) {
async function handle(harness: Harness, request: SimulationProtocol.Frontend.Request, headless: boolean) {
switch (request.method) {
case "ui.screenshot":
return SimulationActions.screenshot(harness)
case "ui.state": {
return SimulationActions.state(harness)
}
case "ui.start-record": {
if (recording.current) throw new Error("UI recording is already active")
const frames = [SimulationActions.frame(harness)]
const current: Recording = {
frames,
timer: setInterval(() => {
current.pending = current.pending.then(async () => {
if (headless) await harness.renderOnce()
frames.push(SimulationActions.frame(harness))
})
}, 100),
pending: Promise.resolve(),
}
recording.current = current
return { recording: true }
}
case "ui.end-record": {
if (!recording.current) throw new Error("UI recording is not active")
const current = recording.current
clearInterval(current.timer)
await current.pending
if (headless) await harness.renderOnce()
current.frames.push(SimulationActions.frame(harness))
recording.current = undefined
return SimulationActions.video(current.frames)
const result = SimulationActions.state(harness)
SimulationTrace.add("ui.state", { elements: result.elements.length, actions: result.actions.length })
return result
}
case "ui.type":
return SimulationActions.execute(harness, { type: "ui.type", text: request.params.text })
case "ui.enter":
return SimulationActions.execute(harness, { type: "ui.enter" })
case "ui.press":
return SimulationActions.execute(harness, {
type: "ui.press",
key: request.params.key,
modifiers: request.params.modifiers,
})
case "ui.arrow":
return SimulationActions.execute(harness, { type: "ui.arrow", direction: request.params.direction })
case "ui.focus":
return SimulationActions.execute(harness, { type: "ui.focus", target: request.params.target })
case "ui.click":
return SimulationActions.execute(harness, {
type: "ui.click",
target: request.params.target,
x: request.params.x,
y: request.params.y,
})
case "ui.action":
return SimulationActions.execute(harness, request.params.action)
case "trace.list":
return { records: SimulationTrace.list() }
case "trace.clear":
SimulationTrace.clear()
return { cleared: true }
case "trace.export":
return SimulationTrace.exportTrace()
}
}
export function start(harness: Harness, endpoint: string, headless: boolean): Server {
const url = new URL(endpoint)
const recording: { current?: Recording } = {}
const server = Bun.serve<{ readonly drive: true; readonly headless: boolean }>({
const server = Bun.serve<{ readonly drive: true }>({
hostname: url.hostname,
port: Number(url.port),
fetch(request, server) {
if (server.upgrade(request, { data: { drive: true, headless } })) return undefined
if (server.upgrade(request, { data: { drive: true } })) return undefined
return new Response("opencode drive ui websocket", { status: 426 })
},
websocket: {
open() {
SimulationTrace.add("control.connect")
},
close() {
SimulationTrace.add("control.disconnect")
},
async message(socket, message) {
let request: SimulationProtocol.Frontend.Request | undefined
try {
request = parseRequest(message)
const result = await handle(harness, request, recording, headless)
const result = await handle(harness, request, headless)
const next = SimulationProtocol.JsonRpc.success(request.id, result)
if (next) socket.send(JSON.stringify(next))
} catch (error) {
@@ -103,10 +60,11 @@ export function start(harness: Harness, endpoint: string, headless: boolean): Se
},
},
})
SimulationTrace.add("control.start", { url: endpoint })
return {
url: endpoint,
stop: () => {
if (recording.current) clearInterval(recording.current.timer)
SimulationTrace.add("control.stop", { url: endpoint })
server.stop(true)
},
}
+37
View File
@@ -0,0 +1,37 @@
export type TraceRecord = {
readonly id: number
readonly time: string
readonly type: string
readonly data?: unknown
}
const records: TraceRecord[] = []
let nextID = 0
export function add(type: string, data?: unknown) {
const record = {
id: ++nextID,
time: new Date().toISOString(),
type,
...(data === undefined ? {} : { data }),
} satisfies TraceRecord
records.push(record)
return record
}
export function list() {
return [...records]
}
export function clear() {
records.length = 0
nextID = 0
}
export function exportTrace() {
return {
records: list(),
}
}
export * as SimulationTrace from "./trace"
+23 -42
View File
@@ -59,12 +59,12 @@ export namespace Frontend {
export interface KeyModifiers extends Schema.Schema.Type<typeof KeyModifiers> {}
export const Action = Schema.Union([
Schema.Struct({ type: Schema.Literal("ui.type"), text: Schema.String }),
Schema.Struct({ type: Schema.Literal("ui.press"), key: Schema.String, modifiers: Schema.optional(KeyModifiers) }),
Schema.Struct({ type: Schema.Literal("ui.enter") }),
Schema.Struct({ type: Schema.Literal("ui.arrow"), direction: Schema.Literals(["up", "down", "left", "right"]) }),
Schema.Struct({ type: Schema.Literal("ui.focus"), target: Schema.Number }),
Schema.Struct({ type: Schema.Literal("ui.click"), target: Schema.Number, x: Schema.Number, y: Schema.Number }),
Schema.Struct({ type: Schema.Literal("typeText"), text: Schema.String }),
Schema.Struct({ type: Schema.Literal("pressKey"), key: Schema.String, modifiers: Schema.optional(KeyModifiers) }),
Schema.Struct({ type: Schema.Literal("pressEnter") }),
Schema.Struct({ type: Schema.Literal("pressArrow"), direction: Schema.Literals(["up", "down", "left", "right"]) }),
Schema.Struct({ type: Schema.Literal("focus"), target: Schema.Number }),
Schema.Struct({ type: Schema.Literal("click"), target: Schema.Number, x: Schema.Number, y: Schema.Number }),
])
export type Action = Schema.Schema.Type<typeof Action>
@@ -83,58 +83,39 @@ export namespace Frontend {
export interface Element extends Schema.Schema.Type<typeof Element> {}
export const State = Schema.Struct({
screen: Schema.String,
focused: Schema.Struct({
renderable: Schema.optional(Schema.Number),
editor: Schema.Boolean,
}),
elements: Schema.Array(Element),
actions: Schema.Array(Action),
})
export interface State extends Schema.Schema.Type<typeof State> {}
export const Screenshot = Schema.String
export type Screenshot = Schema.Schema.Type<typeof Screenshot>
export const StartRecord = Schema.Struct({ recording: Schema.Literal(true) })
export interface StartRecord extends Schema.Schema.Type<typeof StartRecord> {}
export const EndRecord = Schema.String
export type EndRecord = Schema.Schema.Type<typeof EndRecord>
export const TypeParams = Schema.Struct({ text: Schema.String })
export interface TypeParams extends Schema.Schema.Type<typeof TypeParams> {}
export const PressParams = Schema.Struct({ key: Schema.String, modifiers: Schema.optional(KeyModifiers) })
export interface PressParams extends Schema.Schema.Type<typeof PressParams> {}
export const ArrowParams = Schema.Struct({ direction: Schema.Literals(["up", "down", "left", "right"]) })
export interface ArrowParams extends Schema.Schema.Type<typeof ArrowParams> {}
export const FocusParams = Schema.Struct({ target: Schema.Number })
export interface FocusParams extends Schema.Schema.Type<typeof FocusParams> {}
export const ClickParams = Schema.Struct({ target: Schema.Number, x: Schema.Number, y: Schema.Number })
export interface ClickParams extends Schema.Schema.Type<typeof ClickParams> {}
export const ActionParams = Schema.Struct({ action: Action })
export interface ActionParams extends Schema.Schema.Type<typeof ActionParams> {}
export const Request = Schema.Union([
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.type"), params: TypeParams }),
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.press"), params: PressParams }),
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.arrow"), params: ArrowParams }),
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.focus"), params: FocusParams }),
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.click"), params: ClickParams }),
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.action"), params: ActionParams }),
Schema.Struct({
...JsonRpc.RequestFields,
method: Schema.Literals([
"ui.enter",
"ui.screenshot",
"ui.state",
"ui.start-record",
"ui.end-record",
]),
method: Schema.Literals(["ui.state", "trace.list", "trace.clear", "trace.export"]),
}),
])
export type Request = Schema.Schema.Type<typeof Request>
export const decodeRequest = Schema.decodeUnknownSync(Request)
export const TraceRecord = Schema.Struct({
id: Schema.Number,
time: Schema.String,
type: Schema.String,
data: Schema.optional(Schema.Json),
})
export interface TraceRecord extends Schema.Schema.Type<typeof TraceRecord> {}
export const TraceList = Schema.Struct({ records: Schema.Array(TraceRecord) })
export interface TraceList extends Schema.Schema.Type<typeof TraceList> {}
}
export namespace Backend {
@@ -167,7 +148,7 @@ export namespace Backend {
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("llm.disconnect"), params: DisconnectParams }),
Schema.Struct({
...JsonRpc.RequestFields,
method: Schema.Literals(["llm.attach", "llm.pending"]),
method: Schema.Literals(["llm.attach", "llm.pending", "network.log"]),
}),
])
export type Request = Schema.Schema.Type<typeof Request>