From ac336ed8935ce2a8450d7b1e6cd7907f294c60dc Mon Sep 17 00:00:00 2001 From: AMagicPear Date: Wed, 15 Jul 2026 13:09:17 +0800 Subject: [PATCH 1/3] fix apply_patch layout and GPT model detection --- README.md | 4 +- bun.lock | 765 ++++++++++++++++++ src/index.ts => index.ts | 424 ++++++++-- package.json | 2 +- test/index.test.ts | 61 +- test/render.test.ts | 2 +- tsconfig.json | 2 +- ...ite-file-atomic.ts => write-file-atomic.ts | 0 8 files changed, 1156 insertions(+), 104 deletions(-) create mode 100644 bun.lock rename src/index.ts => index.ts (77%) rename src/write-file-atomic.ts => write-file-atomic.ts (100%) diff --git a/README.md b/README.md index a174ac1..67bf685 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ git clone https://github.com/code-yeongyu/pi-apply-patch ~/.pi/agent/extensions/ cd ~/.pi/agent/extensions/pi-apply-patch && npm install # 4. Dev / one-shot test -pi -e /path/to/pi-apply-patch/src/index.ts +pi -e /path/to/pi-apply-patch/index.ts ``` After installation, restart pi or run `/reload` inside an interactive session. @@ -63,7 +63,7 @@ npm test npm run typecheck npm run check npm pack --dry-run -pi -e ./src/index.ts +pi -e ./index.ts ``` The test suite uses vitest. TypeScript is strict, Node-only, and uses ESM imports with `.js` suffixes. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..a3ce7cd --- /dev/null +++ b/bun.lock @@ -0,0 +1,765 @@ +{ + "lockfileVersion": 1, + "configVersion": 0, + "workspaces": { + "": { + "name": "pi-apply-patch", + "dependencies": { + "diff": "^9.0.0", + }, + "devDependencies": { + "@biomejs/biome": "2.4.16", + "@types/node": "^25.9.1", + "@typescript/native-preview": "^7.0.0-dev.20260604.1", + "typescript": "^6.0.3", + "vitest": "^4.1.8", + }, + "peerDependencies": { + "@earendil-works/pi-agent-core": "*", + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", + "typebox": "*", + }, + }, + }, + "packages": { + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "bin": { "anthropic-ai-sdk": "bin/cli" } }, ""], + + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.974.17", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@aws-sdk/xml-builder": "^3.972.27", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-r8o4h2K7j6P9ngno+8ei0aK0U/4JwDb7A2fMMxGVoSqDN8AFlIzSDeZHME9LcVLR2codyhtr1WAAg+/nmkeeMA=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.43", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-g0XVQKzaA/4cq1vz1IvCQwYM+1Pkv01J9yHDpCTXekVuGZRDEz0wqBQ1AuYTq7FM6uik4uBGH8Tb5d9YvgeA7g=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-w9PuOoKCt6+xoESvY+zlV0u3PKQ0mVL259PcsVR6a3S/uYJJHnIi4r1NxdJHEcNldUVRIciltWnFMGBR4YEm3g=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/credential-provider-env": "^3.972.43", "@aws-sdk/credential-provider-http": "^3.972.45", "@aws-sdk/credential-provider-login": "^3.972.48", "@aws-sdk/credential-provider-process": "^3.972.43", "@aws-sdk/credential-provider-sso": "^3.972.48", "@aws-sdk/credential-provider-web-identity": "^3.972.48", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-83r5MK+PERv9irzky1o5aNbXiLuaLfeB7N8MrktB9USpoebdNtuG0Ek9ieIxpGH1aZ9a0nIaDaLjEr3EmOV3Ng=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-amPGeF6fcvLInK4Pu2k2Y2jHFR6MpaIKrZrbaf0QUnV3tjzjWh442eifZ2+KcmzFdsqyvyjBqAhq2JNLt1C5gA=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.51", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.43", "@aws-sdk/credential-provider-http": "^3.972.45", "@aws-sdk/credential-provider-ini": "^3.972.49", "@aws-sdk/credential-provider-process": "^3.972.43", "@aws-sdk/credential-provider-sso": "^3.972.48", "@aws-sdk/credential-provider-web-identity": "^3.972.48", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-mbhSY3ytXIGMuBoJsWCivk+63dtVlenT6wstUra07Lar4Ln2MVL8/j5zCTIOog+ig5/FlFJ8gcFU4nQZV+Jh4Q=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.43", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-GPokLNyvTfCmuaHk+v3GKVs4ZT3cMu5kgS2a+NPkOMt96cq6fSIK0g+mZHpGS6Cd4QGrPKesANEaLUKgOskTzg=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/token-providers": "3.1062.0", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-tf0sD47SeTgCDfOWYssctzGgwAuk8/ECjb7bom4wZ7P1om0qE8i2yjniUdvysmANm5haARr35O8vZnTe/UEtpQ=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-YYsumc2oe09gl4l+fjfmR64JDn6+0o4Ql5HMBkMuhFazO1tZlE5NjSnZM3oXHwenPjh2qow0TFgSIVjfWfsojg=="], + + "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-MZhrsChY4jwEp7LQnNkcNSvF4KHjDC8es1pgu61h6L48fY7YgRqDfGRoT4ADd7lj4dB+gtOYITgmf7k4QQ2TKg=="], + + "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-4qYsO6temM6rEawcxHpMPWnRSIiLzsKhuizMlXCVujj54Q+HoGkVlcxk8S+5ekq/hOBdkyRnQjNsZaeRBz60hg=="], + + "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-1u/r6SYArJr5qBHWQzwGw8cQu32V5Rcx68qb4v+ZhHXFn6dGDtCG5ImyULCLxhTktibLTh2qaRHOoHmkTKCyvA=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.16", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.17", "@aws-sdk/signature-v4-multi-region": "^3.996.31", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-bGvfDgC2KQePjEmZdltScPPLKFoyjPElAXeZcLfvZ58J1AO283//WGtvp9GdnryLHTi7gis0UoCezqh0vl/nig=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.31", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Kn2up9SlG1KC6wRtwf0d7waTGF6rvp9DxYqB54x6UCKdQ6kyaXCqHL4WGb5vUJga5kS8FxnjhY0LqM28aMvnNQ=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.973.10", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-992QrTO7G9qCvKD0fx1rMlqcL14plUcRAbwmqqYVsuF3GrqcvlAL9qxR+baMafarEZ+l7DUQ5lCMmt5mbMhF7g=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.27", "", { "dependencies": { "@smithy/types": "^4.14.3", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-hpsCXCOI436kxWpjtRuIHVvuPP81MOw8f18jzfZeg+UOiiOvlqWcmWChzEhJEu16cOC6+ku4ncBN+7rdt+DZ9g=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, ""], + + "@biomejs/biome": ["@biomejs/biome@2.4.16", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.16", "@biomejs/cli-darwin-x64": "2.4.16", "@biomejs/cli-linux-arm64": "2.4.16", "@biomejs/cli-linux-arm64-musl": "2.4.16", "@biomejs/cli-linux-x64": "2.4.16", "@biomejs/cli-linux-x64-musl": "2.4.16", "@biomejs/cli-win32-arm64": "2.4.16", "@biomejs/cli-win32-x64": "2.4.16" }, "bin": { "biome": "bin/biome" } }, "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.16", "", { "os": "win32", "cpu": "x64" }, "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw=="], + + "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.78.1", "", { "dependencies": { "@earendil-works/pi-ai": "^0.78.1", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" } }, "sha512-oPwVRkkAvyKPWyM7E4k+EaTNmynbYn7ZLG/LBh9BUnMNb2gvpMp+VQ420R6JCJ20uogSqrHnWTyosSa/rU8lVw=="], + + "@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.78.1", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", "@mistralai/mistralai": "2.2.1", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", "typebox": "1.1.38" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-CM2pkTs1iupG/maw381lC9Q/Y/aQaMGK7GILc28ttImD0ci3LDwKroDsGkWbly5JIy3iqxdRxB9JlG7vvzCzTg=="], + + "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.78.1", "", { "dependencies": { "@earendil-works/pi-agent-core": "^0.78.1", "@earendil-works/pi-ai": "^0.78.1", "@earendil-works/pi-tui": "^0.78.1", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", "diff": "8.0.4", "glob": "13.0.6", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.5", "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "typebox": "1.1.38", "undici": "8.3.0", "yaml": "2.9.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "pi": "dist/cli.js" } }, "sha512-Syjf6Ib8UoY5t9ZdKjp0BRrQZuFkFBc8j2KEU9zG/ZnmYPcAxYeioofdv2Q3MEXnHEX2U8sKQptkSnJIdMsd0g=="], + + "@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.78.1", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "15.0.12" } }, "sha512-07GVQo/38a0yvIPlWDr3RJn1B8gk3ZuIX9h2oIQ+Biyu3JN0KppWmgWHfaWRydQgse5JtC++KDw5MWaIRnV0mw=="], + + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="], + + "@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ=="], + + "@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.9", "", { "os": "darwin" }, "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ=="], + + "@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg=="], + + "@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw=="], + + "@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ=="], + + "@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.9", "", { "os": "linux", "cpu": "none" }, "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw=="], + + "@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw=="], + + "@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ=="], + + "@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ=="], + + "@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA=="], + + "@mistralai/mistralai": ["@mistralai/mistralai@2.2.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" } }, ""], + + "@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=="], + + "@nodable/entities": ["@nodable/entities@2.1.1", "", {}, "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg=="], + + "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.2", "", {}, "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.3", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], + + "@smithy/core": ["@smithy/core@3.24.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.8", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ=="], + + "@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], + + "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260604.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260604.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260604.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260604.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260604.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260604.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260604.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260604.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-A3/9yZTt2V5NlDURcVJ4mN2YjfeQTXCRyLuENKrNdGhO+y59mC/2UDr7UvpB3Li+83TRAuhDN8SBoM+7gkHdzQ=="], + + "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260604.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zs616um9UuaODLsNlCu5Aw95rFcTV4u3hVt090r6k0lVvTxfaJOv8HKA6BpIotcEYlZlMQowrMSYCCdedo7iyA=="], + + "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260604.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-pOdNAf2pwc9JBjo8gUsvLs5uqg7d0AhYXfE8/3zvPKBlIZG+mTcvEWW1hPawlWxXzf/vxnP4dgYwUHAMDghhKA=="], + + "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260604.1", "", { "os": "linux", "cpu": "arm" }, "sha512-IKaZL3i5HKmKqwb2IZEXW1j68fVg1HsvAaXkbrkOIG/J5Eyksu5tEnTzucrIY1oPdzgHT+y2HpDIYp2sGeHFvw=="], + + "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260604.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-GjIrt6YHP3bbOWBCCE08SlBSDf84Lnjn3Td822/lOX9nm6ODlA/HI7rtGh7KzS/fxehep2Vy4dXU4Il12X1s5A=="], + + "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260604.1", "", { "os": "linux", "cpu": "x64" }, "sha512-twQ7XDjsmaHIevN1MjeRYIVLUPL0fBm8A0jg1FhGYPckhTxEBiHIpJf9E//eFidAdrEHjeTSBP1jJw3GNAensg=="], + + "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260604.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-QBRxaVT3SFiNfOhwYb/56ddpHWPMFdfiJ4zkFJIjaAXeZ/ssWNHM9lH7yR++GrE9VTsUZ4eVSZfOmQbzRERhgw=="], + + "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260604.1", "", { "os": "win32", "cpu": "x64" }, "sha512-hR7YHoRpm88Q86gK6/IMayWUA+ROdHGzOPKK8EBp1xD/Cg2Bh5AXbut8HcyUDx/bcGQvnuht/XsW47bT3to9mg=="], + + "@vitest/expect": ["@vitest/expect@4.1.8", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.8", "@vitest/utils": "4.1.8", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.8", "", { "dependencies": { "@vitest/spy": "4.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw"] }, "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="], + + "@vitest/runner": ["@vitest/runner@4.1.8", "", { "dependencies": { "@vitest/utils": "4.1.8", "pathe": "^2.0.3" } }, "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "@vitest/utils": "4.1.8", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ=="], + + "@vitest/spy": ["@vitest/spy@4.1.8", "", {}, "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA=="], + + "@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=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + + "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, ""], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, ""], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="], + + "fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gaxios": ["gaxios@7.1.5", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg=="], + + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "google-auth-library": ["google-auth-library@10.7.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ=="], + + "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "ignore": ["ignore@7.0.5", "", {}, ""], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jiti": ["jiti@2.7.0", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, ""], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru-cache": ["lru-cache@11.4.0", "", {}, "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "marked": ["marked@15.0.12", "", { "bin": "bin/marked.js" }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": "bin/nanoid.cjs" }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "obug": ["obug@2.1.1", "", {}, ""], + + "openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "bin": "bin/cli" }, ""], + + "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], + + "partial-json": ["partial-json@0.1.7", "", {}, ""], + + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + + "protobufjs": ["protobufjs@7.6.2", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": "bin/cli.mjs" }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, ""], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "stackback": ["stackback@0.0.2", "", {}, ""], + + "std-env": ["std-env@4.1.0", "", {}, ""], + + "strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="], + + "tinybench": ["tinybench@2.9.0", "", {}, ""], + + "tinyexec": ["tinyexec@1.1.2", "", {}, ""], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "ts-algebra": ["ts-algebra@2.0.0", "", {}, ""], + + "tslib": ["tslib@2.8.1", "", {}, ""], + + "typebox": ["typebox@1.1.38", "", {}, "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, ""], + + "undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx"], "bin": "bin/vite.js" }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="], + + "vitest": ["vitest@4.1.8", "", { "dependencies": { "@vitest/expect": "4.1.8", "@vitest/mocker": "4.1.8", "@vitest/pretty-format": "4.1.8", "@vitest/runner": "4.1.8", "@vitest/snapshot": "4.1.8", "@vitest/spy": "4.1.8", "@vitest/utils": "4.1.8", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.8", "@vitest/browser-preview": "4.1.8", "@vitest/browser-webdriverio": "4.1.8", "@vitest/coverage-istanbul": "4.1.8", "@vitest/coverage-v8": "4.1.8", "@vitest/ui": "4.1.8", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": "vitest.mjs" }, "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": "cli.js" }, ""], + + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""], + + "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], + + "yaml": ["yaml@2.9.0", "", { "bin": "bin.mjs" }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "zod": ["zod@4.4.1", "", {}, ""], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, ""], + + "@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1062.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-fvHh53zSm2FoQPgkw9thH5D7sd13bC0nPyuZb+mQJ85l5v7lQnsZ97u6e6YkJJN/LU1Mxm1/DLGrIIRR2L7tZw=="], + + "@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.78.1", "", { "dependencies": { "@earendil-works/pi-ai": "^0.78.1", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" } }, ""], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.78.1", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", "@mistralai/mistralai": "2.2.1", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", "typebox": "1.1.38" }, "bin": { "pi-ai": "dist/cli.js" } }, ""], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.78.1", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "15.0.12" } }, ""], + + "@earendil-works/pi-coding-agent/diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "@earendil-works/pi-coding-agent/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@earendil-works/pi-coding-agent/typebox": ["typebox@1.1.38", "", {}, "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA=="], + + "@earendil-works/pi-coding-agent/yaml": ["yaml@2.9.0", "", { "bin": "bin.mjs" }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@mistralai/mistralai": ["@mistralai/mistralai@2.2.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" } }, "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "bin": "bin/cli" }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-tui/get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-tui/marked": ["marked@15.0.12", "", { "bin": "bin/marked.js" }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@anthropic-ai/sdk/json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core": ["@aws-sdk/core@3.974.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.24", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.42", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.37", "@aws-sdk/credential-provider-http": "^3.972.39", "@aws-sdk/credential-provider-ini": "^3.972.41", "@aws-sdk/credential-provider-process": "^3.972.37", "@aws-sdk/credential-provider-sso": "^3.972.41", "@aws-sdk/credential-provider-web-identity": "^3.972.41", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.16", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/core": ["@smithy/core@3.24.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs": ["protobufjs@7.5.9", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-Od4muIm3HW1AouyHF5lONOf1FWo3hY1NbFDoy191X9GzhpgW1clCoaFjfVs2rKJNFYpTNJbje4cbAIDBZJ63ZA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@mistralai/mistralai/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@mistralai/mistralai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@mistralai/mistralai/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/@smithy/core": ["@smithy/core@3.24.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/http-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@anthropic-ai/sdk/json-schema-to-ts/@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@anthropic-ai/sdk/json-schema-to-ts/ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-browser/@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-browser/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-browser/@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.24", "", { "dependencies": { "@nodable/entities": "2.1.0", "@smithy/types": "^4.14.1", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.37", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.39", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-env": "^3.972.37", "@aws-sdk/credential-provider-http": "^3.972.39", "@aws-sdk/credential-provider-login": "^3.972.41", "@aws-sdk/credential-provider-process": "^3.972.37", "@aws-sdk/credential-provider-sso": "^3.972.41", "@aws-sdk/credential-provider-web-identity": "^3.972.41", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.37", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/middleware-websocket/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.9", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/core/@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/p-retry/@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/inquire": ["@protobufjs/inquire@1.1.2", "", {}, "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/@smithy/core/@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/http-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/https-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.9", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.9", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.9", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/core/@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/ecdsa-sig-formatter/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gaxios/extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gcp-metadata/json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/jws/jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/jws/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/protobufjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/@smithy/core/@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/@smithy/core/@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/core/@aws-crypto/crc32/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gaxios/node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gaxios/node-fetch/fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gaxios/node-fetch/formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gcp-metadata/json-bigint/bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/jws/jwa/buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/@smithy/core/@aws-crypto/crc32/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/fast-xml-builder/xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/core/@aws-crypto/crc32/@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gaxios/node-fetch/fetch-blob/node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@google/genai/google-auth-library/gaxios/node-fetch/fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/@smithy/core/@aws-crypto/crc32/@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime/@smithy/core/@aws-crypto/crc32/@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@earendil-works/pi-coding-agent/@earendil-works/pi-ai/@smithy/node-http-handler/@smithy/core/@aws-crypto/crc32/@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + } +} diff --git a/src/index.ts b/index.ts similarity index 77% rename from src/index.ts rename to index.ts index 0756bad..ce9323a 100644 --- a/src/index.ts +++ b/index.ts @@ -14,6 +14,33 @@ import * as Diff from "diff"; import { Type } from "typebox"; import { writeFileAtomic } from "./write-file-atomic.js"; +export type ApplyPatchOperations = { + readFile: (absolutePath: string) => Promise; + writeFileAtomic: (absolutePath: string, content: string) => Promise; + mkdir: (directoryPath: string) => Promise; + rm: (absolutePath: string) => Promise; + stat: (absolutePath: string) => Promise; + realpath: (absolutePath: string) => Promise; +}; + +export type ApplyPatchToolOptions = { + operations?: ApplyPatchOperations; + getOperations?: () => ApplyPatchOperations | undefined; +}; + +const LOCAL_APPLY_PATCH_OPERATIONS: ApplyPatchOperations = { + readFile: (absolutePath) => readFile(absolutePath, "utf-8"), + writeFileAtomic, + mkdir: async (directoryPath) => { + await mkdir(directoryPath, { recursive: true }); + }, + rm: async (absolutePath) => { + await rm(absolutePath); + }, + stat: (absolutePath) => stat(absolutePath), + realpath: (absolutePath) => realpath(absolutePath), +}; + const APPLY_PATCH_PARAMS = Type.Object({ input: Type.String({ description: "The entire contents of the apply_patch command", @@ -42,10 +69,17 @@ type ApplyPatchToolDefinition = ToolDefinition void) => () => void; +}; + export type ApplyPatchExtensionAPI = Pick & { + events?: ApplyPatchEventBus; registerTool: (tool: ApplyPatchToolDefinition) => void; }; +const SSH_REMOTE_APPLY_PATCH_OPERATIONS_EVENT = "ssh-remote:apply-patch-operations"; + type ApplyPatchParams = { input: string; }; @@ -67,10 +101,32 @@ type ApplyPatchPreview = { removed: number; }; +/** + * Codex-style per-file change that gets persisted in the final tool result. + * + * Mirrors the shape of `FileChange` in openai/codex: only the hunks-only + * unified diff for updates (no full file content, no line numbers) and the + * touched file path for adds and deletes. This keeps the session file small + * (comparable to `edit` tool output) and lets the TUI re-render the diff + * from the final result on demand. + */ +export type ApplyPatchFileChange = + | { operation: "add"; filePath: string; added: number; removed: number } + | { operation: "delete"; filePath: string; added: number; removed: number } + | { + operation: "update"; + filePath: string; + movePath?: string; + unifiedDiff: string; + added: number; + removed: number; + }; + type ApplyPatchToolDetails = { preview?: ApplyPatchPreview; progress?: ApplyPatchProgress; result?: ApplyPatchResult; + changes?: ApplyPatchFileChange[]; }; type ApplyPatchProgress = { @@ -109,6 +165,7 @@ export type ApplyPatchResult = { failures: ApplyPatchFailure[]; hasPartialSuccess: boolean; recoveryInstructions: ApplyPatchRecoveryInstructions; + changes: ApplyPatchFileChange[]; details: { fuzz: number; }; @@ -175,7 +232,7 @@ function hasErrorCode(error: unknown, code: string): boolean { return Boolean(error && typeof error === "object" && "code" in error && error.code === code); } -const GPT_APPLY_PATCH_PROVIDERS = new Set(["openai", "openai-codex", "azure-openai-responses", "github-copilot"]); +const GPT_APPLY_PATCH_PROVIDERS = new Set(["openai", "openai-codex"]); export const PATCH_PREVIEW_MAX_LINES = 16; export const PATCH_PREVIEW_MAX_CHARS = 4000; const PATCH_PREVIEW_HEAD_LINES = 8; @@ -203,7 +260,7 @@ function applyLayeredBackground(theme: ApplyPatchTheme, bgName: ApplyPatchThemeB } function isChangedPreviewLine(line: string): boolean { - return /^[+-]\s*\d+\s/.test(line); + return /^[+-](?:\s*\d+\s|[^\s])/.test(line); } function countWindowLines(lines: string[], start: number, end: number): number { @@ -335,7 +392,7 @@ eof_line: "*** End of File" LF `; export function isOpenAIGptModel(model: Pick, "provider" | "id"> | undefined): boolean { - return model !== undefined && GPT_APPLY_PATCH_PROVIDERS.has(model.provider) && model.id.startsWith("gpt-"); + return model !== undefined && GPT_APPLY_PATCH_PROVIDERS.has(model.provider) && model.id.toLowerCase().startsWith("gpt-"); } function normalizePatchText(patchText: string): string { @@ -431,50 +488,56 @@ export function extractPatchedPaths(patchText: string): string[] { return Array.from(matches, (match) => match[1] ?? ""); } -function createPatchDiff(oldContent: string, newContent: string): { diff: string; added: number; removed: number } { - const parts = Diff.diffLines(oldContent, newContent); - const oldLines = oldContent.split("\n"); - const newLines = newContent.split("\n"); - const lineNumWidth = String(Math.max(oldLines.length, newLines.length)).length; +function countDiffLines(content: string): number { + if (content === "") return 0; + const lines = content.split("\n"); + if (lines[lines.length - 1] === "") { + lines.pop(); + } + return lines.length; +} + +/** + * Codex-style hunks-only unified diff (no line numbers, no full-file context). + * + * Produces the same format as Codex's @format_update_chunks_for_progress: + * ``@@ `` headers followed by ``-old`` / ``+new`` lines, with an + * optional ``*** End of File`` marker when the parser sets isEndOfFile. + */ +function formatUnifiedDiff(chunks: PatchChunk[]): { diff: string; added: number; removed: number } { const output: string[] = []; - let oldLineNum = 1; - let newLineNum = 1; let added = 0; let removed = 0; - - for (const part of parts) { - const rawLines = part.value.split("\n"); - if (rawLines[rawLines.length - 1] === "") { - rawLines.pop(); - } - - for (const line of rawLines) { - if (part.added) { - output.push(`+${String(newLineNum).padStart(lineNumWidth, " ")} ${line}`); - newLineNum++; - added++; - continue; - } - - if (part.removed) { - output.push(`-${String(oldLineNum).padStart(lineNumWidth, " ")} ${line}`); - oldLineNum++; - removed++; - continue; + for (const chunk of chunks) { + if (chunk.changeContexts.length > 0) { + for (const context of chunk.changeContexts) { + output.push(`@@ ${context}`); } - - output.push(` ${String(oldLineNum).padStart(lineNumWidth, " ")} ${line}`); - oldLineNum++; - newLineNum++; + } else { + output.push("@@"); + } + for (const line of chunk.oldLines) { + output.push(`-${line}`); + removed++; + } + for (const line of chunk.newLines) { + output.push(`+${line}`); + added++; + } + if (chunk.isEndOfFile) { + output.push("*** End of File"); } } - return { diff: output.join("\n"), added, removed }; } -async function readExistingFileForPreview(absolutePath: string): Promise { + +async function readExistingFileForPreview( + absolutePath: string, + operations: ApplyPatchOperations, +): Promise { try { - return await readFile(absolutePath, "utf-8"); + return await operations.readFile(absolutePath); } catch (error) { if (hasErrorCode(error, "ENOENT")) { return ""; @@ -483,6 +546,73 @@ async function readExistingFileForPreview(absolutePath: string): Promise } } +function formatApplyPatchChanges( + changes: ApplyPatchFileChange[], + cwd: string, + expanded: boolean, + theme: ApplyPatchTheme, +): string { + if (changes.length === 0) { + return ""; + } + + const lines: string[] = []; + const totalAdded = changes.reduce((sum, c) => sum + c.added, 0); + const totalRemoved = changes.reduce((sum, c) => sum + c.removed, 0); + + // Header + if (changes.length === 1) { + const change = changes[0]; + if (!change) return ""; + const op = formatPatchOperation(change.operation); + const fp = displayPath(change.filePath, cwd); + const count = `(+${change.added} -${change.removed})`; + const target = + change.operation === "update" && change.movePath + ? `${fp} → ${displayPath(change.movePath, cwd)}` + : fp; + lines.push(theme.fg("toolTitle", theme.bold(`${op} ${target}`))); + lines.push(` ${count}`); + } else { + lines.push( + theme.fg("toolTitle", theme.bold(`Edited ${changes.length} files`)), + ); + lines.push(` ${formatLineCountSummary(totalAdded, totalRemoved)}`); + for (const change of changes) { + const op = formatPatchOperation(change.operation); + const fp = displayPath(change.filePath, cwd); + const count = `(+${change.added} -${change.removed})`; + const target = + change.operation === "update" && change.movePath + ? `${fp} → ${displayPath(change.movePath, cwd)}` + : fp; + lines.push(` └ ${op} ${target} ${count}`); + } + } + + if (expanded) { + for (const change of changes) { + if (change.operation === "update" && change.unifiedDiff) { + const diffLines = truncatePreview(change.unifiedDiff).split("\n"); + for (const diffLine of diffLines) { + if (diffLine.startsWith("-")) { + lines.push(` ${theme.fg("toolDiffRemoved", diffLine)}`); + } else if (diffLine.startsWith("+")) { + lines.push(` ${theme.fg("toolDiffAdded", diffLine)}`); + } else if (diffLine.startsWith("@@") || diffLine.startsWith("***")) { + lines.push(` ${theme.fg("muted", diffLine)}`); + } else { + lines.push(` ${diffLine}`); + } + } + } + } + } + + return lines.join("\n"); +} + + function formatLineCountSummary(added: number, removed: number): string { return `(+${added} -${removed})`; } @@ -626,6 +756,20 @@ type RenderableContentDiffLine = RenderableAddedDiffLine | RenderableContextDiff type RenderableDiffLine = RenderableContentDiffLine | { kind: "meta"; text: string }; function parseRenderableDiffLine(line: string): RenderableDiffLine { + // Handle Codex-style format (no line numbers): +content, -content + const simpleMatch = line.match(/^([+\-])(?!\s*\d)(.*)$/); + if (simpleMatch) { + const sign = simpleMatch[1]; + const content = simpleMatch[2] ?? ""; + if (sign === "+") { + return { content, kind: "added", lineNumber: "", sign }; + } + if (sign === "-") { + return { content, kind: "removed", lineNumber: "", sign }; + } + } + + // Handle legacy format (line-numbered): +1 content, -1 content, 1 content const match = line.match(/^([+\- ])(\s*\d+)\s(.*)$/); if (!match) { return { kind: "meta", text: line }; @@ -714,9 +858,9 @@ function renderOpenCodeLikeDiffLine( theme: ApplyPatchTheme, contentOverride?: string, ): string { - const lineNumber = theme.fg("muted", line.lineNumber); + const lineNumberPart = line.lineNumber ? `${theme.fg("muted", line.lineNumber)} ` : ""; if (line.kind === "context") { - return `${theme.fg("toolDiffContext", line.sign)}${lineNumber} ${highlightDiffContent(line.content, filePath)}`; + return `${theme.fg("toolDiffContext", line.sign)}${lineNumberPart}${highlightDiffContent(line.content, filePath)}`; } const diffColor = line.kind === "added" ? "toolDiffAdded" : "toolDiffRemoved"; @@ -725,7 +869,7 @@ function renderOpenCodeLikeDiffLine( contentOverride === undefined ? highlightDiffContent(line.content, filePath) : theme.fg(diffColor, replaceTabs(contentOverride)); - const rendered = `${theme.fg(diffColor, line.sign)}${lineNumber} ${content}`; + const rendered = `${theme.fg(diffColor, line.sign)}${lineNumberPart}${content}`; return theme.bg(background, rendered); } @@ -862,33 +1006,52 @@ function formatPendingPatchPaths(patchText: string): string { return `Applying patch...\n${paths.map((filePath) => `• ${filePath}`).join("\n")}`; } -async function createPatchPreview(cwd: string, hunks: ParsedPatch[]): Promise { +async function createPatchPreview( + cwd: string, + hunks: ParsedPatch[], + operations: ApplyPatchOperations, +): Promise { const files: ApplyPatchPreviewFile[] = []; for (const hunk of hunks) { - const absolutePath = await resolvePatchPath(cwd, hunk.filePath); + const absolutePath = await resolvePatchPath(cwd, hunk.filePath, operations); if (hunk.type === "add") { - const oldContent = await readExistingFileForPreview(absolutePath); - const diff = createPatchDiff(oldContent, hunk.content); - files.push({ filePath: hunk.filePath, operation: oldContent.length > 0 ? "update" : "add", ...diff }); + const oldContent = await readExistingFileForPreview(absolutePath, operations); + if (oldContent.length > 0) { + // Overwriting existing file — render as update with a minimal inline diff. + const removed = splitFileLines(oldContent).length; + const added = countDiffLines(hunk.content); + const diff = [ + "@@", + ...oldContent.split("\n").filter((line) => line !== "").map((line) => `-${line}`), + ...hunk.content.split("\n").filter((line) => line !== "").map((line) => `+${line}`), + ].join("\n"); + files.push({ filePath: hunk.filePath, operation: "update", diff, added, removed }); + } else { + const added = countDiffLines(hunk.content); + files.push({ filePath: hunk.filePath, operation: "add", diff: "", added, removed: 0 }); + } continue; } if (hunk.type === "delete") { - const oldContent = await readFile(absolutePath, "utf-8"); - const diff = createPatchDiff(oldContent, ""); - files.push({ filePath: hunk.filePath, operation: "delete", ...diff }); + const oldContent = await operations.readFile(absolutePath); + const removed = splitFileLines(oldContent).length; + files.push({ filePath: hunk.filePath, operation: "delete", diff: "", added: 0, removed }); continue; } - const oldContent = await readFile(absolutePath, "utf-8"); - const newContent = - hunk.chunks.length === 0 ? oldContent : replaceChunks(oldContent, hunk.filePath, hunk.chunks).content; + const unified = formatUnifiedDiff(hunk.chunks); if (hunk.movePath) { - await resolvePatchPath(cwd, hunk.movePath); + await resolvePatchPath(cwd, hunk.movePath, operations); } - const diff = createPatchDiff(oldContent, newContent); - const file = { filePath: hunk.filePath, operation: "update", ...diff } satisfies ApplyPatchPreviewFile; - files.push(hunk.movePath !== undefined ? { ...file, movePath: hunk.movePath } : file); + files.push({ + filePath: hunk.filePath, + operation: "update", + diff: unified.diff, + added: unified.added, + removed: unified.removed, + ...(hunk.movePath !== undefined ? { movePath: hunk.movePath } : {}), + }); } return { @@ -1127,69 +1290,102 @@ function replaceChunks(content: string, filePath: string, chunks: PatchChunk[]): async function applySingleHunk( cwd: string, hunk: ParsedPatch, -): Promise<{ summary: string; appliedFile: string; fuzz: number }> { - const absolutePath = await resolvePatchPath(cwd, hunk.filePath); + operations: ApplyPatchOperations, +): Promise<{ + summary: string; + appliedFile: string; + fuzz: number; + change: ApplyPatchFileChange; +}> { + const absolutePath = await resolvePatchPath(cwd, hunk.filePath, operations); if (hunk.type === "add") { - await mkdir(path.dirname(absolutePath), { recursive: true }); - await writeFileAtomic(absolutePath, hunk.content); - return { summary: `add: ${hunk.filePath}`, appliedFile: hunk.filePath, fuzz: 0 }; + await operations.mkdir(path.dirname(absolutePath)); + await operations.writeFileAtomic(absolutePath, hunk.content); + const added = countDiffLines(hunk.content); + return { + summary: `add: ${hunk.filePath}`, + appliedFile: hunk.filePath, + fuzz: 0, + change: { operation: "add", filePath: hunk.filePath, added, removed: 0 }, + }; } if (hunk.type === "delete") { - await stat(absolutePath); - await rm(absolutePath); - return { summary: `delete: ${hunk.filePath}`, appliedFile: hunk.filePath, fuzz: 0 }; + await operations.stat(absolutePath); + const oldContent = await operations.readFile(absolutePath); + await operations.rm(absolutePath); + const removed = splitFileLines(oldContent).length; + return { + summary: `delete: ${hunk.filePath}`, + appliedFile: hunk.filePath, + fuzz: 0, + change: { operation: "delete", filePath: hunk.filePath, added: 0, removed }, + }; } - const currentContent = await readFile(absolutePath, "utf-8"); + const currentContent = await operations.readFile(absolutePath); const chunkResult = hunk.chunks.length === 0 ? { content: currentContent, fuzz: 0 } : replaceChunks(currentContent, hunk.filePath, hunk.chunks); const nextContent = chunkResult.content; + const unified = formatUnifiedDiff(hunk.chunks); + const change: ApplyPatchFileChange = { + operation: "update", + filePath: hunk.filePath, + unifiedDiff: unified.diff, + added: unified.added, + removed: unified.removed, + }; if (hunk.movePath) { - const absoluteMovePath = await resolvePatchPath(cwd, hunk.movePath); - await mkdir(path.dirname(absoluteMovePath), { recursive: true }); - await writeFileAtomic(absoluteMovePath, nextContent); + const absoluteMovePath = await resolvePatchPath(cwd, hunk.movePath, operations); + await operations.mkdir(path.dirname(absoluteMovePath)); + await operations.writeFileAtomic(absoluteMovePath, nextContent); if (absoluteMovePath !== absolutePath) { - await rm(absolutePath); + await operations.rm(absolutePath); } + change.movePath = hunk.movePath; return { summary: `move: ${hunk.filePath} -> ${hunk.movePath}`, appliedFile: hunk.movePath, fuzz: chunkResult.fuzz, + change, }; } - await writeFileAtomic(absolutePath, nextContent); - return { summary: `update: ${hunk.filePath}`, appliedFile: hunk.filePath, fuzz: chunkResult.fuzz }; + await operations.writeFileAtomic(absolutePath, nextContent); + return { summary: `update: ${hunk.filePath}`, appliedFile: hunk.filePath, fuzz: chunkResult.fuzz, change }; } export async function applyPatchDetailed( cwd: string, patchText: string, onProgress?: ApplyPatchProgressCallback, + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, ): Promise { - return applyParsedPatchDetailed(cwd, parseNonEmptyPatch(patchText), onProgress); + return applyParsedPatchDetailed(cwd, parseNonEmptyPatch(patchText), onProgress, operations); } async function applyParsedPatchDetailed( cwd: string, hunks: ParsedPatch[], onProgress?: ApplyPatchProgressCallback, + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, ): Promise { const summaries: string[] = []; const appliedFiles: string[] = []; const failures: ApplyPatchFailure[] = []; + const changes: ApplyPatchFileChange[] = []; let fuzz = 0; for (const hunk of hunks) { try { - const { summary, appliedFile, fuzz: hunkFuzz } = await applySingleHunk(cwd, hunk); + const { summary, appliedFile, fuzz: hunkFuzz, change } = await applySingleHunk(cwd, hunk, operations); summaries.push(summary); appliedFiles.push(appliedFile); fuzz += hunkFuzz; + changes.push(change); } catch (error) { const message = error instanceof Error ? error.message : String(error); failures.push({ filePath: hunk.filePath, operation: hunk.type, message }); @@ -1205,6 +1401,7 @@ async function applyParsedPatchDetailed( summaries, appliedFiles, failures, + changes, hasPartialSuccess: appliedFiles.length > 0 && failures.length > 0, recoveryInstructions: { mustReadFiles: [], mustNotReadFiles: [] }, details: { fuzz }, @@ -1222,22 +1419,29 @@ function createRecoveryInstructions( return { mustReadFiles, mustNotReadFiles }; } -export async function applyPatch(cwd: string, patchText: string): Promise { +export async function applyPatch( + cwd: string, + patchText: string, + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, +): Promise { const hunks = parseNonEmptyPatch(patchText); const summaries: string[] = []; const appliedFiles: string[] = []; + const changes: ApplyPatchFileChange[] = []; for (const hunk of hunks) { try { - const { summary, appliedFile } = await applySingleHunk(cwd, hunk); + const { summary, appliedFile, change } = await applySingleHunk(cwd, hunk, operations); summaries.push(summary); appliedFiles.push(appliedFile); + changes.push(change); } catch (error) { const message = error instanceof Error ? error.message : String(error); const failure = { filePath: hunk.filePath, operation: hunk.type, message } satisfies ApplyPatchFailure; const result: ApplyPatchResult = { summaries, appliedFiles, + changes, failures: [failure], hasPartialSuccess: appliedFiles.length > 0, recoveryInstructions: createRecoveryInstructions({ @@ -1259,6 +1463,7 @@ async function createPendingPatchUpdate( progress?: ApplyPatchProgress, previewOverride?: ApplyPatchPreview, parsedHunks?: ParsedPatch[], + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, ): Promise<{ text: string; details: ApplyPatchToolDetails | undefined }> { const title = progress ? `Applying patch (${progress.applied + progress.failed}/${progress.total})...` @@ -1278,7 +1483,7 @@ async function createPendingPatchUpdate( return { text: title, details: progress ? { progress } : undefined }; } - const preview = await createPatchPreview(cwd, hunks); + const preview = await createPatchPreview(cwd, hunks, operations); if (preview.files.some((file) => file.diff.trim().length > 0)) { const details: ApplyPatchToolDetails = { preview }; if (progress) details.progress = progress; @@ -1317,11 +1522,15 @@ function isPathWithinWorkspace(workspacePath: string, candidatePath: string): bo ); } -async function findExistingAncestor(directoryPath: string, workspacePath: string): Promise { +async function findExistingAncestor( + directoryPath: string, + workspacePath: string, + operations: ApplyPatchOperations, +): Promise { let currentPath = directoryPath; while (isPathWithinWorkspace(workspacePath, currentPath)) { try { - await stat(currentPath); + await operations.stat(currentPath); return currentPath; } catch (error) { if (!hasErrorCode(error, "ENOENT")) { @@ -1339,15 +1548,19 @@ async function findExistingAncestor(directoryPath: string, workspacePath: string throw new PatchApplicationError(`Patch path escapes workspace: ${directoryPath}`); } -async function resolvePatchPath(cwd: string, filePath: string): Promise { - const workspacePath = await realpath(cwd); +async function resolvePatchPath( + cwd: string, + filePath: string, + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, +): Promise { + const workspacePath = await operations.realpath(cwd); const absolutePath = path.resolve(workspacePath, filePath); if (!isPathWithinWorkspace(workspacePath, absolutePath)) { throw new PatchApplicationError(`Patch path escapes workspace: ${filePath}`); } - const existingAncestor = await findExistingAncestor(path.dirname(absolutePath), workspacePath); - const realAncestor = await realpath(existingAncestor); + const existingAncestor = await findExistingAncestor(path.dirname(absolutePath), workspacePath, operations); + const realAncestor = await operations.realpath(existingAncestor); if (!isPathWithinWorkspace(workspacePath, realAncestor)) { throw new PatchApplicationError(`Patch path escapes workspace: ${filePath}`); } @@ -1368,17 +1581,24 @@ function syncToolset( pi.setActiveTools(replaceApplyPatchWithEditTools(currentToolNames)); } -export function createApplyPatchTool(): ApplyPatchToolDefinition { +export function createApplyPatchTool(options: ApplyPatchToolOptions = {}): ApplyPatchToolDefinition { const tool = defineTool({ name: "apply_patch", label: "ApplyPatch", description: APPLY_PATCH_FREEFORM_DESCRIPTION, parameters: APPLY_PATCH_PARAMS, prepareArguments: normalizeApplyPatchArguments, - promptSnippet: "Apply Codex-format file patches with apply_patch", + promptSnippet: + "Use the `apply_patch` tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`).", promptGuidelines: [ - "Use apply_patch for file edits instead of mutating files through bash, Python scripts, heredocs, or shell redirection.", - "After apply_patch succeeds, do not re-read the edited files just to confirm the patch applied.", + "Patches MUST begin with `*** Begin Patch` and end with `*** End Patch`.", + "Use one of these operation headers per file: `*** Add File: `, `*** Delete File: `, `*** Update File: `.", + "Every added line must start with `+`, removed lines with `-`, unchanged context with a single space.", + "Use `*** Move to: ` directly after `*** Update File:` to rename; use `*** End of File` only when the hunk reaches actual EOF.", + "Use `@@` (optionally with a class/function header) when 3 lines of context are insufficient to uniquely identify the snippet.", + "File references MUST be relative — never absolute.", + "Do not re-read files after `apply_patch` succeeds.", + "Do not edit files via bash, Python, or heredocs when `apply_patch` is available.", ], async execute( _toolCallId, @@ -1392,6 +1612,7 @@ export function createApplyPatchTool(): ApplyPatchToolDefinition { throw new Error("input is required"); } + const operations = options.getOperations?.() ?? options.operations ?? LOCAL_APPLY_PATCH_OPERATIONS; let parsedHunks: ParsedPatch[] | undefined; try { parsedHunks = parseNonEmptyPatch(normalizedParams.input); @@ -1406,6 +1627,7 @@ export function createApplyPatchTool(): ApplyPatchToolDefinition { initialProgress, undefined, parsedHunks, + operations, ); onUpdate?.({ content: [{ type: "text", text: pendingUpdate.text }], @@ -1423,12 +1645,14 @@ export function createApplyPatchTool(): ApplyPatchToolDefinition { progress, preview, parsedHunks, + operations, ); onUpdate?.({ content: [{ type: "text", text: progressUpdate.text }], details: progressUpdate.details, }); }, + operations, ); if (result.failures.length > 0) { const mustReadFiles = result.recoveryInstructions.mustReadFiles; @@ -1453,13 +1677,13 @@ export function createApplyPatchTool(): ApplyPatchToolDefinition { .join("\n"), }, ], - details: { result }, + details: { result, changes: result.changes }, }; } return { content: [{ type: "text", text: result.summaries.join("\n") }], - details: { result }, + details: { result, changes: result.changes }, }; }, renderCall(args, theme, context) { @@ -1474,7 +1698,21 @@ export function createApplyPatchTool(): ApplyPatchToolDefinition { }, renderResult(result, options, theme, context) { const component = new Container(); + const changes = result.details?.changes; const preview = result.details?.preview; + + // Final (settled) result: render from Codex-style changes when available. + if (changes && !options.isPartial) { + const bgName = "toolSuccessBg"; + const box = new Box(1, 1, (text: string) => applyLayeredBackground(theme, bgName, text)); + box.addChild(new Text(theme.fg("toolTitle", theme.bold("Applied patch")), 0, 0)); + box.addChild(new Spacer(1)); + box.addChild(new Text(formatApplyPatchChanges(changes, context.cwd, options.expanded ?? true, theme), 0, 0)); + component.addChild(box); + return component; + } + + // Streaming / partial result: render from preview (full-file diff with line numbers). if (preview) { const bgName = options.isPartial ? "toolPendingBg" : "toolSuccessBg"; const progress = result.details?.progress; @@ -1512,7 +1750,17 @@ export function createApplyPatchTool(): ApplyPatchToolDefinition { } export function registerApplyPatchExtension(pi: ApplyPatchExtensionAPI): void { - pi.registerTool(createApplyPatchTool()); + let remoteOperations: ApplyPatchOperations | undefined; + pi.events?.on(SSH_REMOTE_APPLY_PATCH_OPERATIONS_EVENT, (data) => { + if (typeof data !== "object" || data === null) { + remoteOperations = undefined; + return; + } + remoteOperations = + (data as { operations?: ApplyPatchOperations | null }).operations ?? undefined; + }); + + pi.registerTool(createApplyPatchTool({ getOperations: () => remoteOperations })); pi.on("session_start", async (_event, ctx) => { syncToolset(pi, ctx.model); diff --git a/package.json b/package.json index b1a5e0a..130f79d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ ], "pi": { "extensions": [ - "./src/index.ts" + "./index.ts" ] }, "scripts": { diff --git a/test/index.test.ts b/test/index.test.ts index 62fab9f..9d760d4 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,10 +1,11 @@ -import { mkdir, mkdtemp, readdir, readFile, rm, symlink, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readdir, readFile, realpath, rm, stat, symlink, writeFile } from "node:fs/promises"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { APPLY_PATCH_FREEFORM_DESCRIPTION, APPLY_PATCH_LARK_GRAMMAR, type ApplyPatchExtensionAPI, + type ApplyPatchOperations, applyPatch, applyPatchDetailed, createApplyPatchTool, @@ -14,8 +15,8 @@ import { PatchParseError, registerApplyPatchExtension, truncatePreview, -} from "../src/index.js"; -import { writeFileAtomic } from "../src/write-file-atomic.js"; +} from "../index.js"; +import { writeFileAtomic } from "../write-file-atomic.js"; const tempDirectories: string[] = []; const identityTheme = { @@ -213,6 +214,45 @@ describe("pi-apply-patch", () => { expect(await readFile(path.join(directory, "sample.txt"), "utf-8")).toBe("after\n"); }); + it("#given remote filesystem operations #when apply_patch executes #then writes only through the remote backend", async () => { + // given + const localDirectory = await createTempDirectory(); + const remoteDirectory = await createTempDirectory(); + await writeFile(path.join(remoteDirectory, "sample.txt"), "before\n", "utf-8"); + const operations: ApplyPatchOperations = { + readFile: (absolutePath) => readFile(absolutePath, "utf-8"), + writeFileAtomic, + mkdir: async (directoryPath) => { + await mkdir(directoryPath, { recursive: true }); + }, + rm: async (absolutePath) => { + await rm(absolutePath); + }, + stat: (absolutePath) => stat(absolutePath), + realpath: async (absolutePath) => + absolutePath === localDirectory ? realpath(remoteDirectory) : realpath(absolutePath), + }; + const patch = `*** Begin Patch +*** Update File: sample.txt +@@ +-before ++after +*** End Patch`; + + // when + await createApplyPatchTool({ operations }).execute( + "apply-patch-remote-operations-test", + { input: patch }, + undefined, + undefined, + { cwd: localDirectory } as never, + ); + + // then + expect(await readFile(path.join(remoteDirectory, "sample.txt"), "utf-8")).toBe("after\n"); + await expect(readFile(path.join(localDirectory, "sample.txt"), "utf-8")).rejects.toThrow(); + }); + it("#given parent traversal path #when applying patch #then rejects outside workspace", async () => { // given const directory = await createTempDirectory(); @@ -282,10 +322,9 @@ describe("pi-apply-patch", () => { } expect(update.text).toContain("Applying patch (0/2)...\n• Edited 2 files (+2 -1)"); expect(update.text).toContain("sample.txt (+1 -1)"); - expect(update.text).toContain("-1 before"); - expect(update.text).toContain("+1 after"); + expect(update.text).toContain("-before"); + expect(update.text).toContain("+after"); expect(update.text).toContain("created.txt (+1 -0)"); - expect(update.text).toContain("+1 created"); expect(update.text).not.toContain("Index:"); const component = tool.renderResult?.( @@ -298,7 +337,7 @@ describe("pi-apply-patch", () => { expect(rendered).toContain("Applying patch"); expect(rendered).toContain("• Edited 2 files (+2 -1)"); expect(rendered).toContain("sample.txt (+1 -1)"); - expect(rendered).toContain("+1 after"); + expect(rendered).toContain("+after"); expect(rendered).not.toContain("Index:"); }); @@ -365,8 +404,8 @@ describe("pi-apply-patch", () => { ); // then - expect(updates[0]).toContain("-30 line-30"); - expect(updates[0]).toContain("+30 line-30 updated"); + expect(updates[0]).toContain("-line-30"); + expect(updates[0]).toContain("+line-30 updated"); expect(updates[0]).not.toContain(" 1 line-1"); expect(await readFile(path.join(directory, "large.txt"), "utf-8")).toContain("line-30 updated"); }); @@ -484,8 +523,8 @@ describe("pi-apply-patch", () => { // then expect(updates[0]).toContain("• Edited existing.txt (+1 -1)"); - expect(updates[0]).toContain("-1 old"); - expect(updates[0]).toContain("+1 new"); + expect(updates[0]).toContain("-old"); + expect(updates[0]).toContain("+new"); expect(await readFile(path.join(directory, "existing.txt"), "utf-8")).toBe("new\n"); }); diff --git a/test/render.test.ts b/test/render.test.ts index 6990197..f531254 100644 --- a/test/render.test.ts +++ b/test/render.test.ts @@ -8,7 +8,7 @@ import { PATCH_PREVIEW_MAX_CHARS, PATCH_PREVIEW_MAX_LINES, truncatePreview, -} from "../src/index.js"; +} from "../index.js"; const identityTheme = { fg: (_name: string, text: string) => text, diff --git a/tsconfig.json b/tsconfig.json index d3aeaf9..618b4d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,5 @@ "types": ["node"], "noEmit": true }, - "include": ["src/**/*", "test/**/*"] + "include": ["*.ts", "test/**/*"] } diff --git a/src/write-file-atomic.ts b/write-file-atomic.ts similarity index 100% rename from src/write-file-atomic.ts rename to write-file-atomic.ts From 31e6e1640c9285412183edd2ca8f7450f6c3275f Mon Sep 17 00:00:00 2001 From: AMagicPear Date: Wed, 15 Jul 2026 13:16:25 +0800 Subject: [PATCH 2/3] add local provider configuration --- README.md | 12 ++++++++++++ index.ts | 28 ++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67bf685..97a06b3 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,18 @@ pi -e /path/to/pi-apply-patch/index.ts After installation, restart pi or run `/reload` inside an interactive session. +### Private provider configuration + +The extension enables `openai` and `openai-codex` by default. To enable a private provider only on your machine, create `~/.pi/agent/pi-apply-patch.json`: + +```json +{ + "providers": ["openai", "openai-codex", "suda-mkt"] +} +``` + +The file is optional and is never part of the package or repository. Missing or invalid configuration falls back to the standard providers. Only models whose IDs start with `gpt-` activate `apply_patch`. + ## Development ```bash diff --git a/index.ts b/index.ts index ce9323a..f3cc7ca 100644 --- a/index.ts +++ b/index.ts @@ -1,10 +1,12 @@ import { mkdir, readFile, realpath, rm, stat } from "node:fs/promises"; +import { readFileSync } from "node:fs"; import path from "node:path"; import type { AgentToolResult } from "@earendil-works/pi-agent-core"; import type { Model } from "@earendil-works/pi-ai"; import { defineTool, type ExtensionAPI, + getAgentDir, getLanguageFromPath, highlightCode, type ToolDefinition, @@ -232,7 +234,29 @@ function hasErrorCode(error: unknown, code: string): boolean { return Boolean(error && typeof error === "object" && "code" in error && error.code === code); } -const GPT_APPLY_PATCH_PROVIDERS = new Set(["openai", "openai-codex"]); +const DEFAULT_GPT_APPLY_PATCH_PROVIDERS = ["openai", "openai-codex"] as const; +export const APPLY_PATCH_LOCAL_CONFIG_PATH = path.join(getAgentDir(), "pi-apply-patch.json"); + +type ApplyPatchLocalConfig = { + providers?: unknown; +}; + +function getGPTApplyPatchProviders(): Set { + try { + const config = JSON.parse(readFileSync(APPLY_PATCH_LOCAL_CONFIG_PATH, "utf8")) as ApplyPatchLocalConfig; + if (Array.isArray(config.providers)) { + const providers = config.providers.filter( + (provider): provider is string => typeof provider === "string" && provider.length > 0, + ); + if (providers.length > 0) { + return new Set(providers); + } + } + } catch { + // Missing or invalid local config falls back to standard providers. + } + return new Set(DEFAULT_GPT_APPLY_PATCH_PROVIDERS); +} export const PATCH_PREVIEW_MAX_LINES = 16; export const PATCH_PREVIEW_MAX_CHARS = 4000; const PATCH_PREVIEW_HEAD_LINES = 8; @@ -392,7 +416,7 @@ eof_line: "*** End of File" LF `; export function isOpenAIGptModel(model: Pick, "provider" | "id"> | undefined): boolean { - return model !== undefined && GPT_APPLY_PATCH_PROVIDERS.has(model.provider) && model.id.toLowerCase().startsWith("gpt-"); + return model !== undefined && getGPTApplyPatchProviders().has(model.provider) && model.id.toLowerCase().startsWith("gpt-"); } function normalizePatchText(patchText: string): string { From 44087174b377a26c2e91f6e78447ebdb38761e6e Mon Sep 17 00:00:00 2001 From: AMagicPear Date: Wed, 15 Jul 2026 13:19:43 +0800 Subject: [PATCH 3/3] preserve src layout with root extension entry --- index.ts | 1803 +---------------- src/index.ts | 1802 ++++++++++++++++ .../write-file-atomic.ts | 0 test/index.test.ts | 4 +- test/render.test.ts | 2 +- tsconfig.json | 2 +- 6 files changed, 1807 insertions(+), 1806 deletions(-) create mode 100644 src/index.ts rename write-file-atomic.ts => src/write-file-atomic.ts (100%) diff --git a/index.ts b/index.ts index f3cc7ca..0fdc642 100644 --- a/index.ts +++ b/index.ts @@ -1,1802 +1 @@ -import { mkdir, readFile, realpath, rm, stat } from "node:fs/promises"; -import { readFileSync } from "node:fs"; -import path from "node:path"; -import type { AgentToolResult } from "@earendil-works/pi-agent-core"; -import type { Model } from "@earendil-works/pi-ai"; -import { - defineTool, - type ExtensionAPI, - getAgentDir, - getLanguageFromPath, - highlightCode, - type ToolDefinition, -} from "@earendil-works/pi-coding-agent"; -import { Box, Container, Spacer, Text } from "@earendil-works/pi-tui"; -import * as Diff from "diff"; -import { Type } from "typebox"; -import { writeFileAtomic } from "./write-file-atomic.js"; - -export type ApplyPatchOperations = { - readFile: (absolutePath: string) => Promise; - writeFileAtomic: (absolutePath: string, content: string) => Promise; - mkdir: (directoryPath: string) => Promise; - rm: (absolutePath: string) => Promise; - stat: (absolutePath: string) => Promise; - realpath: (absolutePath: string) => Promise; -}; - -export type ApplyPatchToolOptions = { - operations?: ApplyPatchOperations; - getOperations?: () => ApplyPatchOperations | undefined; -}; - -const LOCAL_APPLY_PATCH_OPERATIONS: ApplyPatchOperations = { - readFile: (absolutePath) => readFile(absolutePath, "utf-8"), - writeFileAtomic, - mkdir: async (directoryPath) => { - await mkdir(directoryPath, { recursive: true }); - }, - rm: async (absolutePath) => { - await rm(absolutePath); - }, - stat: (absolutePath) => stat(absolutePath), - realpath: (absolutePath) => realpath(absolutePath), -}; - -const APPLY_PATCH_PARAMS = Type.Object({ - input: Type.String({ - description: "The entire contents of the apply_patch command", - }), -}); - -type ParsedPatch = - | { type: "add"; filePath: string; content: string } - | { type: "delete"; filePath: string } - | { type: "update"; filePath: string; movePath?: string; chunks: PatchChunk[] }; - -type PatchChunk = { - changeContexts: string[]; - oldLines: string[]; - newLines: string[]; - isEndOfFile: boolean; -}; - -export type FreeformToolFormat = { - type: "grammar"; - syntax: "lark"; - definition: string; -}; - -type ApplyPatchToolDefinition = ToolDefinition & { - freeform: FreeformToolFormat; -}; - -type ApplyPatchEventBus = { - on: (channel: string, handler: (data: unknown) => void) => () => void; -}; - -export type ApplyPatchExtensionAPI = Pick & { - events?: ApplyPatchEventBus; - registerTool: (tool: ApplyPatchToolDefinition) => void; -}; - -const SSH_REMOTE_APPLY_PATCH_OPERATIONS_EVENT = "ssh-remote:apply-patch-operations"; - -type ApplyPatchParams = { - input: string; -}; - -type ApplyPatchOperation = "add" | "delete" | "update"; - -type ApplyPatchPreviewFile = { - filePath: string; - movePath?: string; - operation: ApplyPatchOperation; - diff: string; - added: number; - removed: number; -}; - -type ApplyPatchPreview = { - files: ApplyPatchPreviewFile[]; - added: number; - removed: number; -}; - -/** - * Codex-style per-file change that gets persisted in the final tool result. - * - * Mirrors the shape of `FileChange` in openai/codex: only the hunks-only - * unified diff for updates (no full file content, no line numbers) and the - * touched file path for adds and deletes. This keeps the session file small - * (comparable to `edit` tool output) and lets the TUI re-render the diff - * from the final result on demand. - */ -export type ApplyPatchFileChange = - | { operation: "add"; filePath: string; added: number; removed: number } - | { operation: "delete"; filePath: string; added: number; removed: number } - | { - operation: "update"; - filePath: string; - movePath?: string; - unifiedDiff: string; - added: number; - removed: number; - }; - -type ApplyPatchToolDetails = { - preview?: ApplyPatchPreview; - progress?: ApplyPatchProgress; - result?: ApplyPatchResult; - changes?: ApplyPatchFileChange[]; -}; - -type ApplyPatchProgress = { - applied: number; - failed: number; - total: number; -}; - -type ApplyPatchProgressCallback = (progress: ApplyPatchProgress) => Promise | void; - -async function notifyApplyPatchProgress( - onProgress: ApplyPatchProgressCallback | undefined, - progress: ApplyPatchProgress, -): Promise { - try { - await onProgress?.(progress); - } catch { - // Rendering progress must not affect patch application or recovery details. - } -} - -export type ApplyPatchFailure = { - filePath: string; - operation: ApplyPatchOperation; - message: string; -}; - -export type ApplyPatchRecoveryInstructions = { - mustReadFiles: string[]; - mustNotReadFiles: string[]; -}; - -export type ApplyPatchResult = { - summaries: string[]; - appliedFiles: string[]; - failures: ApplyPatchFailure[]; - hasPartialSuccess: boolean; - recoveryInstructions: ApplyPatchRecoveryInstructions; - changes: ApplyPatchFileChange[]; - details: { - fuzz: number; - }; -}; - -export class ApplyPatchError extends Error { - public readonly failures: ApplyPatchFailure[]; - public readonly result: ApplyPatchResult; - - constructor(message: string, result: ApplyPatchResult) { - super(message); - this.name = "ApplyPatchError"; - this.failures = result.failures; - this.result = result; - } - - hasPartialSuccess(): boolean { - return this.result.hasPartialSuccess; - } -} - -export class PatchParseError extends Error { - constructor(message: string) { - super(message); - this.name = "PatchParseError"; - } -} - -export class PatchApplicationError extends Error { - constructor(message: string) { - super(message); - this.name = "PatchApplicationError"; - } -} - -type ApplyPatchRenderState = { - cwd: string; - patchText: string; - callText: string; - collapsed: string; - expanded: string; -}; - -type ApplyPatchThemeColor = - | "accent" - | "error" - | "muted" - | "toolDiffAdded" - | "toolDiffContext" - | "toolDiffRemoved" - | "toolOutput" - | "toolTitle"; - -type ApplyPatchThemeBg = "toolErrorBg" | "toolPendingBg" | "toolSuccessBg"; - -type ApplyPatchTheme = { - fg: (name: ApplyPatchThemeColor, text: string) => string; - bg: (name: ApplyPatchThemeBg, text: string) => string; - bold: (text: string) => string; - inverse: (text: string) => string; -}; - -function hasErrorCode(error: unknown, code: string): boolean { - return Boolean(error && typeof error === "object" && "code" in error && error.code === code); -} - -const DEFAULT_GPT_APPLY_PATCH_PROVIDERS = ["openai", "openai-codex"] as const; -export const APPLY_PATCH_LOCAL_CONFIG_PATH = path.join(getAgentDir(), "pi-apply-patch.json"); - -type ApplyPatchLocalConfig = { - providers?: unknown; -}; - -function getGPTApplyPatchProviders(): Set { - try { - const config = JSON.parse(readFileSync(APPLY_PATCH_LOCAL_CONFIG_PATH, "utf8")) as ApplyPatchLocalConfig; - if (Array.isArray(config.providers)) { - const providers = config.providers.filter( - (provider): provider is string => typeof provider === "string" && provider.length > 0, - ); - if (providers.length > 0) { - return new Set(providers); - } - } - } catch { - // Missing or invalid local config falls back to standard providers. - } - return new Set(DEFAULT_GPT_APPLY_PATCH_PROVIDERS); -} -export const PATCH_PREVIEW_MAX_LINES = 16; -export const PATCH_PREVIEW_MAX_CHARS = 4000; -const PATCH_PREVIEW_HEAD_LINES = 8; -const PATCH_PREVIEW_TAIL_LINES = PATCH_PREVIEW_MAX_LINES - PATCH_PREVIEW_HEAD_LINES - 1; -const PATCH_PREVIEW_TRUNCATION_MARKER = "…"; -const applyPatchRenderStates = new Map(); - -function applyLayeredBackground(theme: ApplyPatchTheme, bgName: ApplyPatchThemeBg, text: string): string { - const marker = "\x1fpi-bg-marker\x1f"; - const wrappedMarker = theme.bg(bgName, marker); - const markerIndex = wrappedMarker.indexOf(marker); - if (markerIndex === -1) { - return theme.bg(bgName, text); - } - - const bgStart = wrappedMarker.slice(0, markerIndex); - const bgEnd = wrappedMarker.slice(markerIndex + marker.length); - const restored = text.replace(/\x1b\[([0-9;]*)m/g, (sequence: string, params: string) => { - if (params === "" || params.split(";").some((param) => param === "0" || param === "49")) { - return `${sequence}${bgStart}`; - } - return sequence; - }); - return `${bgStart}${restored}${bgEnd}`; -} - -function isChangedPreviewLine(line: string): boolean { - return /^[+-](?:\s*\d+\s|[^\s])/.test(line); -} - -function countWindowLines(lines: string[], start: number, end: number): number { - return end - start + (start > 0 ? 1 : 0) + (end < lines.length ? 1 : 0); -} - -function formatPreviewWindow(lines: string[], start: number, end: number): string { - const previewLines = lines.slice(start, end); - if (start > 0) { - previewLines.unshift("…"); - } - if (end < lines.length) { - previewLines.push("…"); - } - return previewLines.join("\n"); -} - -function createChangedHunkPreview(lines: string[]): string | undefined { - const firstChangedLine = lines.findIndex(isChangedPreviewLine); - if (firstChangedLine === -1) { - return undefined; - } - - let start = firstChangedLine; - let end = firstChangedLine + 1; - while (end < lines.length) { - const line = lines[end]; - if (line === undefined || !isChangedPreviewLine(line)) { - break; - } - end++; - } - - const changedHunkEnd = end; - while (end > start && countWindowLines(lines, start, end) > PATCH_PREVIEW_MAX_LINES) { - end--; - } - - while (countWindowLines(lines, start, end) < PATCH_PREVIEW_MAX_LINES) { - const canAddBefore = start > 0; - const canAddAfter = end < lines.length; - if (!canAddBefore && !canAddAfter) { - break; - } - - const beforeContextLines = firstChangedLine - start; - const afterContextLines = end - changedHunkEnd; - if (canAddBefore && (!canAddAfter || beforeContextLines <= afterContextLines)) { - start--; - } else { - end++; - } - } - - return formatPreviewWindow(lines, start, end); -} - -function countLines(text: string): number { - if (text.length === 0) { - return 0; - } - let lines = 1; - for (let index = 0; index < text.length; index++) { - if (text.charCodeAt(index) === 10) { - lines += 1; - } - } - return lines; -} - -function enforcePreviewCharLimit(preview: string): string { - if (preview.length <= PATCH_PREVIEW_MAX_CHARS) { - return preview; - } - - return `${preview.slice(0, PATCH_PREVIEW_MAX_CHARS - PATCH_PREVIEW_TRUNCATION_MARKER.length).trimEnd()}${PATCH_PREVIEW_TRUNCATION_MARKER}`; -} - -export function truncatePreview(text: string): string { - if (text.length <= PATCH_PREVIEW_MAX_CHARS && countLines(text) <= PATCH_PREVIEW_MAX_LINES) { - return text; - } - - const lines = text.split("\n"); - const changedHunkPreview = createChangedHunkPreview(lines); - const previewText = - changedHunkPreview ?? - [...lines.slice(0, PATCH_PREVIEW_HEAD_LINES), "…", ...lines.slice(-PATCH_PREVIEW_TAIL_LINES)].join("\n"); - return enforcePreviewCharLimit(previewText); -} - -function normalizeApplyPatchArguments(args: unknown): ApplyPatchParams { - if (typeof args === "string") { - return { input: args }; - } - - if (args && typeof args === "object" && "input" in args) { - const input = (args as { input?: unknown }).input; - if (typeof input === "string") { - return { input }; - } - } - - return { input: "" }; -} - -const STANDARD_EDIT_TOOL_NAMES = ["edit", "write"] as const; -export const APPLY_PATCH_FREEFORM_DESCRIPTION = - "Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON."; -export const APPLY_PATCH_LARK_GRAMMAR = `start: begin_patch hunk+ end_patch -begin_patch: "*** Begin Patch" LF -end_patch: "*** End Patch" LF? - -hunk: add_hunk | delete_hunk | update_hunk -add_hunk: "*** Add File: " filename LF add_line+ -delete_hunk: "*** Delete File: " filename LF -update_hunk: "*** Update File: " filename LF change_move? change? - -filename: /(.+)/ -add_line: "+" /(.*)/ LF -> line - -change_move: "*** Move to: " filename LF -change: (change_context | change_line)+ eof_line? -change_context: ("@@" | "@@ " /(.+)/) LF -change_line: ("+" | "-" | " ") /(.*)/ LF -eof_line: "*** End of File" LF - -%import common.LF -`; - -export function isOpenAIGptModel(model: Pick, "provider" | "id"> | undefined): boolean { - return model !== undefined && getGPTApplyPatchProviders().has(model.provider) && model.id.toLowerCase().startsWith("gpt-"); -} - -function normalizePatchText(patchText: string): string { - return patchText.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); -} - -function stripHeredoc(input: string): string { - const heredocMatch = input.match(/^(?:cat\s+)?<<['"]?(\w+)['"]?\s*\n([\s\S]*?)\n\1\s*$/); - if (heredocMatch) { - return heredocMatch[2] ?? input; - } - return input; -} - -function normalizeSeekLine(line: string): string { - return line - .trim() - .replace(/[‐‑‒–—―−]/g, "-") - .replace(/[‘’‚‛]/g, "'") - .replace(/[“”„‟]/g, '"') - .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " "); -} - -function seekSequence( - lines: string[], - pattern: string[], - start: number, - eof: boolean, -): { index: number; fuzz: 0 | 1 | 100 | 10000 } | undefined { - if (pattern.length === 0) { - return { index: start, fuzz: 0 }; - } - if (pattern.length > lines.length) { - return undefined; - } - - const searchStart = eof && lines.length >= pattern.length ? lines.length - pattern.length : start; - const lastStart = lines.length - pattern.length; - const matches = (index: number, compare: (left: string, right: string) => boolean): boolean => { - for (let patternIndex = 0; patternIndex < pattern.length; patternIndex++) { - const line = lines[index + patternIndex]; - const expected = pattern[patternIndex]; - if (line === undefined || expected === undefined || !compare(line, expected)) { - return false; - } - } - return true; - }; - const matchesPrepared = (index: number, preparedLines: string[], preparedPattern: string[]): boolean => { - for (let patternIndex = 0; patternIndex < preparedPattern.length; patternIndex++) { - const line = preparedLines[index + patternIndex]; - const expected = preparedPattern[patternIndex]; - if (line === undefined || expected === undefined || line !== expected) { - return false; - } - } - return true; - }; - - for (let index = searchStart; index <= lastStart; index++) { - if (matches(index, (line, expected) => line === expected)) { - return { index, fuzz: 0 }; - } - } - const linesTrimEnd = lines.map((line) => line.trimEnd()); - const patternTrimEnd = pattern.map((line) => line.trimEnd()); - for (let index = searchStart; index <= lastStart; index++) { - if (matchesPrepared(index, linesTrimEnd, patternTrimEnd)) { - return { index, fuzz: 1 }; - } - } - const linesTrim = lines.map((line) => line.trim()); - const patternTrim = pattern.map((line) => line.trim()); - for (let index = searchStart; index <= lastStart; index++) { - if (matchesPrepared(index, linesTrim, patternTrim)) { - return { index, fuzz: 100 }; - } - } - const linesNormalized = lines.map(normalizeSeekLine); - const patternNormalized = pattern.map(normalizeSeekLine); - for (let index = searchStart; index <= lastStart; index++) { - if (matchesPrepared(index, linesNormalized, patternNormalized)) { - return { index, fuzz: 10000 }; - } - } - - return undefined; -} - -export function extractPatchedPaths(patchText: string): string[] { - const normalized = stripHeredoc(normalizePatchText(patchText)); - const matches = normalized.matchAll(/^\*\*\* (?:(?:Add|Delete|Update) File|Move to): (.+)$/gm); - return Array.from(matches, (match) => match[1] ?? ""); -} - -function countDiffLines(content: string): number { - if (content === "") return 0; - const lines = content.split("\n"); - if (lines[lines.length - 1] === "") { - lines.pop(); - } - return lines.length; -} - -/** - * Codex-style hunks-only unified diff (no line numbers, no full-file context). - * - * Produces the same format as Codex's @format_update_chunks_for_progress: - * ``@@ `` headers followed by ``-old`` / ``+new`` lines, with an - * optional ``*** End of File`` marker when the parser sets isEndOfFile. - */ -function formatUnifiedDiff(chunks: PatchChunk[]): { diff: string; added: number; removed: number } { - const output: string[] = []; - let added = 0; - let removed = 0; - for (const chunk of chunks) { - if (chunk.changeContexts.length > 0) { - for (const context of chunk.changeContexts) { - output.push(`@@ ${context}`); - } - } else { - output.push("@@"); - } - for (const line of chunk.oldLines) { - output.push(`-${line}`); - removed++; - } - for (const line of chunk.newLines) { - output.push(`+${line}`); - added++; - } - if (chunk.isEndOfFile) { - output.push("*** End of File"); - } - } - return { diff: output.join("\n"), added, removed }; -} - - -async function readExistingFileForPreview( - absolutePath: string, - operations: ApplyPatchOperations, -): Promise { - try { - return await operations.readFile(absolutePath); - } catch (error) { - if (hasErrorCode(error, "ENOENT")) { - return ""; - } - throw error; - } -} - -function formatApplyPatchChanges( - changes: ApplyPatchFileChange[], - cwd: string, - expanded: boolean, - theme: ApplyPatchTheme, -): string { - if (changes.length === 0) { - return ""; - } - - const lines: string[] = []; - const totalAdded = changes.reduce((sum, c) => sum + c.added, 0); - const totalRemoved = changes.reduce((sum, c) => sum + c.removed, 0); - - // Header - if (changes.length === 1) { - const change = changes[0]; - if (!change) return ""; - const op = formatPatchOperation(change.operation); - const fp = displayPath(change.filePath, cwd); - const count = `(+${change.added} -${change.removed})`; - const target = - change.operation === "update" && change.movePath - ? `${fp} → ${displayPath(change.movePath, cwd)}` - : fp; - lines.push(theme.fg("toolTitle", theme.bold(`${op} ${target}`))); - lines.push(` ${count}`); - } else { - lines.push( - theme.fg("toolTitle", theme.bold(`Edited ${changes.length} files`)), - ); - lines.push(` ${formatLineCountSummary(totalAdded, totalRemoved)}`); - for (const change of changes) { - const op = formatPatchOperation(change.operation); - const fp = displayPath(change.filePath, cwd); - const count = `(+${change.added} -${change.removed})`; - const target = - change.operation === "update" && change.movePath - ? `${fp} → ${displayPath(change.movePath, cwd)}` - : fp; - lines.push(` └ ${op} ${target} ${count}`); - } - } - - if (expanded) { - for (const change of changes) { - if (change.operation === "update" && change.unifiedDiff) { - const diffLines = truncatePreview(change.unifiedDiff).split("\n"); - for (const diffLine of diffLines) { - if (diffLine.startsWith("-")) { - lines.push(` ${theme.fg("toolDiffRemoved", diffLine)}`); - } else if (diffLine.startsWith("+")) { - lines.push(` ${theme.fg("toolDiffAdded", diffLine)}`); - } else if (diffLine.startsWith("@@") || diffLine.startsWith("***")) { - lines.push(` ${theme.fg("muted", diffLine)}`); - } else { - lines.push(` ${diffLine}`); - } - } - } - } - } - - return lines.join("\n"); -} - - -function formatLineCountSummary(added: number, removed: number): string { - return `(+${added} -${removed})`; -} - -function formatPatchFileSummary(file: ApplyPatchPreviewFile, cwd: string): string { - return `${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`; -} - -function formatPatchFileHeader(file: ApplyPatchPreviewFile, cwd: string): string { - return `• ${formatPatchOperation(file.operation)} ${formatPatchFileSummary(file, cwd)}`; -} - -function normalizeDisplayPath(filePath: string): string { - return filePath.replaceAll(path.sep, "/"); -} - -export function displayPath(filePath: string, cwd: string): string { - if (!path.isAbsolute(filePath)) { - return normalizeDisplayPath(filePath); - } - - const absoluteCwd = path.resolve(cwd); - const relativePath = path.relative(absoluteCwd, filePath); - if ( - relativePath === "" || - (!relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath)) - ) { - return normalizeDisplayPath(relativePath || "."); - } - - return normalizeDisplayPath(filePath); -} - -export function formatPatchFilePath(file: ApplyPatchPreviewFile, cwd: string = process.cwd()): string { - const filePath = displayPath(file.filePath, cwd); - if (!file.movePath) { - return filePath; - } - return `${filePath} → ${displayPath(file.movePath, cwd)}`; -} - -function formatPatchOperation(operation: ApplyPatchOperation): string { - if (operation === "add") { - return "Added"; - } - if (operation === "delete") { - return "Deleted"; - } - return "Edited"; -} - -export function formatPatchPreview( - preview: ApplyPatchPreview, - cwd: string = process.cwd(), - expanded: boolean = true, -): string { - const lines: string[] = []; - if (preview.files.length === 1) { - const file = preview.files[0]; - if (file) { - lines.push(formatPatchFileHeader(file, cwd)); - if (expanded && file.diff) { - lines.push( - ...truncatePreview(file.diff) - .split("\n") - .map((line) => ` ${line}`), - ); - } - } - return lines.join("\n"); - } - - const noun = "files"; - lines.push(`• Edited ${preview.files.length} ${noun} ${formatLineCountSummary(preview.added, preview.removed)}`); - for (const file of preview.files) { - lines.push(` └ ${formatPatchFileSummary(file, cwd)}`); - if (expanded && file.diff) { - lines.push( - ...truncatePreview(file.diff) - .split("\n") - .map((line) => ` ${line}`), - ); - } - } - return lines.join("\n"); -} - -function getApplyPatchRenderState(toolCallId: string, cwd: string, patchText: string): ApplyPatchRenderState { - const existing = applyPatchRenderStates.get(toolCallId); - if (existing && existing.cwd === cwd && existing.patchText === patchText) { - return existing; - } - - const callText = formatInFlightCallText(patchText); - let collapsed = ""; - let expanded = ""; - try { - const hunks = parsePatch(patchText); - if (hunks.length > 0) { - const files = hunks.map((hunk) => { - const file = { - filePath: hunk.filePath, - operation: hunk.type, - diff: "", - added: 0, - removed: 0, - } satisfies ApplyPatchPreviewFile; - return hunk.type === "update" && hunk.movePath !== undefined ? { ...file, movePath: hunk.movePath } : file; - }) satisfies ApplyPatchPreviewFile[]; - const preview: ApplyPatchPreview = { files, added: 0, removed: 0 }; - collapsed = formatPatchPreview(preview, cwd, false); - expanded = formatPatchPreview(preview, cwd, true); - } - } catch { - // leave summaries empty for partial/incomplete patch text - } - - const nextState: ApplyPatchRenderState = { cwd, patchText, callText, collapsed, expanded }; - applyPatchRenderStates.set(toolCallId, nextState); - return nextState; -} - -export function clearApplyPatchRenderState(): void { - applyPatchRenderStates.clear(); -} - -export function formatInFlightCallText(patchText: string): string { - const paths = extractPatchedPaths(patchText); - if (paths.length === 0) { - return "Patching"; - } - const noun = paths.length === 1 ? "file" : "files"; - const count = paths.length > 1 ? ` (${paths.length} ${noun})` : ""; - return `Patching${count}: ${paths.join(", ")}`; -} - -type RenderableAddedDiffLine = { content: string; kind: "added"; lineNumber: string; sign: "+" }; -type RenderableRemovedDiffLine = { content: string; kind: "removed"; lineNumber: string; sign: "-" }; -type RenderableContextDiffLine = { content: string; kind: "context"; lineNumber: string; sign: " " }; -type RenderableContentDiffLine = RenderableAddedDiffLine | RenderableContextDiffLine | RenderableRemovedDiffLine; -type RenderableDiffLine = RenderableContentDiffLine | { kind: "meta"; text: string }; - -function parseRenderableDiffLine(line: string): RenderableDiffLine { - // Handle Codex-style format (no line numbers): +content, -content - const simpleMatch = line.match(/^([+\-])(?!\s*\d)(.*)$/); - if (simpleMatch) { - const sign = simpleMatch[1]; - const content = simpleMatch[2] ?? ""; - if (sign === "+") { - return { content, kind: "added", lineNumber: "", sign }; - } - if (sign === "-") { - return { content, kind: "removed", lineNumber: "", sign }; - } - } - - // Handle legacy format (line-numbered): +1 content, -1 content, 1 content - const match = line.match(/^([+\- ])(\s*\d+)\s(.*)$/); - if (!match) { - return { kind: "meta", text: line }; - } - - const sign = match[1]; - const lineNumber = match[2]; - if ((sign !== "+" && sign !== "-" && sign !== " ") || lineNumber === undefined) { - return { kind: "meta", text: line }; - } - - const content = match[3] ?? ""; - if (sign === "+") { - return { content, kind: "added", lineNumber, sign }; - } - if (sign === "-") { - return { content, kind: "removed", lineNumber, sign }; - } - return { content, kind: "context", lineNumber, sign }; -} - -function replaceTabs(text: string): string { - return text.replace(/\t/g, " "); -} - -function highlightDiffContent(content: string, filePath: string): string { - const plainContent = replaceTabs(content); - const language = getLanguageFromPath(filePath); - try { - return highlightCode(plainContent, language)[0] ?? plainContent; - } catch { - return plainContent; - } -} - -function renderInlineDiff( - oldContent: string, - newContent: string, - theme: ApplyPatchTheme, -): { added: string; removed: string } { - const parts = Diff.diffWords(replaceTabs(oldContent), replaceTabs(newContent)); - let added = ""; - let removed = ""; - let firstAdded = true; - let firstRemoved = true; - - for (const part of parts) { - if (part.added) { - let value = part.value; - if (firstAdded) { - const leadingWhitespace = value.match(/^(\s*)/)?.[1] ?? ""; - added += leadingWhitespace; - value = value.slice(leadingWhitespace.length); - firstAdded = false; - } - if (value) { - added += theme.inverse(value); - } - continue; - } - - if (part.removed) { - let value = part.value; - if (firstRemoved) { - const leadingWhitespace = value.match(/^(\s*)/)?.[1] ?? ""; - removed += leadingWhitespace; - value = value.slice(leadingWhitespace.length); - firstRemoved = false; - } - if (value) { - removed += theme.inverse(value); - } - continue; - } - - added += part.value; - removed += part.value; - } - - return { added, removed }; -} - -function renderOpenCodeLikeDiffLine( - line: RenderableContentDiffLine, - filePath: string, - theme: ApplyPatchTheme, - contentOverride?: string, -): string { - const lineNumberPart = line.lineNumber ? `${theme.fg("muted", line.lineNumber)} ` : ""; - if (line.kind === "context") { - return `${theme.fg("toolDiffContext", line.sign)}${lineNumberPart}${highlightDiffContent(line.content, filePath)}`; - } - - const diffColor = line.kind === "added" ? "toolDiffAdded" : "toolDiffRemoved"; - const background = line.kind === "added" ? "toolSuccessBg" : "toolErrorBg"; - const content = - contentOverride === undefined - ? highlightDiffContent(line.content, filePath) - : theme.fg(diffColor, replaceTabs(contentOverride)); - const rendered = `${theme.fg(diffColor, line.sign)}${lineNumberPart}${content}`; - return theme.bg(background, rendered); -} - -function renderOpenCodeLikeDiff(diffText: string, filePath: string, theme: ApplyPatchTheme): string { - const parsedLines = diffText.split("\n").map(parseRenderableDiffLine); - const rendered: string[] = []; - let index = 0; - - while (index < parsedLines.length) { - const line = parsedLines[index]; - if (!line) { - index++; - continue; - } - - if (line.kind !== "removed") { - rendered.push( - line.kind === "meta" - ? theme.fg("toolDiffContext", line.text) - : renderOpenCodeLikeDiffLine(line, filePath, theme), - ); - index++; - continue; - } - - const removedLines: RenderableRemovedDiffLine[] = []; - while (parsedLines[index]?.kind === "removed") { - const removedLine = parsedLines[index]; - if (removedLine?.kind === "removed") { - removedLines.push(removedLine); - } - index++; - } - - const addedLines: RenderableAddedDiffLine[] = []; - while (parsedLines[index]?.kind === "added") { - const addedLine = parsedLines[index]; - if (addedLine?.kind === "added") { - addedLines.push(addedLine); - } - index++; - } - - const pairedCount = Math.min(removedLines.length, addedLines.length); - for (let pairIndex = 0; pairIndex < pairedCount; pairIndex++) { - const removedLine = removedLines[pairIndex]; - const addedLine = addedLines[pairIndex]; - if (!removedLine || !addedLine) { - continue; - } - - const inline = renderInlineDiff(removedLine.content, addedLine.content, theme); - rendered.push(renderOpenCodeLikeDiffLine(removedLine, filePath, theme, inline.removed)); - rendered.push(renderOpenCodeLikeDiffLine(addedLine, filePath, theme, inline.added)); - } - - for (const removedLine of removedLines.slice(pairedCount)) { - rendered.push(renderOpenCodeLikeDiffLine(removedLine, filePath, theme)); - } - for (const addedLine of addedLines.slice(pairedCount)) { - rendered.push(renderOpenCodeLikeDiffLine(addedLine, filePath, theme)); - } - } - - return rendered.join("\n"); -} - -function renderPatchPreview( - preview: ApplyPatchPreview, - cwd: string, - theme: ApplyPatchTheme, - expanded: boolean, -): string { - if (expanded) { - try { - const renderFile = (file: ApplyPatchPreviewFile, headerPrefix: string): string => { - const header = formatPatchFileHeader(file, cwd); - if (!file.diff) { - return headerPrefix.length > 0 ? `${headerPrefix}${formatPatchFileSummary(file, cwd)}` : header; - } - const previewDiff = truncatePreview(file.diff); - const renderedDiff = renderOpenCodeLikeDiff(previewDiff, file.movePath ?? file.filePath, theme); - if (headerPrefix.length > 0) { - const nestedHeader = `${headerPrefix}${formatPatchFileSummary(file, cwd)}`; - return `${nestedHeader}\n${renderedDiff - .split("\n") - .map((line) => ` ${line}`) - .join("\n")}`; - } - return `${header}\n${renderedDiff}`; - }; - - if (preview.files.length === 1) { - const file = preview.files[0]; - return file ? renderFile(file, "") : ""; - } - - const noun = "files"; - const renderedFiles = preview.files.map((file) => renderFile(file, " └ ")).join("\n"); - if (renderedFiles.length > 0) { - return `• Edited ${preview.files.length} ${noun} ${formatLineCountSummary(preview.added, preview.removed)}\n${renderedFiles}`; - } - } catch { - // fall back to manual themed line rendering - } - } - - return formatPatchPreview(preview, cwd, expanded) - .split("\n") - .map((line) => { - const trimmed = line.trimStart(); - if (trimmed.startsWith("+")) { - return theme.fg("toolDiffAdded", line); - } - if (trimmed.startsWith("-")) { - return theme.fg("toolDiffRemoved", line); - } - if (trimmed.startsWith("•")) { - return theme.fg("toolTitle", theme.bold(line)); - } - if (trimmed.startsWith("└")) { - return theme.fg("accent", line); - } - return theme.fg("toolDiffContext", line); - }) - .join("\n"); -} - -function formatPendingPatchPaths(patchText: string): string { - const paths = extractPatchedPaths(patchText); - if (paths.length === 0) { - return "Applying patch..."; - } - return `Applying patch...\n${paths.map((filePath) => `• ${filePath}`).join("\n")}`; -} - -async function createPatchPreview( - cwd: string, - hunks: ParsedPatch[], - operations: ApplyPatchOperations, -): Promise { - const files: ApplyPatchPreviewFile[] = []; - for (const hunk of hunks) { - const absolutePath = await resolvePatchPath(cwd, hunk.filePath, operations); - if (hunk.type === "add") { - const oldContent = await readExistingFileForPreview(absolutePath, operations); - if (oldContent.length > 0) { - // Overwriting existing file — render as update with a minimal inline diff. - const removed = splitFileLines(oldContent).length; - const added = countDiffLines(hunk.content); - const diff = [ - "@@", - ...oldContent.split("\n").filter((line) => line !== "").map((line) => `-${line}`), - ...hunk.content.split("\n").filter((line) => line !== "").map((line) => `+${line}`), - ].join("\n"); - files.push({ filePath: hunk.filePath, operation: "update", diff, added, removed }); - } else { - const added = countDiffLines(hunk.content); - files.push({ filePath: hunk.filePath, operation: "add", diff: "", added, removed: 0 }); - } - continue; - } - - if (hunk.type === "delete") { - const oldContent = await operations.readFile(absolutePath); - const removed = splitFileLines(oldContent).length; - files.push({ filePath: hunk.filePath, operation: "delete", diff: "", added: 0, removed }); - continue; - } - - const unified = formatUnifiedDiff(hunk.chunks); - if (hunk.movePath) { - await resolvePatchPath(cwd, hunk.movePath, operations); - } - files.push({ - filePath: hunk.filePath, - operation: "update", - diff: unified.diff, - added: unified.added, - removed: unified.removed, - ...(hunk.movePath !== undefined ? { movePath: hunk.movePath } : {}), - }); - } - - return { - files, - added: files.reduce((sum, file) => sum + file.added, 0), - removed: files.reduce((sum, file) => sum + file.removed, 0), - }; -} - -function parsePatch(patchText: string): ParsedPatch[] { - const normalized = stripHeredoc(normalizePatchText(patchText).trim()).trim(); - const lines = normalized.split("\n"); - const beginIndex = lines[0]?.trim() === "*** Begin Patch" ? 0 : -1; - const lastLine = lines[lines.length - 1]; - const endIndex = lastLine?.trim() === "*** End Patch" ? lines.length - 1 : -1; - - if (beginIndex === -1 || endIndex === -1 || endIndex < beginIndex) { - throw new PatchParseError("Invalid patch format: expected *** Begin Patch ... *** End Patch envelope"); - } - - const hunks: ParsedPatch[] = []; - let index = beginIndex + 1; - while (index < endIndex) { - const line = lines[index] ?? ""; - if (!line.startsWith("*** ")) { - index++; - continue; - } - - if (line.startsWith("*** Add File: ")) { - const filePath = line.slice("*** Add File: ".length); - index++; - const contentLines: string[] = []; - while (index < endIndex) { - const nextLine = lines[index] ?? ""; - if (nextLine.startsWith("*** ")) { - break; - } - if (!nextLine.startsWith("+")) { - throw new PatchParseError(`Invalid patch format: Add File lines must start with '+'`); - } - contentLines.push(nextLine.slice(1)); - index++; - } - hunks.push({ - type: "add", - filePath, - content: contentLines.length === 0 ? "" : `${contentLines.join("\n")}\n`, - }); - continue; - } - - if (line.startsWith("*** Delete File: ")) { - hunks.push({ type: "delete", filePath: line.slice("*** Delete File: ".length) }); - index++; - continue; - } - - if (line.startsWith("*** Update File: ")) { - const filePath = line.slice("*** Update File: ".length); - index++; - let movePath: string | undefined; - if ((lines[index] ?? "").startsWith("*** Move to: ")) { - movePath = (lines[index] ?? "").slice("*** Move to: ".length); - index++; - } - - const chunks: PatchChunk[] = []; - while (index < endIndex) { - const nextLine = lines[index] ?? ""; - if (nextLine.trim() === "") { - index++; - continue; - } - if (nextLine.startsWith("*** ")) { - break; - } - - const allowMissingContext = chunks.length === 0; - const changeContexts: string[] = []; - if (nextLine.startsWith("@@")) { - while (index < endIndex) { - const contextLine = lines[index] ?? ""; - if (contextLine === "@@") { - index++; - continue; - } - if (contextLine.startsWith("@@ ")) { - changeContexts.push(contextLine.slice("@@ ".length)); - index++; - continue; - } - break; - } - } else if (!allowMissingContext) { - throw new PatchParseError(`Expected update hunk to start with a @@ context marker, got: '${nextLine}'`); - } - - const oldLines: string[] = []; - const newLines: string[] = []; - let isEndOfFile = false; - let parsedLines = 0; - while (index < endIndex) { - const hunkLine = lines[index] ?? ""; - if (hunkLine === "*** End of File") { - if (parsedLines === 0) { - throw new PatchParseError("Update hunk does not contain any lines"); - } - isEndOfFile = true; - index++; - break; - } - if (hunkLine.startsWith("@@") || hunkLine.startsWith("*** ")) { - break; - } - const prefix = hunkLine[0]; - const value = hunkLine.slice(1); - if (prefix === undefined) { - oldLines.push(""); - newLines.push(""); - } else if (prefix === " ") { - oldLines.push(value); - newLines.push(value); - } else if (prefix === "-") { - oldLines.push(value); - } else if (prefix === "+") { - newLines.push(value); - } else if (parsedLines > 0) { - break; - } else { - throw new PatchParseError( - `Unexpected line found in update hunk: '${hunkLine}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`, - ); - } - parsedLines++; - index++; - } - - if (parsedLines === 0) { - throw new PatchParseError("Update hunk does not contain any lines"); - } - chunks.push({ changeContexts, oldLines, newLines, isEndOfFile }); - } - if (chunks.length === 0 && !movePath) { - throw new PatchParseError(`Update file hunk for path '${filePath}' is empty`); - } - - hunks.push( - movePath !== undefined - ? { type: "update", filePath, movePath, chunks } - : { type: "update", filePath, chunks }, - ); - continue; - } - - throw new PatchParseError( - `'${line}' is not a valid hunk header. Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'`, - ); - } - - return hunks; -} - -function parseNonEmptyPatch(patchText: string): ParsedPatch[] { - const hunks = parsePatch(patchText); - if (hunks.length > 0) { - return hunks; - } - - const normalized = normalizePatchText(patchText).trim(); - if (normalized === "*** Begin Patch\n*** End Patch") { - throw new PatchParseError("patch rejected: empty patch"); - } - throw new PatchParseError("apply_patch verification failed: no hunks found"); -} - -function splitFileLines(content: string): string[] { - const lines = normalizePatchText(content).split("\n"); - if (lines[lines.length - 1] === "") { - lines.pop(); - } - return lines; -} - -function replaceChunks(content: string, filePath: string, chunks: PatchChunk[]): { content: string; fuzz: number } { - const originalLines = splitFileLines(content); - const replacements: { start: number; oldLength: number; newLines: string[] }[] = []; - let lineIndex = 0; - let fuzz = 0; - - for (const chunk of chunks) { - for (const changeContext of chunk.changeContexts) { - const contextMatch = seekSequence(originalLines, [changeContext], lineIndex, false); - if (contextMatch === undefined) { - throw new PatchApplicationError(`Failed to find context '${changeContext}' in ${filePath}`); - } - fuzz += contextMatch.fuzz; - lineIndex = contextMatch.index + 1; - } - - if (chunk.oldLines.length === 0) { - const insertionIndex = - originalLines[originalLines.length - 1] === "" ? originalLines.length - 1 : originalLines.length; - replacements.push({ start: insertionIndex, oldLength: 0, newLines: chunk.newLines }); - continue; - } - - let pattern = chunk.oldLines; - let newLines = chunk.newLines; - let foundAt = seekSequence(originalLines, pattern, lineIndex, chunk.isEndOfFile); - if (foundAt === undefined && pattern[pattern.length - 1] === "") { - pattern = pattern.slice(0, -1); - if (newLines[newLines.length - 1] === "") { - newLines = newLines.slice(0, -1); - } - foundAt = seekSequence(originalLines, pattern, lineIndex, chunk.isEndOfFile); - } - - if (foundAt === undefined) { - throw new PatchApplicationError(`Failed to find expected lines in ${filePath}:\n${chunk.oldLines.join("\n")}`); - } - - fuzz += foundAt.fuzz; - replacements.push({ start: foundAt.index, oldLength: pattern.length, newLines }); - lineIndex = foundAt.index + pattern.length; - } - - const nextLines = [...originalLines]; - for (const replacement of replacements.sort((left, right) => right.start - left.start)) { - nextLines.splice(replacement.start, replacement.oldLength, ...replacement.newLines); - } - nextLines.push(""); - return { content: nextLines.join("\n"), fuzz }; -} - -async function applySingleHunk( - cwd: string, - hunk: ParsedPatch, - operations: ApplyPatchOperations, -): Promise<{ - summary: string; - appliedFile: string; - fuzz: number; - change: ApplyPatchFileChange; -}> { - const absolutePath = await resolvePatchPath(cwd, hunk.filePath, operations); - if (hunk.type === "add") { - await operations.mkdir(path.dirname(absolutePath)); - await operations.writeFileAtomic(absolutePath, hunk.content); - const added = countDiffLines(hunk.content); - return { - summary: `add: ${hunk.filePath}`, - appliedFile: hunk.filePath, - fuzz: 0, - change: { operation: "add", filePath: hunk.filePath, added, removed: 0 }, - }; - } - - if (hunk.type === "delete") { - await operations.stat(absolutePath); - const oldContent = await operations.readFile(absolutePath); - await operations.rm(absolutePath); - const removed = splitFileLines(oldContent).length; - return { - summary: `delete: ${hunk.filePath}`, - appliedFile: hunk.filePath, - fuzz: 0, - change: { operation: "delete", filePath: hunk.filePath, added: 0, removed }, - }; - } - - const currentContent = await operations.readFile(absolutePath); - const chunkResult = - hunk.chunks.length === 0 - ? { content: currentContent, fuzz: 0 } - : replaceChunks(currentContent, hunk.filePath, hunk.chunks); - const nextContent = chunkResult.content; - const unified = formatUnifiedDiff(hunk.chunks); - const change: ApplyPatchFileChange = { - operation: "update", - filePath: hunk.filePath, - unifiedDiff: unified.diff, - added: unified.added, - removed: unified.removed, - }; - - if (hunk.movePath) { - const absoluteMovePath = await resolvePatchPath(cwd, hunk.movePath, operations); - await operations.mkdir(path.dirname(absoluteMovePath)); - await operations.writeFileAtomic(absoluteMovePath, nextContent); - if (absoluteMovePath !== absolutePath) { - await operations.rm(absolutePath); - } - change.movePath = hunk.movePath; - return { - summary: `move: ${hunk.filePath} -> ${hunk.movePath}`, - appliedFile: hunk.movePath, - fuzz: chunkResult.fuzz, - change, - }; - } - - await operations.writeFileAtomic(absolutePath, nextContent); - return { summary: `update: ${hunk.filePath}`, appliedFile: hunk.filePath, fuzz: chunkResult.fuzz, change }; -} - -export async function applyPatchDetailed( - cwd: string, - patchText: string, - onProgress?: ApplyPatchProgressCallback, - operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, -): Promise { - return applyParsedPatchDetailed(cwd, parseNonEmptyPatch(patchText), onProgress, operations); -} - -async function applyParsedPatchDetailed( - cwd: string, - hunks: ParsedPatch[], - onProgress?: ApplyPatchProgressCallback, - operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, -): Promise { - const summaries: string[] = []; - const appliedFiles: string[] = []; - const failures: ApplyPatchFailure[] = []; - const changes: ApplyPatchFileChange[] = []; - let fuzz = 0; - - for (const hunk of hunks) { - try { - const { summary, appliedFile, fuzz: hunkFuzz, change } = await applySingleHunk(cwd, hunk, operations); - summaries.push(summary); - appliedFiles.push(appliedFile); - fuzz += hunkFuzz; - changes.push(change); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - failures.push({ filePath: hunk.filePath, operation: hunk.type, message }); - } - await notifyApplyPatchProgress(onProgress, { - applied: appliedFiles.length, - failed: failures.length, - total: hunks.length, - }); - } - - const result: ApplyPatchResult = { - summaries, - appliedFiles, - failures, - changes, - hasPartialSuccess: appliedFiles.length > 0 && failures.length > 0, - recoveryInstructions: { mustReadFiles: [], mustNotReadFiles: [] }, - details: { fuzz }, - }; - result.recoveryInstructions = createRecoveryInstructions(result); - return result; -} - -function createRecoveryInstructions( - result: Pick, -): ApplyPatchRecoveryInstructions { - const mustReadFiles = [...new Set(result.failures.map((failure) => failure.filePath))]; - const mustReadFileSet = new Set(mustReadFiles); - const mustNotReadFiles = [...new Set(result.appliedFiles.filter((filePath) => !mustReadFileSet.has(filePath)))]; - return { mustReadFiles, mustNotReadFiles }; -} - -export async function applyPatch( - cwd: string, - patchText: string, - operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, -): Promise { - const hunks = parseNonEmptyPatch(patchText); - - const summaries: string[] = []; - const appliedFiles: string[] = []; - const changes: ApplyPatchFileChange[] = []; - for (const hunk of hunks) { - try { - const { summary, appliedFile, change } = await applySingleHunk(cwd, hunk, operations); - summaries.push(summary); - appliedFiles.push(appliedFile); - changes.push(change); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - const failure = { filePath: hunk.filePath, operation: hunk.type, message } satisfies ApplyPatchFailure; - const result: ApplyPatchResult = { - summaries, - appliedFiles, - changes, - failures: [failure], - hasPartialSuccess: appliedFiles.length > 0, - recoveryInstructions: createRecoveryInstructions({ - appliedFiles, - failures: [failure], - }), - details: { fuzz: 0 }, - }; - throw new ApplyPatchError(message, result); - } - } - - return summaries; -} - -async function createPendingPatchUpdate( - cwd: string, - patchText: string, - progress?: ApplyPatchProgress, - previewOverride?: ApplyPatchPreview, - parsedHunks?: ParsedPatch[], - operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, -): Promise<{ text: string; details: ApplyPatchToolDetails | undefined }> { - const title = progress - ? `Applying patch (${progress.applied + progress.failed}/${progress.total})...` - : "Applying patch..."; - if (previewOverride) { - const details: ApplyPatchToolDetails = { preview: previewOverride }; - if (progress) details.progress = progress; - return { - text: `${title}\n${formatPatchPreview(previewOverride, cwd)}`, - details, - }; - } - - try { - const hunks = parsedHunks ?? parsePatch(patchText); - if (hunks.length === 0) { - return { text: title, details: progress ? { progress } : undefined }; - } - - const preview = await createPatchPreview(cwd, hunks, operations); - if (preview.files.some((file) => file.diff.trim().length > 0)) { - const details: ApplyPatchToolDetails = { preview }; - if (progress) details.progress = progress; - return { text: `${title}\n${formatPatchPreview(preview, cwd)}`, details }; - } - } catch { - return { - text: progress ? title : formatPendingPatchPaths(patchText), - details: progress ? { progress } : undefined, - }; - } - - return { text: progress ? title : formatPendingPatchPaths(patchText), details: progress ? { progress } : undefined }; -} - -function withoutExtensionManagedEditTools(toolNames: string[]): string[] { - return toolNames.filter( - (toolName) => - toolName !== "apply_patch" && !STANDARD_EDIT_TOOL_NAMES.some((editToolName) => editToolName === toolName), - ); -} - -function replaceEditToolsWithApplyPatch(toolNames: string[]): string[] { - return [...withoutExtensionManagedEditTools(toolNames), "apply_patch"]; -} - -function replaceApplyPatchWithEditTools(toolNames: string[]): string[] { - return [...withoutExtensionManagedEditTools(toolNames), ...STANDARD_EDIT_TOOL_NAMES]; -} - -function isPathWithinWorkspace(workspacePath: string, candidatePath: string): boolean { - const relativePath = path.relative(workspacePath, candidatePath); - return ( - relativePath === "" || - (!relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath)) - ); -} - -async function findExistingAncestor( - directoryPath: string, - workspacePath: string, - operations: ApplyPatchOperations, -): Promise { - let currentPath = directoryPath; - while (isPathWithinWorkspace(workspacePath, currentPath)) { - try { - await operations.stat(currentPath); - return currentPath; - } catch (error) { - if (!hasErrorCode(error, "ENOENT")) { - throw error; - } - } - - const parentPath = path.dirname(currentPath); - if (parentPath === currentPath) { - break; - } - currentPath = parentPath; - } - - throw new PatchApplicationError(`Patch path escapes workspace: ${directoryPath}`); -} - -async function resolvePatchPath( - cwd: string, - filePath: string, - operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, -): Promise { - const workspacePath = await operations.realpath(cwd); - const absolutePath = path.resolve(workspacePath, filePath); - if (!isPathWithinWorkspace(workspacePath, absolutePath)) { - throw new PatchApplicationError(`Patch path escapes workspace: ${filePath}`); - } - - const existingAncestor = await findExistingAncestor(path.dirname(absolutePath), workspacePath, operations); - const realAncestor = await operations.realpath(existingAncestor); - if (!isPathWithinWorkspace(workspacePath, realAncestor)) { - throw new PatchApplicationError(`Patch path escapes workspace: ${filePath}`); - } - - return absolutePath; -} - -function syncToolset( - pi: Pick, - model: Model | undefined, -): void { - const currentToolNames = pi.getActiveTools(); - if (isOpenAIGptModel(model)) { - pi.setActiveTools(replaceEditToolsWithApplyPatch(currentToolNames)); - return; - } - - pi.setActiveTools(replaceApplyPatchWithEditTools(currentToolNames)); -} - -export function createApplyPatchTool(options: ApplyPatchToolOptions = {}): ApplyPatchToolDefinition { - const tool = defineTool({ - name: "apply_patch", - label: "ApplyPatch", - description: APPLY_PATCH_FREEFORM_DESCRIPTION, - parameters: APPLY_PATCH_PARAMS, - prepareArguments: normalizeApplyPatchArguments, - promptSnippet: - "Use the `apply_patch` tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`).", - promptGuidelines: [ - "Patches MUST begin with `*** Begin Patch` and end with `*** End Patch`.", - "Use one of these operation headers per file: `*** Add File: `, `*** Delete File: `, `*** Update File: `.", - "Every added line must start with `+`, removed lines with `-`, unchanged context with a single space.", - "Use `*** Move to: ` directly after `*** Update File:` to rename; use `*** End of File` only when the hunk reaches actual EOF.", - "Use `@@` (optionally with a class/function header) when 3 lines of context are insufficient to uniquely identify the snippet.", - "File references MUST be relative — never absolute.", - "Do not re-read files after `apply_patch` succeeds.", - "Do not edit files via bash, Python, or heredocs when `apply_patch` is available.", - ], - async execute( - _toolCallId, - params, - _signal, - onUpdate, - ctx, - ): Promise> { - const normalizedParams = normalizeApplyPatchArguments(params); - if (!normalizedParams.input) { - throw new Error("input is required"); - } - - const operations = options.getOperations?.() ?? options.operations ?? LOCAL_APPLY_PATCH_OPERATIONS; - let parsedHunks: ParsedPatch[] | undefined; - try { - parsedHunks = parseNonEmptyPatch(normalizedParams.input); - } catch { - // createPendingPatchUpdate keeps incomplete or invalid patch text renderable. - } - const totalOperations = parsedHunks?.length ?? 0; - const initialProgress = totalOperations > 0 ? { applied: 0, failed: 0, total: totalOperations } : undefined; - const pendingUpdate = await createPendingPatchUpdate( - ctx.cwd, - normalizedParams.input, - initialProgress, - undefined, - parsedHunks, - operations, - ); - onUpdate?.({ - content: [{ type: "text", text: pendingUpdate.text }], - details: pendingUpdate.details, - }); - - const preview = pendingUpdate.details?.preview; - const result = await applyParsedPatchDetailed( - ctx.cwd, - parsedHunks ?? parseNonEmptyPatch(normalizedParams.input), - async (progress) => { - const progressUpdate = await createPendingPatchUpdate( - ctx.cwd, - normalizedParams.input, - progress, - preview, - parsedHunks, - operations, - ); - onUpdate?.({ - content: [{ type: "text", text: progressUpdate.text }], - details: progressUpdate.details, - }); - }, - operations, - ); - if (result.failures.length > 0) { - const mustReadFiles = result.recoveryInstructions.mustReadFiles; - const failed = mustReadFiles.join(", "); - const mustReadText = mustReadFiles.join(" and "); - return { - content: [ - { - type: "text", - text: [ - "apply_patch partially failed.", - `Failed: ${failed}`, - `Recovery: MUST read ${mustReadText} before retrying.`, - result.appliedFiles.length > 0 - ? "Earlier file actions in this patch were already applied." - : "No file actions were applied.", - result.recoveryInstructions.mustNotReadFiles.length > 0 - ? "Recovery: MUST NOT reread other files from this patch unless a specific dependency requires it." - : "", - ] - .filter((line) => line.length > 0) - .join("\n"), - }, - ], - details: { result, changes: result.changes }, - }; - } - - return { - content: [{ type: "text", text: result.summaries.join("\n") }], - details: { result, changes: result.changes }, - }; - }, - renderCall(args, theme, context) { - if (!context.argsComplete) { - return new Text(theme.fg("toolTitle", theme.bold("apply_patch: Patching")), 0, 0); - } - - const normalizedArgs = normalizeApplyPatchArguments(args); - const renderState = getApplyPatchRenderState(context.toolCallId, context.cwd, normalizedArgs.input); - const text = renderState.callText.length > 0 ? `apply_patch: ${renderState.callText}` : "apply_patch"; - return new Text(theme.fg("toolTitle", theme.bold(text)), 0, 0); - }, - renderResult(result, options, theme, context) { - const component = new Container(); - const changes = result.details?.changes; - const preview = result.details?.preview; - - // Final (settled) result: render from Codex-style changes when available. - if (changes && !options.isPartial) { - const bgName = "toolSuccessBg"; - const box = new Box(1, 1, (text: string) => applyLayeredBackground(theme, bgName, text)); - box.addChild(new Text(theme.fg("toolTitle", theme.bold("Applied patch")), 0, 0)); - box.addChild(new Spacer(1)); - box.addChild(new Text(formatApplyPatchChanges(changes, context.cwd, options.expanded ?? true, theme), 0, 0)); - component.addChild(box); - return component; - } - - // Streaming / partial result: render from preview (full-file diff with line numbers). - if (preview) { - const bgName = options.isPartial ? "toolPendingBg" : "toolSuccessBg"; - const progress = result.details?.progress; - const title = progress - ? `Applying patch (${progress.applied + progress.failed}/${progress.total})` - : "Applying patch"; - const box = new Box(1, 1, (text: string) => applyLayeredBackground(theme, bgName, text)); - box.addChild(new Text(theme.fg("toolTitle", theme.bold(title)), 0, 0)); - box.addChild(new Spacer(1)); - const expanded = options.isPartial ? true : (options.expanded ?? true); - box.addChild(new Text(renderPatchPreview(preview, context.cwd, theme, expanded), 0, 0)); - component.addChild(box); - return component; - } - - const text = result.content - .filter((block) => block.type === "text") - .map((block) => block.text) - .filter((value) => typeof value === "string" && value.length > 0) - .join("\n"); - if (text) { - component.addChild(new Text(theme.fg("toolOutput", text), 0, 0)); - } - return component; - }, - }); - - return Object.assign(tool, { - freeform: { - type: "grammar", - syntax: "lark", - definition: APPLY_PATCH_LARK_GRAMMAR, - } satisfies FreeformToolFormat, - }); -} - -export function registerApplyPatchExtension(pi: ApplyPatchExtensionAPI): void { - let remoteOperations: ApplyPatchOperations | undefined; - pi.events?.on(SSH_REMOTE_APPLY_PATCH_OPERATIONS_EVENT, (data) => { - if (typeof data !== "object" || data === null) { - remoteOperations = undefined; - return; - } - remoteOperations = - (data as { operations?: ApplyPatchOperations | null }).operations ?? undefined; - }); - - pi.registerTool(createApplyPatchTool({ getOperations: () => remoteOperations })); - - pi.on("session_start", async (_event, ctx) => { - syncToolset(pi, ctx.model); - }); - - pi.on("model_select", async (event) => { - syncToolset(pi, event.model); - }); - - pi.on("before_agent_start", async (_event, ctx) => { - syncToolset(pi, ctx.model); - }); -} - -export default registerApplyPatchExtension; +export { default } from "./src/index.js"; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..23ea953 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,1802 @@ +import { readFileSync } from "node:fs"; +import { mkdir, readFile, realpath, rm, stat } from "node:fs/promises"; +import path from "node:path"; +import type { AgentToolResult } from "@earendil-works/pi-agent-core"; +import type { Model } from "@earendil-works/pi-ai"; +import { + defineTool, + type ExtensionAPI, + getAgentDir, + getLanguageFromPath, + highlightCode, + type ToolDefinition, +} from "@earendil-works/pi-coding-agent"; +import { Box, Container, Spacer, Text } from "@earendil-works/pi-tui"; +import * as Diff from "diff"; +import { Type } from "typebox"; +import { writeFileAtomic } from "./write-file-atomic.js"; + +export type ApplyPatchOperations = { + readFile: (absolutePath: string) => Promise; + writeFileAtomic: (absolutePath: string, content: string) => Promise; + mkdir: (directoryPath: string) => Promise; + rm: (absolutePath: string) => Promise; + stat: (absolutePath: string) => Promise; + realpath: (absolutePath: string) => Promise; +}; + +export type ApplyPatchToolOptions = { + operations?: ApplyPatchOperations; + getOperations?: () => ApplyPatchOperations | undefined; +}; + +const LOCAL_APPLY_PATCH_OPERATIONS: ApplyPatchOperations = { + readFile: (absolutePath) => readFile(absolutePath, "utf-8"), + writeFileAtomic, + mkdir: async (directoryPath) => { + await mkdir(directoryPath, { recursive: true }); + }, + rm: async (absolutePath) => { + await rm(absolutePath); + }, + stat: (absolutePath) => stat(absolutePath), + realpath: (absolutePath) => realpath(absolutePath), +}; + +const APPLY_PATCH_PARAMS = Type.Object({ + input: Type.String({ + description: "The entire contents of the apply_patch command", + }), +}); + +type ParsedPatch = + | { type: "add"; filePath: string; content: string } + | { type: "delete"; filePath: string } + | { type: "update"; filePath: string; movePath?: string; chunks: PatchChunk[] }; + +type PatchChunk = { + changeContexts: string[]; + oldLines: string[]; + newLines: string[]; + isEndOfFile: boolean; +}; + +export type FreeformToolFormat = { + type: "grammar"; + syntax: "lark"; + definition: string; +}; + +type ApplyPatchToolDefinition = ToolDefinition & { + freeform: FreeformToolFormat; +}; + +type ApplyPatchEventBus = { + on: (channel: string, handler: (data: unknown) => void) => () => void; +}; + +export type ApplyPatchExtensionAPI = Pick & { + events?: ApplyPatchEventBus; + registerTool: (tool: ApplyPatchToolDefinition) => void; +}; + +const SSH_REMOTE_APPLY_PATCH_OPERATIONS_EVENT = "ssh-remote:apply-patch-operations"; + +type ApplyPatchParams = { + input: string; +}; + +type ApplyPatchOperation = "add" | "delete" | "update"; + +type ApplyPatchPreviewFile = { + filePath: string; + movePath?: string; + operation: ApplyPatchOperation; + diff: string; + added: number; + removed: number; +}; + +type ApplyPatchPreview = { + files: ApplyPatchPreviewFile[]; + added: number; + removed: number; +}; + +/** + * Codex-style per-file change that gets persisted in the final tool result. + * + * Mirrors the shape of `FileChange` in openai/codex: only the hunks-only + * unified diff for updates (no full file content, no line numbers) and the + * touched file path for adds and deletes. This keeps the session file small + * (comparable to `edit` tool output) and lets the TUI re-render the diff + * from the final result on demand. + */ +export type ApplyPatchFileChange = + | { operation: "add"; filePath: string; added: number; removed: number } + | { operation: "delete"; filePath: string; added: number; removed: number } + | { + operation: "update"; + filePath: string; + movePath?: string; + unifiedDiff: string; + added: number; + removed: number; + }; + +type ApplyPatchToolDetails = { + preview?: ApplyPatchPreview; + progress?: ApplyPatchProgress; + result?: ApplyPatchResult; + changes?: ApplyPatchFileChange[]; +}; + +type ApplyPatchProgress = { + applied: number; + failed: number; + total: number; +}; + +type ApplyPatchProgressCallback = (progress: ApplyPatchProgress) => Promise | void; + +async function notifyApplyPatchProgress( + onProgress: ApplyPatchProgressCallback | undefined, + progress: ApplyPatchProgress, +): Promise { + try { + await onProgress?.(progress); + } catch { + // Rendering progress must not affect patch application or recovery details. + } +} + +export type ApplyPatchFailure = { + filePath: string; + operation: ApplyPatchOperation; + message: string; +}; + +export type ApplyPatchRecoveryInstructions = { + mustReadFiles: string[]; + mustNotReadFiles: string[]; +}; + +export type ApplyPatchResult = { + summaries: string[]; + appliedFiles: string[]; + failures: ApplyPatchFailure[]; + hasPartialSuccess: boolean; + recoveryInstructions: ApplyPatchRecoveryInstructions; + changes: ApplyPatchFileChange[]; + details: { + fuzz: number; + }; +}; + +export class ApplyPatchError extends Error { + public readonly failures: ApplyPatchFailure[]; + public readonly result: ApplyPatchResult; + + constructor(message: string, result: ApplyPatchResult) { + super(message); + this.name = "ApplyPatchError"; + this.failures = result.failures; + this.result = result; + } + + hasPartialSuccess(): boolean { + return this.result.hasPartialSuccess; + } +} + +export class PatchParseError extends Error { + constructor(message: string) { + super(message); + this.name = "PatchParseError"; + } +} + +export class PatchApplicationError extends Error { + constructor(message: string) { + super(message); + this.name = "PatchApplicationError"; + } +} + +type ApplyPatchRenderState = { + cwd: string; + patchText: string; + callText: string; + collapsed: string; + expanded: string; +}; + +type ApplyPatchThemeColor = + | "accent" + | "error" + | "muted" + | "toolDiffAdded" + | "toolDiffContext" + | "toolDiffRemoved" + | "toolOutput" + | "toolTitle"; + +type ApplyPatchThemeBg = "toolErrorBg" | "toolPendingBg" | "toolSuccessBg"; + +type ApplyPatchTheme = { + fg: (name: ApplyPatchThemeColor, text: string) => string; + bg: (name: ApplyPatchThemeBg, text: string) => string; + bold: (text: string) => string; + inverse: (text: string) => string; +}; + +function hasErrorCode(error: unknown, code: string): boolean { + return Boolean(error && typeof error === "object" && "code" in error && error.code === code); +} + +const DEFAULT_GPT_APPLY_PATCH_PROVIDERS = ["openai", "openai-codex"] as const; +export const APPLY_PATCH_LOCAL_CONFIG_PATH = path.join(getAgentDir(), "pi-apply-patch.json"); + +type ApplyPatchLocalConfig = { + providers?: unknown; +}; + +function getGPTApplyPatchProviders(): Set { + try { + const config = JSON.parse(readFileSync(APPLY_PATCH_LOCAL_CONFIG_PATH, "utf8")) as ApplyPatchLocalConfig; + if (Array.isArray(config.providers)) { + const providers = config.providers.filter( + (provider): provider is string => typeof provider === "string" && provider.length > 0, + ); + if (providers.length > 0) { + return new Set(providers); + } + } + } catch { + // Missing or invalid local config falls back to standard providers. + } + return new Set(DEFAULT_GPT_APPLY_PATCH_PROVIDERS); +} +export const PATCH_PREVIEW_MAX_LINES = 16; +export const PATCH_PREVIEW_MAX_CHARS = 4000; +const PATCH_PREVIEW_HEAD_LINES = 8; +const PATCH_PREVIEW_TAIL_LINES = PATCH_PREVIEW_MAX_LINES - PATCH_PREVIEW_HEAD_LINES - 1; +const PATCH_PREVIEW_TRUNCATION_MARKER = "…"; +const applyPatchRenderStates = new Map(); + +function applyLayeredBackground(theme: ApplyPatchTheme, bgName: ApplyPatchThemeBg, text: string): string { + const marker = "\x1fpi-bg-marker\x1f"; + const wrappedMarker = theme.bg(bgName, marker); + const markerIndex = wrappedMarker.indexOf(marker); + if (markerIndex === -1) { + return theme.bg(bgName, text); + } + + const bgStart = wrappedMarker.slice(0, markerIndex); + const bgEnd = wrappedMarker.slice(markerIndex + marker.length); + const restored = text.replace(/\x1b\[([0-9;]*)m/g, (sequence: string, params: string) => { + if (params === "" || params.split(";").some((param) => param === "0" || param === "49")) { + return `${sequence}${bgStart}`; + } + return sequence; + }); + return `${bgStart}${restored}${bgEnd}`; +} + +function isChangedPreviewLine(line: string): boolean { + return /^[+-](?:\s*\d+\s|[^\s])/.test(line); +} + +function countWindowLines(lines: string[], start: number, end: number): number { + return end - start + (start > 0 ? 1 : 0) + (end < lines.length ? 1 : 0); +} + +function formatPreviewWindow(lines: string[], start: number, end: number): string { + const previewLines = lines.slice(start, end); + if (start > 0) { + previewLines.unshift("…"); + } + if (end < lines.length) { + previewLines.push("…"); + } + return previewLines.join("\n"); +} + +function createChangedHunkPreview(lines: string[]): string | undefined { + const firstChangedLine = lines.findIndex(isChangedPreviewLine); + if (firstChangedLine === -1) { + return undefined; + } + + let start = firstChangedLine; + let end = firstChangedLine + 1; + while (end < lines.length) { + const line = lines[end]; + if (line === undefined || !isChangedPreviewLine(line)) { + break; + } + end++; + } + + const changedHunkEnd = end; + while (end > start && countWindowLines(lines, start, end) > PATCH_PREVIEW_MAX_LINES) { + end--; + } + + while (countWindowLines(lines, start, end) < PATCH_PREVIEW_MAX_LINES) { + const canAddBefore = start > 0; + const canAddAfter = end < lines.length; + if (!canAddBefore && !canAddAfter) { + break; + } + + const beforeContextLines = firstChangedLine - start; + const afterContextLines = end - changedHunkEnd; + if (canAddBefore && (!canAddAfter || beforeContextLines <= afterContextLines)) { + start--; + } else { + end++; + } + } + + return formatPreviewWindow(lines, start, end); +} + +function countLines(text: string): number { + if (text.length === 0) { + return 0; + } + let lines = 1; + for (let index = 0; index < text.length; index++) { + if (text.charCodeAt(index) === 10) { + lines += 1; + } + } + return lines; +} + +function enforcePreviewCharLimit(preview: string): string { + if (preview.length <= PATCH_PREVIEW_MAX_CHARS) { + return preview; + } + + return `${preview.slice(0, PATCH_PREVIEW_MAX_CHARS - PATCH_PREVIEW_TRUNCATION_MARKER.length).trimEnd()}${PATCH_PREVIEW_TRUNCATION_MARKER}`; +} + +export function truncatePreview(text: string): string { + if (text.length <= PATCH_PREVIEW_MAX_CHARS && countLines(text) <= PATCH_PREVIEW_MAX_LINES) { + return text; + } + + const lines = text.split("\n"); + const changedHunkPreview = createChangedHunkPreview(lines); + const previewText = + changedHunkPreview ?? + [...lines.slice(0, PATCH_PREVIEW_HEAD_LINES), "…", ...lines.slice(-PATCH_PREVIEW_TAIL_LINES)].join("\n"); + return enforcePreviewCharLimit(previewText); +} + +function normalizeApplyPatchArguments(args: unknown): ApplyPatchParams { + if (typeof args === "string") { + return { input: args }; + } + + if (args && typeof args === "object" && "input" in args) { + const input = (args as { input?: unknown }).input; + if (typeof input === "string") { + return { input }; + } + } + + return { input: "" }; +} + +const STANDARD_EDIT_TOOL_NAMES = ["edit", "write"] as const; +export const APPLY_PATCH_FREEFORM_DESCRIPTION = + "Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON."; +export const APPLY_PATCH_LARK_GRAMMAR = `start: begin_patch hunk+ end_patch +begin_patch: "*** Begin Patch" LF +end_patch: "*** End Patch" LF? + +hunk: add_hunk | delete_hunk | update_hunk +add_hunk: "*** Add File: " filename LF add_line+ +delete_hunk: "*** Delete File: " filename LF +update_hunk: "*** Update File: " filename LF change_move? change? + +filename: /(.+)/ +add_line: "+" /(.*)/ LF -> line + +change_move: "*** Move to: " filename LF +change: (change_context | change_line)+ eof_line? +change_context: ("@@" | "@@ " /(.+)/) LF +change_line: ("+" | "-" | " ") /(.*)/ LF +eof_line: "*** End of File" LF + +%import common.LF +`; + +export function isOpenAIGptModel(model: Pick, "provider" | "id"> | undefined): boolean { + return ( + model !== undefined && + getGPTApplyPatchProviders().has(model.provider) && + model.id.toLowerCase().startsWith("gpt-") + ); +} + +function normalizePatchText(patchText: string): string { + return patchText.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); +} + +function stripHeredoc(input: string): string { + const heredocMatch = input.match(/^(?:cat\s+)?<<['"]?(\w+)['"]?\s*\n([\s\S]*?)\n\1\s*$/); + if (heredocMatch) { + return heredocMatch[2] ?? input; + } + return input; +} + +function normalizeSeekLine(line: string): string { + return line + .trim() + .replace(/[‐‑‒–—―−]/g, "-") + .replace(/[‘’‚‛]/g, "'") + .replace(/[“”„‟]/g, '"') + .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " "); +} + +function seekSequence( + lines: string[], + pattern: string[], + start: number, + eof: boolean, +): { index: number; fuzz: 0 | 1 | 100 | 10000 } | undefined { + if (pattern.length === 0) { + return { index: start, fuzz: 0 }; + } + if (pattern.length > lines.length) { + return undefined; + } + + const searchStart = eof && lines.length >= pattern.length ? lines.length - pattern.length : start; + const lastStart = lines.length - pattern.length; + const matches = (index: number, compare: (left: string, right: string) => boolean): boolean => { + for (let patternIndex = 0; patternIndex < pattern.length; patternIndex++) { + const line = lines[index + patternIndex]; + const expected = pattern[patternIndex]; + if (line === undefined || expected === undefined || !compare(line, expected)) { + return false; + } + } + return true; + }; + const matchesPrepared = (index: number, preparedLines: string[], preparedPattern: string[]): boolean => { + for (let patternIndex = 0; patternIndex < preparedPattern.length; patternIndex++) { + const line = preparedLines[index + patternIndex]; + const expected = preparedPattern[patternIndex]; + if (line === undefined || expected === undefined || line !== expected) { + return false; + } + } + return true; + }; + + for (let index = searchStart; index <= lastStart; index++) { + if (matches(index, (line, expected) => line === expected)) { + return { index, fuzz: 0 }; + } + } + const linesTrimEnd = lines.map((line) => line.trimEnd()); + const patternTrimEnd = pattern.map((line) => line.trimEnd()); + for (let index = searchStart; index <= lastStart; index++) { + if (matchesPrepared(index, linesTrimEnd, patternTrimEnd)) { + return { index, fuzz: 1 }; + } + } + const linesTrim = lines.map((line) => line.trim()); + const patternTrim = pattern.map((line) => line.trim()); + for (let index = searchStart; index <= lastStart; index++) { + if (matchesPrepared(index, linesTrim, patternTrim)) { + return { index, fuzz: 100 }; + } + } + const linesNormalized = lines.map(normalizeSeekLine); + const patternNormalized = pattern.map(normalizeSeekLine); + for (let index = searchStart; index <= lastStart; index++) { + if (matchesPrepared(index, linesNormalized, patternNormalized)) { + return { index, fuzz: 10000 }; + } + } + + return undefined; +} + +export function extractPatchedPaths(patchText: string): string[] { + const normalized = stripHeredoc(normalizePatchText(patchText)); + const matches = normalized.matchAll(/^\*\*\* (?:(?:Add|Delete|Update) File|Move to): (.+)$/gm); + return Array.from(matches, (match) => match[1] ?? ""); +} + +function countDiffLines(content: string): number { + if (content === "") return 0; + const lines = content.split("\n"); + if (lines[lines.length - 1] === "") { + lines.pop(); + } + return lines.length; +} + +/** + * Codex-style hunks-only unified diff (no line numbers, no full-file context). + * + * Produces the same format as Codex's @format_update_chunks_for_progress: + * ``@@ `` headers followed by ``-old`` / ``+new`` lines, with an + * optional ``*** End of File`` marker when the parser sets isEndOfFile. + */ +function formatUnifiedDiff(chunks: PatchChunk[]): { diff: string; added: number; removed: number } { + const output: string[] = []; + let added = 0; + let removed = 0; + for (const chunk of chunks) { + if (chunk.changeContexts.length > 0) { + for (const context of chunk.changeContexts) { + output.push(`@@ ${context}`); + } + } else { + output.push("@@"); + } + for (const line of chunk.oldLines) { + output.push(`-${line}`); + removed++; + } + for (const line of chunk.newLines) { + output.push(`+${line}`); + added++; + } + if (chunk.isEndOfFile) { + output.push("*** End of File"); + } + } + return { diff: output.join("\n"), added, removed }; +} + +async function readExistingFileForPreview(absolutePath: string, operations: ApplyPatchOperations): Promise { + try { + return await operations.readFile(absolutePath); + } catch (error) { + if (hasErrorCode(error, "ENOENT")) { + return ""; + } + throw error; + } +} + +function formatApplyPatchChanges( + changes: ApplyPatchFileChange[], + cwd: string, + expanded: boolean, + theme: ApplyPatchTheme, +): string { + if (changes.length === 0) { + return ""; + } + + const lines: string[] = []; + const totalAdded = changes.reduce((sum, c) => sum + c.added, 0); + const totalRemoved = changes.reduce((sum, c) => sum + c.removed, 0); + + // Header + if (changes.length === 1) { + const change = changes[0]; + if (!change) return ""; + const op = formatPatchOperation(change.operation); + const fp = displayPath(change.filePath, cwd); + const count = `(+${change.added} -${change.removed})`; + const target = + change.operation === "update" && change.movePath ? `${fp} → ${displayPath(change.movePath, cwd)}` : fp; + lines.push(theme.fg("toolTitle", theme.bold(`${op} ${target}`))); + lines.push(` ${count}`); + } else { + lines.push(theme.fg("toolTitle", theme.bold(`Edited ${changes.length} files`))); + lines.push(` ${formatLineCountSummary(totalAdded, totalRemoved)}`); + for (const change of changes) { + const op = formatPatchOperation(change.operation); + const fp = displayPath(change.filePath, cwd); + const count = `(+${change.added} -${change.removed})`; + const target = + change.operation === "update" && change.movePath ? `${fp} → ${displayPath(change.movePath, cwd)}` : fp; + lines.push(` └ ${op} ${target} ${count}`); + } + } + + if (expanded) { + for (const change of changes) { + if (change.operation === "update" && change.unifiedDiff) { + const diffLines = truncatePreview(change.unifiedDiff).split("\n"); + for (const diffLine of diffLines) { + if (diffLine.startsWith("-")) { + lines.push(` ${theme.fg("toolDiffRemoved", diffLine)}`); + } else if (diffLine.startsWith("+")) { + lines.push(` ${theme.fg("toolDiffAdded", diffLine)}`); + } else if (diffLine.startsWith("@@") || diffLine.startsWith("***")) { + lines.push(` ${theme.fg("muted", diffLine)}`); + } else { + lines.push(` ${diffLine}`); + } + } + } + } + } + + return lines.join("\n"); +} + +function formatLineCountSummary(added: number, removed: number): string { + return `(+${added} -${removed})`; +} + +function formatPatchFileSummary(file: ApplyPatchPreviewFile, cwd: string): string { + return `${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`; +} + +function formatPatchFileHeader(file: ApplyPatchPreviewFile, cwd: string): string { + return `• ${formatPatchOperation(file.operation)} ${formatPatchFileSummary(file, cwd)}`; +} + +function normalizeDisplayPath(filePath: string): string { + return filePath.replaceAll(path.sep, "/"); +} + +export function displayPath(filePath: string, cwd: string): string { + if (!path.isAbsolute(filePath)) { + return normalizeDisplayPath(filePath); + } + + const absoluteCwd = path.resolve(cwd); + const relativePath = path.relative(absoluteCwd, filePath); + if ( + relativePath === "" || + (!relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath)) + ) { + return normalizeDisplayPath(relativePath || "."); + } + + return normalizeDisplayPath(filePath); +} + +export function formatPatchFilePath(file: ApplyPatchPreviewFile, cwd: string = process.cwd()): string { + const filePath = displayPath(file.filePath, cwd); + if (!file.movePath) { + return filePath; + } + return `${filePath} → ${displayPath(file.movePath, cwd)}`; +} + +function formatPatchOperation(operation: ApplyPatchOperation): string { + if (operation === "add") { + return "Added"; + } + if (operation === "delete") { + return "Deleted"; + } + return "Edited"; +} + +export function formatPatchPreview( + preview: ApplyPatchPreview, + cwd: string = process.cwd(), + expanded: boolean = true, +): string { + const lines: string[] = []; + if (preview.files.length === 1) { + const file = preview.files[0]; + if (file) { + lines.push(formatPatchFileHeader(file, cwd)); + if (expanded && file.diff) { + lines.push( + ...truncatePreview(file.diff) + .split("\n") + .map((line) => ` ${line}`), + ); + } + } + return lines.join("\n"); + } + + const noun = "files"; + lines.push(`• Edited ${preview.files.length} ${noun} ${formatLineCountSummary(preview.added, preview.removed)}`); + for (const file of preview.files) { + lines.push(` └ ${formatPatchFileSummary(file, cwd)}`); + if (expanded && file.diff) { + lines.push( + ...truncatePreview(file.diff) + .split("\n") + .map((line) => ` ${line}`), + ); + } + } + return lines.join("\n"); +} + +function getApplyPatchRenderState(toolCallId: string, cwd: string, patchText: string): ApplyPatchRenderState { + const existing = applyPatchRenderStates.get(toolCallId); + if (existing && existing.cwd === cwd && existing.patchText === patchText) { + return existing; + } + + const callText = formatInFlightCallText(patchText); + let collapsed = ""; + let expanded = ""; + try { + const hunks = parsePatch(patchText); + if (hunks.length > 0) { + const files = hunks.map((hunk) => { + const file = { + filePath: hunk.filePath, + operation: hunk.type, + diff: "", + added: 0, + removed: 0, + } satisfies ApplyPatchPreviewFile; + return hunk.type === "update" && hunk.movePath !== undefined ? { ...file, movePath: hunk.movePath } : file; + }) satisfies ApplyPatchPreviewFile[]; + const preview: ApplyPatchPreview = { files, added: 0, removed: 0 }; + collapsed = formatPatchPreview(preview, cwd, false); + expanded = formatPatchPreview(preview, cwd, true); + } + } catch { + // leave summaries empty for partial/incomplete patch text + } + + const nextState: ApplyPatchRenderState = { cwd, patchText, callText, collapsed, expanded }; + applyPatchRenderStates.set(toolCallId, nextState); + return nextState; +} + +export function clearApplyPatchRenderState(): void { + applyPatchRenderStates.clear(); +} + +export function formatInFlightCallText(patchText: string): string { + const paths = extractPatchedPaths(patchText); + if (paths.length === 0) { + return "Patching"; + } + const noun = paths.length === 1 ? "file" : "files"; + const count = paths.length > 1 ? ` (${paths.length} ${noun})` : ""; + return `Patching${count}: ${paths.join(", ")}`; +} + +type RenderableAddedDiffLine = { content: string; kind: "added"; lineNumber: string; sign: "+" }; +type RenderableRemovedDiffLine = { content: string; kind: "removed"; lineNumber: string; sign: "-" }; +type RenderableContextDiffLine = { content: string; kind: "context"; lineNumber: string; sign: " " }; +type RenderableContentDiffLine = RenderableAddedDiffLine | RenderableContextDiffLine | RenderableRemovedDiffLine; +type RenderableDiffLine = RenderableContentDiffLine | { kind: "meta"; text: string }; + +function parseRenderableDiffLine(line: string): RenderableDiffLine { + // Handle Codex-style format (no line numbers): +content, -content + const simpleMatch = line.match(/^([+-])(?!\s*\d)(.*)$/); + if (simpleMatch) { + const sign = simpleMatch[1]; + const content = simpleMatch[2] ?? ""; + if (sign === "+") { + return { content, kind: "added", lineNumber: "", sign }; + } + if (sign === "-") { + return { content, kind: "removed", lineNumber: "", sign }; + } + } + + // Handle legacy format (line-numbered): +1 content, -1 content, 1 content + const match = line.match(/^([+\- ])(\s*\d+)\s(.*)$/); + if (!match) { + return { kind: "meta", text: line }; + } + + const sign = match[1]; + const lineNumber = match[2]; + if ((sign !== "+" && sign !== "-" && sign !== " ") || lineNumber === undefined) { + return { kind: "meta", text: line }; + } + + const content = match[3] ?? ""; + if (sign === "+") { + return { content, kind: "added", lineNumber, sign }; + } + if (sign === "-") { + return { content, kind: "removed", lineNumber, sign }; + } + return { content, kind: "context", lineNumber, sign }; +} + +function replaceTabs(text: string): string { + return text.replace(/\t/g, " "); +} + +function highlightDiffContent(content: string, filePath: string): string { + const plainContent = replaceTabs(content); + const language = getLanguageFromPath(filePath); + try { + return highlightCode(plainContent, language)[0] ?? plainContent; + } catch { + return plainContent; + } +} + +function renderInlineDiff( + oldContent: string, + newContent: string, + theme: ApplyPatchTheme, +): { added: string; removed: string } { + const parts = Diff.diffWords(replaceTabs(oldContent), replaceTabs(newContent)); + let added = ""; + let removed = ""; + let firstAdded = true; + let firstRemoved = true; + + for (const part of parts) { + if (part.added) { + let value = part.value; + if (firstAdded) { + const leadingWhitespace = value.match(/^(\s*)/)?.[1] ?? ""; + added += leadingWhitespace; + value = value.slice(leadingWhitespace.length); + firstAdded = false; + } + if (value) { + added += theme.inverse(value); + } + continue; + } + + if (part.removed) { + let value = part.value; + if (firstRemoved) { + const leadingWhitespace = value.match(/^(\s*)/)?.[1] ?? ""; + removed += leadingWhitespace; + value = value.slice(leadingWhitespace.length); + firstRemoved = false; + } + if (value) { + removed += theme.inverse(value); + } + continue; + } + + added += part.value; + removed += part.value; + } + + return { added, removed }; +} + +function renderOpenCodeLikeDiffLine( + line: RenderableContentDiffLine, + filePath: string, + theme: ApplyPatchTheme, + contentOverride?: string, +): string { + const lineNumberPart = line.lineNumber ? `${theme.fg("muted", line.lineNumber)} ` : ""; + if (line.kind === "context") { + return `${theme.fg("toolDiffContext", line.sign)}${lineNumberPart}${highlightDiffContent(line.content, filePath)}`; + } + + const diffColor = line.kind === "added" ? "toolDiffAdded" : "toolDiffRemoved"; + const background = line.kind === "added" ? "toolSuccessBg" : "toolErrorBg"; + const content = + contentOverride === undefined + ? highlightDiffContent(line.content, filePath) + : theme.fg(diffColor, replaceTabs(contentOverride)); + const rendered = `${theme.fg(diffColor, line.sign)}${lineNumberPart}${content}`; + return theme.bg(background, rendered); +} + +function renderOpenCodeLikeDiff(diffText: string, filePath: string, theme: ApplyPatchTheme): string { + const parsedLines = diffText.split("\n").map(parseRenderableDiffLine); + const rendered: string[] = []; + let index = 0; + + while (index < parsedLines.length) { + const line = parsedLines[index]; + if (!line) { + index++; + continue; + } + + if (line.kind !== "removed") { + rendered.push( + line.kind === "meta" + ? theme.fg("toolDiffContext", line.text) + : renderOpenCodeLikeDiffLine(line, filePath, theme), + ); + index++; + continue; + } + + const removedLines: RenderableRemovedDiffLine[] = []; + while (parsedLines[index]?.kind === "removed") { + const removedLine = parsedLines[index]; + if (removedLine?.kind === "removed") { + removedLines.push(removedLine); + } + index++; + } + + const addedLines: RenderableAddedDiffLine[] = []; + while (parsedLines[index]?.kind === "added") { + const addedLine = parsedLines[index]; + if (addedLine?.kind === "added") { + addedLines.push(addedLine); + } + index++; + } + + const pairedCount = Math.min(removedLines.length, addedLines.length); + for (let pairIndex = 0; pairIndex < pairedCount; pairIndex++) { + const removedLine = removedLines[pairIndex]; + const addedLine = addedLines[pairIndex]; + if (!removedLine || !addedLine) { + continue; + } + + const inline = renderInlineDiff(removedLine.content, addedLine.content, theme); + rendered.push(renderOpenCodeLikeDiffLine(removedLine, filePath, theme, inline.removed)); + rendered.push(renderOpenCodeLikeDiffLine(addedLine, filePath, theme, inline.added)); + } + + for (const removedLine of removedLines.slice(pairedCount)) { + rendered.push(renderOpenCodeLikeDiffLine(removedLine, filePath, theme)); + } + for (const addedLine of addedLines.slice(pairedCount)) { + rendered.push(renderOpenCodeLikeDiffLine(addedLine, filePath, theme)); + } + } + + return rendered.join("\n"); +} + +function renderPatchPreview( + preview: ApplyPatchPreview, + cwd: string, + theme: ApplyPatchTheme, + expanded: boolean, +): string { + if (expanded) { + try { + const renderFile = (file: ApplyPatchPreviewFile, headerPrefix: string): string => { + const header = formatPatchFileHeader(file, cwd); + if (!file.diff) { + return headerPrefix.length > 0 ? `${headerPrefix}${formatPatchFileSummary(file, cwd)}` : header; + } + const previewDiff = truncatePreview(file.diff); + const renderedDiff = renderOpenCodeLikeDiff(previewDiff, file.movePath ?? file.filePath, theme); + if (headerPrefix.length > 0) { + const nestedHeader = `${headerPrefix}${formatPatchFileSummary(file, cwd)}`; + return `${nestedHeader}\n${renderedDiff + .split("\n") + .map((line) => ` ${line}`) + .join("\n")}`; + } + return `${header}\n${renderedDiff}`; + }; + + if (preview.files.length === 1) { + const file = preview.files[0]; + return file ? renderFile(file, "") : ""; + } + + const noun = "files"; + const renderedFiles = preview.files.map((file) => renderFile(file, " └ ")).join("\n"); + if (renderedFiles.length > 0) { + return `• Edited ${preview.files.length} ${noun} ${formatLineCountSummary(preview.added, preview.removed)}\n${renderedFiles}`; + } + } catch { + // fall back to manual themed line rendering + } + } + + return formatPatchPreview(preview, cwd, expanded) + .split("\n") + .map((line) => { + const trimmed = line.trimStart(); + if (trimmed.startsWith("+")) { + return theme.fg("toolDiffAdded", line); + } + if (trimmed.startsWith("-")) { + return theme.fg("toolDiffRemoved", line); + } + if (trimmed.startsWith("•")) { + return theme.fg("toolTitle", theme.bold(line)); + } + if (trimmed.startsWith("└")) { + return theme.fg("accent", line); + } + return theme.fg("toolDiffContext", line); + }) + .join("\n"); +} + +function formatPendingPatchPaths(patchText: string): string { + const paths = extractPatchedPaths(patchText); + if (paths.length === 0) { + return "Applying patch..."; + } + return `Applying patch...\n${paths.map((filePath) => `• ${filePath}`).join("\n")}`; +} + +async function createPatchPreview( + cwd: string, + hunks: ParsedPatch[], + operations: ApplyPatchOperations, +): Promise { + const files: ApplyPatchPreviewFile[] = []; + for (const hunk of hunks) { + const absolutePath = await resolvePatchPath(cwd, hunk.filePath, operations); + if (hunk.type === "add") { + const oldContent = await readExistingFileForPreview(absolutePath, operations); + if (oldContent.length > 0) { + // Overwriting existing file — render as update with a minimal inline diff. + const removed = splitFileLines(oldContent).length; + const added = countDiffLines(hunk.content); + const diff = [ + "@@", + ...oldContent + .split("\n") + .filter((line) => line !== "") + .map((line) => `-${line}`), + ...hunk.content + .split("\n") + .filter((line) => line !== "") + .map((line) => `+${line}`), + ].join("\n"); + files.push({ filePath: hunk.filePath, operation: "update", diff, added, removed }); + } else { + const added = countDiffLines(hunk.content); + files.push({ filePath: hunk.filePath, operation: "add", diff: "", added, removed: 0 }); + } + continue; + } + + if (hunk.type === "delete") { + const oldContent = await operations.readFile(absolutePath); + const removed = splitFileLines(oldContent).length; + files.push({ filePath: hunk.filePath, operation: "delete", diff: "", added: 0, removed }); + continue; + } + + const unified = formatUnifiedDiff(hunk.chunks); + if (hunk.movePath) { + await resolvePatchPath(cwd, hunk.movePath, operations); + } + files.push({ + filePath: hunk.filePath, + operation: "update", + diff: unified.diff, + added: unified.added, + removed: unified.removed, + ...(hunk.movePath !== undefined ? { movePath: hunk.movePath } : {}), + }); + } + + return { + files, + added: files.reduce((sum, file) => sum + file.added, 0), + removed: files.reduce((sum, file) => sum + file.removed, 0), + }; +} + +function parsePatch(patchText: string): ParsedPatch[] { + const normalized = stripHeredoc(normalizePatchText(patchText).trim()).trim(); + const lines = normalized.split("\n"); + const beginIndex = lines[0]?.trim() === "*** Begin Patch" ? 0 : -1; + const lastLine = lines[lines.length - 1]; + const endIndex = lastLine?.trim() === "*** End Patch" ? lines.length - 1 : -1; + + if (beginIndex === -1 || endIndex === -1 || endIndex < beginIndex) { + throw new PatchParseError("Invalid patch format: expected *** Begin Patch ... *** End Patch envelope"); + } + + const hunks: ParsedPatch[] = []; + let index = beginIndex + 1; + while (index < endIndex) { + const line = lines[index] ?? ""; + if (!line.startsWith("*** ")) { + index++; + continue; + } + + if (line.startsWith("*** Add File: ")) { + const filePath = line.slice("*** Add File: ".length); + index++; + const contentLines: string[] = []; + while (index < endIndex) { + const nextLine = lines[index] ?? ""; + if (nextLine.startsWith("*** ")) { + break; + } + if (!nextLine.startsWith("+")) { + throw new PatchParseError(`Invalid patch format: Add File lines must start with '+'`); + } + contentLines.push(nextLine.slice(1)); + index++; + } + hunks.push({ + type: "add", + filePath, + content: contentLines.length === 0 ? "" : `${contentLines.join("\n")}\n`, + }); + continue; + } + + if (line.startsWith("*** Delete File: ")) { + hunks.push({ type: "delete", filePath: line.slice("*** Delete File: ".length) }); + index++; + continue; + } + + if (line.startsWith("*** Update File: ")) { + const filePath = line.slice("*** Update File: ".length); + index++; + let movePath: string | undefined; + if ((lines[index] ?? "").startsWith("*** Move to: ")) { + movePath = (lines[index] ?? "").slice("*** Move to: ".length); + index++; + } + + const chunks: PatchChunk[] = []; + while (index < endIndex) { + const nextLine = lines[index] ?? ""; + if (nextLine.trim() === "") { + index++; + continue; + } + if (nextLine.startsWith("*** ")) { + break; + } + + const allowMissingContext = chunks.length === 0; + const changeContexts: string[] = []; + if (nextLine.startsWith("@@")) { + while (index < endIndex) { + const contextLine = lines[index] ?? ""; + if (contextLine === "@@") { + index++; + continue; + } + if (contextLine.startsWith("@@ ")) { + changeContexts.push(contextLine.slice("@@ ".length)); + index++; + continue; + } + break; + } + } else if (!allowMissingContext) { + throw new PatchParseError(`Expected update hunk to start with a @@ context marker, got: '${nextLine}'`); + } + + const oldLines: string[] = []; + const newLines: string[] = []; + let isEndOfFile = false; + let parsedLines = 0; + while (index < endIndex) { + const hunkLine = lines[index] ?? ""; + if (hunkLine === "*** End of File") { + if (parsedLines === 0) { + throw new PatchParseError("Update hunk does not contain any lines"); + } + isEndOfFile = true; + index++; + break; + } + if (hunkLine.startsWith("@@") || hunkLine.startsWith("*** ")) { + break; + } + const prefix = hunkLine[0]; + const value = hunkLine.slice(1); + if (prefix === undefined) { + oldLines.push(""); + newLines.push(""); + } else if (prefix === " ") { + oldLines.push(value); + newLines.push(value); + } else if (prefix === "-") { + oldLines.push(value); + } else if (prefix === "+") { + newLines.push(value); + } else if (parsedLines > 0) { + break; + } else { + throw new PatchParseError( + `Unexpected line found in update hunk: '${hunkLine}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`, + ); + } + parsedLines++; + index++; + } + + if (parsedLines === 0) { + throw new PatchParseError("Update hunk does not contain any lines"); + } + chunks.push({ changeContexts, oldLines, newLines, isEndOfFile }); + } + if (chunks.length === 0 && !movePath) { + throw new PatchParseError(`Update file hunk for path '${filePath}' is empty`); + } + + hunks.push( + movePath !== undefined + ? { type: "update", filePath, movePath, chunks } + : { type: "update", filePath, chunks }, + ); + continue; + } + + throw new PatchParseError( + `'${line}' is not a valid hunk header. Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'`, + ); + } + + return hunks; +} + +function parseNonEmptyPatch(patchText: string): ParsedPatch[] { + const hunks = parsePatch(patchText); + if (hunks.length > 0) { + return hunks; + } + + const normalized = normalizePatchText(patchText).trim(); + if (normalized === "*** Begin Patch\n*** End Patch") { + throw new PatchParseError("patch rejected: empty patch"); + } + throw new PatchParseError("apply_patch verification failed: no hunks found"); +} + +function splitFileLines(content: string): string[] { + const lines = normalizePatchText(content).split("\n"); + if (lines[lines.length - 1] === "") { + lines.pop(); + } + return lines; +} + +function replaceChunks(content: string, filePath: string, chunks: PatchChunk[]): { content: string; fuzz: number } { + const originalLines = splitFileLines(content); + const replacements: { start: number; oldLength: number; newLines: string[] }[] = []; + let lineIndex = 0; + let fuzz = 0; + + for (const chunk of chunks) { + for (const changeContext of chunk.changeContexts) { + const contextMatch = seekSequence(originalLines, [changeContext], lineIndex, false); + if (contextMatch === undefined) { + throw new PatchApplicationError(`Failed to find context '${changeContext}' in ${filePath}`); + } + fuzz += contextMatch.fuzz; + lineIndex = contextMatch.index + 1; + } + + if (chunk.oldLines.length === 0) { + const insertionIndex = + originalLines[originalLines.length - 1] === "" ? originalLines.length - 1 : originalLines.length; + replacements.push({ start: insertionIndex, oldLength: 0, newLines: chunk.newLines }); + continue; + } + + let pattern = chunk.oldLines; + let newLines = chunk.newLines; + let foundAt = seekSequence(originalLines, pattern, lineIndex, chunk.isEndOfFile); + if (foundAt === undefined && pattern[pattern.length - 1] === "") { + pattern = pattern.slice(0, -1); + if (newLines[newLines.length - 1] === "") { + newLines = newLines.slice(0, -1); + } + foundAt = seekSequence(originalLines, pattern, lineIndex, chunk.isEndOfFile); + } + + if (foundAt === undefined) { + throw new PatchApplicationError(`Failed to find expected lines in ${filePath}:\n${chunk.oldLines.join("\n")}`); + } + + fuzz += foundAt.fuzz; + replacements.push({ start: foundAt.index, oldLength: pattern.length, newLines }); + lineIndex = foundAt.index + pattern.length; + } + + const nextLines = [...originalLines]; + for (const replacement of replacements.sort((left, right) => right.start - left.start)) { + nextLines.splice(replacement.start, replacement.oldLength, ...replacement.newLines); + } + nextLines.push(""); + return { content: nextLines.join("\n"), fuzz }; +} + +async function applySingleHunk( + cwd: string, + hunk: ParsedPatch, + operations: ApplyPatchOperations, +): Promise<{ + summary: string; + appliedFile: string; + fuzz: number; + change: ApplyPatchFileChange; +}> { + const absolutePath = await resolvePatchPath(cwd, hunk.filePath, operations); + if (hunk.type === "add") { + await operations.mkdir(path.dirname(absolutePath)); + await operations.writeFileAtomic(absolutePath, hunk.content); + const added = countDiffLines(hunk.content); + return { + summary: `add: ${hunk.filePath}`, + appliedFile: hunk.filePath, + fuzz: 0, + change: { operation: "add", filePath: hunk.filePath, added, removed: 0 }, + }; + } + + if (hunk.type === "delete") { + await operations.stat(absolutePath); + const oldContent = await operations.readFile(absolutePath); + await operations.rm(absolutePath); + const removed = splitFileLines(oldContent).length; + return { + summary: `delete: ${hunk.filePath}`, + appliedFile: hunk.filePath, + fuzz: 0, + change: { operation: "delete", filePath: hunk.filePath, added: 0, removed }, + }; + } + + const currentContent = await operations.readFile(absolutePath); + const chunkResult = + hunk.chunks.length === 0 + ? { content: currentContent, fuzz: 0 } + : replaceChunks(currentContent, hunk.filePath, hunk.chunks); + const nextContent = chunkResult.content; + const unified = formatUnifiedDiff(hunk.chunks); + const change: ApplyPatchFileChange = { + operation: "update", + filePath: hunk.filePath, + unifiedDiff: unified.diff, + added: unified.added, + removed: unified.removed, + }; + + if (hunk.movePath) { + const absoluteMovePath = await resolvePatchPath(cwd, hunk.movePath, operations); + await operations.mkdir(path.dirname(absoluteMovePath)); + await operations.writeFileAtomic(absoluteMovePath, nextContent); + if (absoluteMovePath !== absolutePath) { + await operations.rm(absolutePath); + } + change.movePath = hunk.movePath; + return { + summary: `move: ${hunk.filePath} -> ${hunk.movePath}`, + appliedFile: hunk.movePath, + fuzz: chunkResult.fuzz, + change, + }; + } + + await operations.writeFileAtomic(absolutePath, nextContent); + return { summary: `update: ${hunk.filePath}`, appliedFile: hunk.filePath, fuzz: chunkResult.fuzz, change }; +} + +export async function applyPatchDetailed( + cwd: string, + patchText: string, + onProgress?: ApplyPatchProgressCallback, + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, +): Promise { + return applyParsedPatchDetailed(cwd, parseNonEmptyPatch(patchText), onProgress, operations); +} + +async function applyParsedPatchDetailed( + cwd: string, + hunks: ParsedPatch[], + onProgress?: ApplyPatchProgressCallback, + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, +): Promise { + const summaries: string[] = []; + const appliedFiles: string[] = []; + const failures: ApplyPatchFailure[] = []; + const changes: ApplyPatchFileChange[] = []; + let fuzz = 0; + + for (const hunk of hunks) { + try { + const { summary, appliedFile, fuzz: hunkFuzz, change } = await applySingleHunk(cwd, hunk, operations); + summaries.push(summary); + appliedFiles.push(appliedFile); + fuzz += hunkFuzz; + changes.push(change); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + failures.push({ filePath: hunk.filePath, operation: hunk.type, message }); + } + await notifyApplyPatchProgress(onProgress, { + applied: appliedFiles.length, + failed: failures.length, + total: hunks.length, + }); + } + + const result: ApplyPatchResult = { + summaries, + appliedFiles, + failures, + changes, + hasPartialSuccess: appliedFiles.length > 0 && failures.length > 0, + recoveryInstructions: { mustReadFiles: [], mustNotReadFiles: [] }, + details: { fuzz }, + }; + result.recoveryInstructions = createRecoveryInstructions(result); + return result; +} + +function createRecoveryInstructions( + result: Pick, +): ApplyPatchRecoveryInstructions { + const mustReadFiles = [...new Set(result.failures.map((failure) => failure.filePath))]; + const mustReadFileSet = new Set(mustReadFiles); + const mustNotReadFiles = [...new Set(result.appliedFiles.filter((filePath) => !mustReadFileSet.has(filePath)))]; + return { mustReadFiles, mustNotReadFiles }; +} + +export async function applyPatch( + cwd: string, + patchText: string, + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, +): Promise { + const hunks = parseNonEmptyPatch(patchText); + + const summaries: string[] = []; + const appliedFiles: string[] = []; + const changes: ApplyPatchFileChange[] = []; + for (const hunk of hunks) { + try { + const { summary, appliedFile, change } = await applySingleHunk(cwd, hunk, operations); + summaries.push(summary); + appliedFiles.push(appliedFile); + changes.push(change); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const failure = { filePath: hunk.filePath, operation: hunk.type, message } satisfies ApplyPatchFailure; + const result: ApplyPatchResult = { + summaries, + appliedFiles, + changes, + failures: [failure], + hasPartialSuccess: appliedFiles.length > 0, + recoveryInstructions: createRecoveryInstructions({ + appliedFiles, + failures: [failure], + }), + details: { fuzz: 0 }, + }; + throw new ApplyPatchError(message, result); + } + } + + return summaries; +} + +async function createPendingPatchUpdate( + cwd: string, + patchText: string, + progress?: ApplyPatchProgress, + previewOverride?: ApplyPatchPreview, + parsedHunks?: ParsedPatch[], + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, +): Promise<{ text: string; details: ApplyPatchToolDetails | undefined }> { + const title = progress + ? `Applying patch (${progress.applied + progress.failed}/${progress.total})...` + : "Applying patch..."; + if (previewOverride) { + const details: ApplyPatchToolDetails = { preview: previewOverride }; + if (progress) details.progress = progress; + return { + text: `${title}\n${formatPatchPreview(previewOverride, cwd)}`, + details, + }; + } + + try { + const hunks = parsedHunks ?? parsePatch(patchText); + if (hunks.length === 0) { + return { text: title, details: progress ? { progress } : undefined }; + } + + const preview = await createPatchPreview(cwd, hunks, operations); + if (preview.files.some((file) => file.diff.trim().length > 0)) { + const details: ApplyPatchToolDetails = { preview }; + if (progress) details.progress = progress; + return { text: `${title}\n${formatPatchPreview(preview, cwd)}`, details }; + } + } catch { + return { + text: progress ? title : formatPendingPatchPaths(patchText), + details: progress ? { progress } : undefined, + }; + } + + return { text: progress ? title : formatPendingPatchPaths(patchText), details: progress ? { progress } : undefined }; +} + +function withoutExtensionManagedEditTools(toolNames: string[]): string[] { + return toolNames.filter( + (toolName) => + toolName !== "apply_patch" && !STANDARD_EDIT_TOOL_NAMES.some((editToolName) => editToolName === toolName), + ); +} + +function replaceEditToolsWithApplyPatch(toolNames: string[]): string[] { + return [...withoutExtensionManagedEditTools(toolNames), "apply_patch"]; +} + +function replaceApplyPatchWithEditTools(toolNames: string[]): string[] { + return [...withoutExtensionManagedEditTools(toolNames), ...STANDARD_EDIT_TOOL_NAMES]; +} + +function isPathWithinWorkspace(workspacePath: string, candidatePath: string): boolean { + const relativePath = path.relative(workspacePath, candidatePath); + return ( + relativePath === "" || + (!relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath)) + ); +} + +async function findExistingAncestor( + directoryPath: string, + workspacePath: string, + operations: ApplyPatchOperations, +): Promise { + let currentPath = directoryPath; + while (isPathWithinWorkspace(workspacePath, currentPath)) { + try { + await operations.stat(currentPath); + return currentPath; + } catch (error) { + if (!hasErrorCode(error, "ENOENT")) { + throw error; + } + } + + const parentPath = path.dirname(currentPath); + if (parentPath === currentPath) { + break; + } + currentPath = parentPath; + } + + throw new PatchApplicationError(`Patch path escapes workspace: ${directoryPath}`); +} + +async function resolvePatchPath( + cwd: string, + filePath: string, + operations: ApplyPatchOperations = LOCAL_APPLY_PATCH_OPERATIONS, +): Promise { + const workspacePath = await operations.realpath(cwd); + const absolutePath = path.resolve(workspacePath, filePath); + if (!isPathWithinWorkspace(workspacePath, absolutePath)) { + throw new PatchApplicationError(`Patch path escapes workspace: ${filePath}`); + } + + const existingAncestor = await findExistingAncestor(path.dirname(absolutePath), workspacePath, operations); + const realAncestor = await operations.realpath(existingAncestor); + if (!isPathWithinWorkspace(workspacePath, realAncestor)) { + throw new PatchApplicationError(`Patch path escapes workspace: ${filePath}`); + } + + return absolutePath; +} + +function syncToolset( + pi: Pick, + model: Model | undefined, +): void { + const currentToolNames = pi.getActiveTools(); + if (isOpenAIGptModel(model)) { + pi.setActiveTools(replaceEditToolsWithApplyPatch(currentToolNames)); + return; + } + + pi.setActiveTools(replaceApplyPatchWithEditTools(currentToolNames)); +} + +export function createApplyPatchTool(options: ApplyPatchToolOptions = {}): ApplyPatchToolDefinition { + const tool = defineTool({ + name: "apply_patch", + label: "ApplyPatch", + description: APPLY_PATCH_FREEFORM_DESCRIPTION, + parameters: APPLY_PATCH_PARAMS, + prepareArguments: normalizeApplyPatchArguments, + promptSnippet: + "Use the `apply_patch` tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`).", + promptGuidelines: [ + "Patches MUST begin with `*** Begin Patch` and end with `*** End Patch`.", + "Use one of these operation headers per file: `*** Add File: `, `*** Delete File: `, `*** Update File: `.", + "Every added line must start with `+`, removed lines with `-`, unchanged context with a single space.", + "Use `*** Move to: ` directly after `*** Update File:` to rename; use `*** End of File` only when the hunk reaches actual EOF.", + "Use `@@` (optionally with a class/function header) when 3 lines of context are insufficient to uniquely identify the snippet.", + "File references MUST be relative — never absolute.", + "Do not re-read files after `apply_patch` succeeds.", + "Do not edit files via bash, Python, or heredocs when `apply_patch` is available.", + ], + async execute( + _toolCallId, + params, + _signal, + onUpdate, + ctx, + ): Promise> { + const normalizedParams = normalizeApplyPatchArguments(params); + if (!normalizedParams.input) { + throw new Error("input is required"); + } + + const operations = options.getOperations?.() ?? options.operations ?? LOCAL_APPLY_PATCH_OPERATIONS; + let parsedHunks: ParsedPatch[] | undefined; + try { + parsedHunks = parseNonEmptyPatch(normalizedParams.input); + } catch { + // createPendingPatchUpdate keeps incomplete or invalid patch text renderable. + } + const totalOperations = parsedHunks?.length ?? 0; + const initialProgress = totalOperations > 0 ? { applied: 0, failed: 0, total: totalOperations } : undefined; + const pendingUpdate = await createPendingPatchUpdate( + ctx.cwd, + normalizedParams.input, + initialProgress, + undefined, + parsedHunks, + operations, + ); + onUpdate?.({ + content: [{ type: "text", text: pendingUpdate.text }], + details: pendingUpdate.details, + }); + + const preview = pendingUpdate.details?.preview; + const result = await applyParsedPatchDetailed( + ctx.cwd, + parsedHunks ?? parseNonEmptyPatch(normalizedParams.input), + async (progress) => { + const progressUpdate = await createPendingPatchUpdate( + ctx.cwd, + normalizedParams.input, + progress, + preview, + parsedHunks, + operations, + ); + onUpdate?.({ + content: [{ type: "text", text: progressUpdate.text }], + details: progressUpdate.details, + }); + }, + operations, + ); + if (result.failures.length > 0) { + const mustReadFiles = result.recoveryInstructions.mustReadFiles; + const failed = mustReadFiles.join(", "); + const mustReadText = mustReadFiles.join(" and "); + return { + content: [ + { + type: "text", + text: [ + "apply_patch partially failed.", + `Failed: ${failed}`, + `Recovery: MUST read ${mustReadText} before retrying.`, + result.appliedFiles.length > 0 + ? "Earlier file actions in this patch were already applied." + : "No file actions were applied.", + result.recoveryInstructions.mustNotReadFiles.length > 0 + ? "Recovery: MUST NOT reread other files from this patch unless a specific dependency requires it." + : "", + ] + .filter((line) => line.length > 0) + .join("\n"), + }, + ], + details: { result, changes: result.changes }, + }; + } + + return { + content: [{ type: "text", text: result.summaries.join("\n") }], + details: { result, changes: result.changes }, + }; + }, + renderCall(args, theme, context) { + if (!context.argsComplete) { + return new Text(theme.fg("toolTitle", theme.bold("apply_patch: Patching")), 0, 0); + } + + const normalizedArgs = normalizeApplyPatchArguments(args); + const renderState = getApplyPatchRenderState(context.toolCallId, context.cwd, normalizedArgs.input); + const text = renderState.callText.length > 0 ? `apply_patch: ${renderState.callText}` : "apply_patch"; + return new Text(theme.fg("toolTitle", theme.bold(text)), 0, 0); + }, + renderResult(result, options, theme, context) { + const component = new Container(); + const changes = result.details?.changes; + const preview = result.details?.preview; + + // Final (settled) result: render from Codex-style changes when available. + if (changes && !options.isPartial) { + const bgName = "toolSuccessBg"; + const box = new Box(1, 1, (text: string) => applyLayeredBackground(theme, bgName, text)); + box.addChild(new Text(theme.fg("toolTitle", theme.bold("Applied patch")), 0, 0)); + box.addChild(new Spacer(1)); + box.addChild( + new Text(formatApplyPatchChanges(changes, context.cwd, options.expanded ?? true, theme), 0, 0), + ); + component.addChild(box); + return component; + } + + // Streaming / partial result: render from preview (full-file diff with line numbers). + if (preview) { + const bgName = options.isPartial ? "toolPendingBg" : "toolSuccessBg"; + const progress = result.details?.progress; + const title = progress + ? `Applying patch (${progress.applied + progress.failed}/${progress.total})` + : "Applying patch"; + const box = new Box(1, 1, (text: string) => applyLayeredBackground(theme, bgName, text)); + box.addChild(new Text(theme.fg("toolTitle", theme.bold(title)), 0, 0)); + box.addChild(new Spacer(1)); + const expanded = options.isPartial ? true : (options.expanded ?? true); + box.addChild(new Text(renderPatchPreview(preview, context.cwd, theme, expanded), 0, 0)); + component.addChild(box); + return component; + } + + const text = result.content + .filter((block) => block.type === "text") + .map((block) => block.text) + .filter((value) => typeof value === "string" && value.length > 0) + .join("\n"); + if (text) { + component.addChild(new Text(theme.fg("toolOutput", text), 0, 0)); + } + return component; + }, + }); + + return Object.assign(tool, { + freeform: { + type: "grammar", + syntax: "lark", + definition: APPLY_PATCH_LARK_GRAMMAR, + } satisfies FreeformToolFormat, + }); +} + +export function registerApplyPatchExtension(pi: ApplyPatchExtensionAPI): void { + let remoteOperations: ApplyPatchOperations | undefined; + pi.events?.on(SSH_REMOTE_APPLY_PATCH_OPERATIONS_EVENT, (data) => { + if (typeof data !== "object" || data === null) { + remoteOperations = undefined; + return; + } + remoteOperations = (data as { operations?: ApplyPatchOperations | null }).operations ?? undefined; + }); + + pi.registerTool(createApplyPatchTool({ getOperations: () => remoteOperations })); + + pi.on("session_start", async (_event, ctx) => { + syncToolset(pi, ctx.model); + }); + + pi.on("model_select", async (event) => { + syncToolset(pi, event.model); + }); + + pi.on("before_agent_start", async (_event, ctx) => { + syncToolset(pi, ctx.model); + }); +} + +export default registerApplyPatchExtension; diff --git a/write-file-atomic.ts b/src/write-file-atomic.ts similarity index 100% rename from write-file-atomic.ts rename to src/write-file-atomic.ts diff --git a/test/index.test.ts b/test/index.test.ts index 9d760d4..7ab2936 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -15,8 +15,8 @@ import { PatchParseError, registerApplyPatchExtension, truncatePreview, -} from "../index.js"; -import { writeFileAtomic } from "../write-file-atomic.js"; +} from "../src/index.js"; +import { writeFileAtomic } from "../src/write-file-atomic.js"; const tempDirectories: string[] = []; const identityTheme = { diff --git a/test/render.test.ts b/test/render.test.ts index f531254..6990197 100644 --- a/test/render.test.ts +++ b/test/render.test.ts @@ -8,7 +8,7 @@ import { PATCH_PREVIEW_MAX_CHARS, PATCH_PREVIEW_MAX_LINES, truncatePreview, -} from "../index.js"; +} from "../src/index.js"; const identityTheme = { fg: (_name: string, text: string) => text, diff --git a/tsconfig.json b/tsconfig.json index 618b4d1..e04756c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,5 @@ "types": ["node"], "noEmit": true }, - "include": ["*.ts", "test/**/*"] + "include": ["src/**/*", "index.ts", "test/**/*"] }