wip: port LSP tool from Oh My Pi (needs type fixes)

All 10 LSP files ported and adapted. Wired into tools/index.ts.
Remaining work: fix TypeScript compilation errors (see below).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson 2026-03-13 11:19:43 -06:00
parent 1fc3a4ca77
commit f51a080bcf
12 changed files with 4766 additions and 0 deletions

728
bun.lock Normal file
View file

@ -0,0 +1,728 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "gsd-pi",
"dependencies": {
"@clack/prompts": "^1.1.0",
"@gsd/pi-agent-core": "*",
"@gsd/pi-ai": "*",
"@gsd/pi-coding-agent": "*",
"@gsd/pi-tui": "*",
"picocolors": "^1.1.1",
"picomatch": "^4.0.3",
"playwright": "^1.58.2",
"sharp": "^0.34.5",
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/picomatch": "^4.0.2",
"jiti": "^2.6.1",
"typescript": "^5.4.0",
},
"optionalDependencies": {
"fsevents": "~2.3.3",
},
},
"packages/pi-agent-core": {
"name": "@gsd/pi-agent-core",
"version": "0.57.1",
"dependencies": {
"@gsd/pi-ai": "*",
},
},
"packages/pi-ai": {
"name": "@gsd/pi-ai",
"version": "0.57.1",
"dependencies": {
"@anthropic-ai/sdk": "^0.73.0",
"@aws-sdk/client-bedrock-runtime": "^3.983.0",
"@google/genai": "^1.40.0",
"@mistralai/mistralai": "1.14.1",
"@sinclair/typebox": "^0.34.41",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"chalk": "^5.6.2",
"openai": "6.26.0",
"partial-json": "^0.1.7",
"proxy-agent": "^6.5.0",
"undici": "^7.19.1",
"zod-to-json-schema": "^3.24.6",
},
},
"packages/pi-coding-agent": {
"name": "@gsd/pi-coding-agent",
"version": "0.57.1",
"dependencies": {
"@gsd/pi-agent-core": "*",
"@gsd/pi-ai": "*",
"@gsd/pi-tui": "*",
"@mariozechner/jiti": "^2.6.2",
"@silvia-odwyer/photon-node": "^0.3.4",
"chalk": "^5.5.0",
"cli-highlight": "^2.1.11",
"diff": "^8.0.2",
"extract-zip": "^2.0.1",
"file-type": "^21.1.1",
"glob": "^13.0.1",
"hosted-git-info": "^9.0.2",
"ignore": "^7.0.5",
"marked": "^15.0.12",
"minimatch": "^10.2.3",
"proper-lockfile": "^4.1.2",
"strip-ansi": "^7.1.0",
"undici": "^7.19.1",
"yaml": "^2.8.2",
},
"devDependencies": {
"@types/diff": "^7.0.2",
"@types/hosted-git-info": "^3.0.5",
"@types/proper-lockfile": "^4.1.4",
},
"optionalDependencies": {
"@mariozechner/clipboard": "^0.3.2",
},
},
"packages/pi-tui": {
"name": "@gsd/pi-tui",
"version": "0.57.1",
"dependencies": {
"@types/mime-types": "^2.1.4",
"chalk": "^5.5.0",
"get-east-asian-width": "^1.3.0",
"marked": "^15.0.12",
"mime-types": "^3.0.1",
},
"optionalDependencies": {
"koffi": "^2.9.0",
},
},
},
"overrides": {
"gaxios": "7.1.4",
},
"packages": {
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.73.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw=="],
"@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.1008.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.19", "@aws-sdk/credential-provider-node": "^3.972.20", "@aws-sdk/eventstream-handler-node": "^3.972.10", "@aws-sdk/middleware-eventstream": "^3.972.7", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/middleware-websocket": "^3.972.12", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/token-providers": "3.1008.0", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.6", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/eventstream-serde-browser": "^4.2.11", "@smithy/eventstream-serde-config-resolver": "^4.3.11", "@smithy/eventstream-serde-node": "^4.2.11", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-retry": "^4.4.40", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.39", "@smithy/util-defaults-mode-node": "^4.2.42", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-155H8HBuN4PLbhwOk7lA7RJ3wD4EWjminnNQoUS9PK2wQ0oGdTad0IHz1aCzNZNmI3fxsJqyty6YBSkbCZ5Lew=="],
"@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-MBAMW6YELzE1SdkOniqr51mrjapQUv8JXSGxtwRjQV0mwVDutVsn22OPAUt4RcLRvdiHQmNBDEFP9iTeSVCOlA=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-9EJROO8LXll5a7eUFqu48k6BChrtokbmgeMWmsH7lBb6lVbtjslUYz/ShLi+SHkYzTomiGBhmzTW7y+H4BxsnA=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/credential-provider-env": "^3.972.17", "@aws-sdk/credential-provider-http": "^3.972.19", "@aws-sdk/credential-provider-login": "^3.972.19", "@aws-sdk/credential-provider-process": "^3.972.17", "@aws-sdk/credential-provider-sso": "^3.972.19", "@aws-sdk/credential-provider-web-identity": "^3.972.19", "@aws-sdk/nested-clients": "^3.996.9", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-pVJVjWqVrPqjpFq7o0mCmeZu1Y0c94OCHSYgivdCD2wfmYVtBbwQErakruhgOD8pcMcx9SCqRw1pzHKR7OGBcA=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.9", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-jOXdZ1o+CywQKr6gyxgxuUmnGwTTnY2Kxs1PM7fI6AYtDWDnmW/yKXayNqkF8KjP1unflqMWKVbVt5VgmE3L0g=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.20", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.17", "@aws-sdk/credential-provider-http": "^3.972.19", "@aws-sdk/credential-provider-ini": "^3.972.19", "@aws-sdk/credential-provider-process": "^3.972.17", "@aws-sdk/credential-provider-sso": "^3.972.19", "@aws-sdk/credential-provider-web-identity": "^3.972.19", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-0xHca2BnPY0kzjDYPH7vk8YbfdBPpWVS67rtqQMalYDQUCBYS37cZ55K6TuFxCoIyNZgSCFrVKr9PXC5BVvQQw=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-c8G8wT1axpJDgaP3xzcy+q8Y1fTi9A2eIQJvyhQ9xuXrUZhlCfXbC0vM9bM1CUXiZppFQ1p7g0tuUMvil/gCPg=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.9", "@aws-sdk/token-providers": "3.1008.0", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-kVjQsEU3b///q7EZGrUzol9wzwJFKbEzqJKSq82A9ShrUTEO7FNylTtby3sPV19ndADZh1H3FB3+5ZrvKtEEeg=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.9", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-BV1BlTFdG4w4tAihxN7iXDBoNcNewXD4q8uZlNQiUrnqxwGWUhKHODIQVSPlQGxXClEj+63m+cqZskw+ESmeZg=="],
"@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/eventstream-codec": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-g2Z9s6Y4iNh0wICaEqutgYgt/Pmhv5Ev9G3eKGFe2w9VuZDhc76vYdop6I5OocmpHV79d4TuLG+JWg5rQIVDVA=="],
"@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-VWndapHYCfwLgPpCb/xwlMKG4imhFzKJzZcKOEioGn7OHY+6gdr0K7oqy1HZgbLa3ACznZ9fku+DzmAi8fUC0g=="],
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="],
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="],
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="],
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="],
"@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-format-url": "^3.972.7", "@smithy/eventstream-codec": "^4.2.11", "@smithy/eventstream-serde-browser": "^4.2.11", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-iyPP6FVDKe/5wy5ojC0akpDFG1vX3FeCUU47JuwN8xfvT66xlEI8qUJZPtN55TJVFzzWZJpWL78eqUE31md08Q=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.9", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.19", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.6", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-retry": "^4.4.40", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.39", "@smithy/util-defaults-mode-node": "^4.2.42", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-+RpVtpmQbbtzFOKhMlsRcXM/3f1Z49qTOHaA8gEpHOYruERmog6f2AUtf/oTRLCWjR9H2b3roqryV/hI7QMW8w=="],
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1008.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.9", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-TulwlHQBWcJs668kNUDMZHN51DeLrDsYT59Ux4a/nbvr025gM6HjKJJ3LvnZccam7OS/ZKUVkWomCneRQKJbBg=="],
"@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="],
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="],
"@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-V+PbnWfUl93GuFwsOHsAq7hY/fnm9kElRqR8IexIJr5Rvif9e614X5sGSyz3mVSf1YAZ+VTy63W1/pGdA55zyA=="],
"@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/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="],
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.6", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-iF7G0prk7AvmOK64FcLvc/fW+Ty1H+vttajL7PvJFReU8urMxfYmynTTuFKDTA76Wgpq3FzTPKwabMQIXQHiXQ=="],
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="],
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
"@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
"@clack/core": ["@clack/core@1.1.0", "", { "dependencies": { "sisteransi": "^1.0.5" } }, "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA=="],
"@clack/prompts": ["@clack/prompts@1.1.0", "", { "dependencies": { "@clack/core": "1.1.0", "sisteransi": "^1.0.5" } }, "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="],
"@google/genai": ["@google/genai@1.45.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-+sNRWhKiRibVgc4OKi7aBJJ0A7RcoVD8tGG+eFkqxAWRjASDW+ktS9lLwTDnAxZICzCVoeAdu8dYLJVTX60N9w=="],
"@gsd/pi-agent-core": ["@gsd/pi-agent-core@workspace:packages/pi-agent-core"],
"@gsd/pi-ai": ["@gsd/pi-ai@workspace:packages/pi-ai"],
"@gsd/pi-coding-agent": ["@gsd/pi-coding-agent@workspace:packages/pi-coding-agent"],
"@gsd/pi-tui": ["@gsd/pi-tui@workspace:packages/pi-tui"],
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.2", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.2", "@mariozechner/clipboard-darwin-universal": "0.3.2", "@mariozechner/clipboard-darwin-x64": "0.3.2", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", "@mariozechner/clipboard-linux-x64-musl": "0.3.2", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" } }, "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA=="],
"@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw=="],
"@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.2", "", { "os": "darwin" }, "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg=="],
"@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg=="],
"@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ=="],
"@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw=="],
"@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.2", "", { "os": "linux", "cpu": "none" }, "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA=="],
"@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A=="],
"@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw=="],
"@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg=="],
"@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA=="],
"@mariozechner/jiti": ["@mariozechner/jiti@2.6.5", "", { "dependencies": { "std-env": "^3.10.0", "yoctocolors": "^2.1.2" }, "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw=="],
"@mistralai/mistralai": ["@mistralai/mistralai@1.14.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.1" } }, "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ=="],
"@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.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="],
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="],
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="],
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="],
"@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.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
"@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="],
"@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="],
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-YxFiiG4YDAtX7WMN7RuhHZLeTmRRAOyCbr+zB8e3AQzHPnUhS8zXjB1+cniPVQI3xbWsQPM0X2aaIkO/ME0ymw=="],
"@smithy/core": ["@smithy/core@3.23.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.19", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-952rGf7hBRnhUIaeLp6q4MptKW8sPFe5VvkoZ5qIzFAtx6c/QZ/54FS3yootsyUSf9gJX/NBqEBNdNR7jMIlpQ=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="],
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="],
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="],
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="],
"@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="],
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="],
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="],
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.25", "", { "dependencies": { "@smithy/core": "^3.23.11", "@smithy/middleware-serde": "^4.2.14", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-dqjLwZs2eBxIUG6Qtw8/YZ4DvzHGIf0DA18wrgtfP6a50UIO7e2nY0FPdcbv5tVJKqWCCU5BmGMOUwT7Puan+A=="],
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.42", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-vbwyqHRIpIZutNXZpLAozakzamcINaRCpEy1MYmK6xBeW3xN+TyPRA123GjXnuxZIjc9848MRRCugVMTXxC4Eg=="],
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.14", "", { "dependencies": { "@smithy/core": "^3.23.11", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+CcaLoLa5apzSRtloOyG7lQvkUw2ZDml3hRh4QiG9WyEPfW5Ke/3tPOPiPjUneuT59Tpn8+c3RVaUvvkkwqZwg=="],
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="],
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.16", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ULC8UCS/HivdCB3jhi+kLFYe4B5gxH2gi9vHBfEIiRrT2jfKiZNiETJSlzRtE6B26XbBHjPtc8iZKSNqMol9bw=="],
"@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="],
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="],
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@smithy/smithy-client": ["@smithy/smithy-client@4.12.5", "", { "dependencies": { "@smithy/core": "^3.23.11", "@smithy/middleware-endpoint": "^4.4.25", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.19", "tslib": "^2.6.2" } }, "sha512-UqwYawyqSr/aog8mnLnfbPurS0gi4G7IYDcD28cUIBhsvWs1+rQcL2IwkUQ+QZ7dibaoRzhNF99fAQ9AUcO00w=="],
"@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="],
"@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="],
"@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="],
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="],
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="],
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="],
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="],
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.41", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-M1w1Ux0rSVvBOxIIiqbxvZvhnjQ+VUjJrugtORE90BbadSTH+jsQL279KRL3Hv0w69rE7EuYkV/4Lepz/NBW9g=="],
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.44", "", { "dependencies": { "@smithy/config-resolver": "^4.4.11", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YPze3/lD1KmWuZsl9JlfhcgGLX7AXhSoaCDtiPntUjNW5/YY0lOHjkcgxyE9x/h5vvS1fzDifMGjzqnNlNiqOQ=="],
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="],
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="],
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="],
"@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="],
"@smithy/util-stream": ["@smithy/util-stream@4.5.19", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.4.16", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-v4sa+3xTweL1CLO2UP0p7tvIMH/Rq1X4KKOxd568mpe6LSLMQCnDHs4uv7m3ukpl3HvcN2JH6jiCS0SNRXKP/w=="],
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="],
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
"@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="],
"@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
"@types/diff": ["@types/diff@7.0.2", "", {}, "sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q=="],
"@types/hosted-git-info": ["@types/hosted-git-info@3.0.5", "", {}, "sha512-Dmngh7U003cOHPhKGyA7LWqrnvcTyILNgNPmNCxlx7j8MIi54iBliiT8XqVLIQ3GchoOjVAyBzNJVyuaJjqokg=="],
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
"@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
"@types/picomatch": ["@types/picomatch@4.0.2", "", {}, "sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA=="],
"@types/proper-lockfile": ["@types/proper-lockfile@4.1.4", "", { "dependencies": { "@types/retry": "*" } }, "sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ=="],
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" }, "peerDependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"basic-ftp": ["basic-ftp@5.2.0", "", {}, "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw=="],
"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.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="],
"cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "escodegen": "bin/escodegen.js", "esgenerate": "bin/esgenerate.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": "cli.js" }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fast-xml-builder": ["fast-xml-builder@1.1.2", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-NJAmiuVaJEjVa7TjLZKlYd7RqmzOC91EtPFXHvlTcqBVo50Qh7XV5IwvXi1c7NRz2Q/majGX9YLcwJtWgHjtkA=="],
"fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"file-type": ["file-type@21.3.1", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-SrzXX46I/zsRDjTb82eucsGg0ODq2NpGDp4HcsFKApPy8P8vACjpJRDoGGMfEzhFC0ry61ajd7f72J3603anBA=="],
"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.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="],
"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-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
"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.6.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "7.1.3", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA=="],
"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=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
"hosted-git-info": ["hosted-git-info@9.0.2", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg=="],
"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=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"jiti": ["jiti@2.6.1", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"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" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"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=="],
"koffi": ["koffi@2.15.2", "", {}, "sha512-r9tjJLVRSOhCRWdVyQlF3/Ugzeg13jlzS4czS82MAgLff4W+BcYOW7g8Y62t9O5JYjYOLAjAovAZDNlDfZNu+g=="],
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
"marked": ["marked@15.0.12", "", { "bin": "bin/marked.js" }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
"minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
"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=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "bin": "bin/cli" }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="],
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
"pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
"pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
"parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="],
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="],
"partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="],
"path-expression-matcher": ["path-expression-matcher@1.1.3", "", {}, "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ=="],
"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=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": "cli.js" }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="],
"playwright-core": ["playwright-core@1.58.2", "", { "bin": "cli.js" }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="],
"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.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="],
"proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
"socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="],
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
"token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
"undici": ["undici@7.24.0", "", {}, "sha512-jxytwMHhsbdpBXxLAcuu0fzlQeXCNnWdDyRHpvWsUl8vd98UwYdl9YTyn8/HcpcJPC3pwUveefsa3zTxyD/ERg=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yaml": ["yaml@2.8.2", "", { "bin": "bin.mjs" }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
"yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
"yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
"@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=="],
"@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=="],
"cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"hosted-git-info/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
"parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
"path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@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=="],
"@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=="],
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@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=="],
"@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=="],
}
}

View file

@ -0,0 +1,852 @@
import { killProcessTree } from "../../utils/shell.js";
import { ToolAbortError, isEnoent, throwIfAborted, untilAborted } from "./helpers";
import { applyWorkspaceEdit } from "./edits";
import { getLspmuxCommand, isLspmuxSupported } from "./lspmux";
import type {
Diagnostic,
LspClient,
LspJsonRpcNotification,
LspJsonRpcRequest,
LspJsonRpcResponse,
ServerConfig,
WorkspaceEdit,
} from "./types";
import { detectLanguageId, fileToUri } from "./utils";
// =============================================================================
// Client State
// =============================================================================
const clients = new Map<string, LspClient>();
const clientLocks = new Map<string, Promise<LspClient>>();
const fileOperationLocks = new Map<string, Promise<void>>();
// Idle timeout configuration (disabled by default)
let idleTimeoutMs: number | null = null;
let idleCheckInterval: Timer | null = null;
const IDLE_CHECK_INTERVAL_MS = 60 * 1000;
/**
* Configure the idle timeout for LSP clients.
*/
export function setIdleTimeout(ms: number | null | undefined): void {
idleTimeoutMs = ms ?? null;
if (idleTimeoutMs && idleTimeoutMs > 0) {
startIdleChecker();
} else {
stopIdleChecker();
}
}
function startIdleChecker(): void {
if (idleCheckInterval) return;
idleCheckInterval = setInterval(() => {
if (!idleTimeoutMs) return;
const now = Date.now();
for (const [key, client] of Array.from(clients.entries())) {
if (now - client.lastActivity > idleTimeoutMs) {
shutdownClient(key);
}
}
}, IDLE_CHECK_INTERVAL_MS);
}
function stopIdleChecker(): void {
if (idleCheckInterval) {
clearInterval(idleCheckInterval);
idleCheckInterval = null;
}
}
// =============================================================================
// Client Capabilities
// =============================================================================
const CLIENT_CAPABILITIES = {
textDocument: {
synchronization: {
didSave: true,
dynamicRegistration: false,
willSave: false,
willSaveWaitUntil: false,
},
hover: {
contentFormat: ["markdown", "plaintext"],
dynamicRegistration: false,
},
definition: {
dynamicRegistration: false,
linkSupport: true,
},
typeDefinition: {
dynamicRegistration: false,
linkSupport: true,
},
implementation: {
dynamicRegistration: false,
linkSupport: true,
},
references: {
dynamicRegistration: false,
},
documentSymbol: {
dynamicRegistration: false,
hierarchicalDocumentSymbolSupport: true,
symbolKind: {
valueSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26],
},
},
rename: {
dynamicRegistration: false,
prepareSupport: true,
},
codeAction: {
dynamicRegistration: false,
codeActionLiteralSupport: {
codeActionKind: {
valueSet: [
"quickfix",
"refactor",
"refactor.extract",
"refactor.inline",
"refactor.rewrite",
"source",
"source.organizeImports",
"source.fixAll",
],
},
},
resolveSupport: {
properties: ["edit"],
},
},
formatting: {
dynamicRegistration: false,
},
rangeFormatting: {
dynamicRegistration: false,
},
publishDiagnostics: {
relatedInformation: true,
versionSupport: false,
tagSupport: { valueSet: [1, 2] },
codeDescriptionSupport: true,
dataSupport: true,
},
},
workspace: {
applyEdit: true,
workspaceEdit: {
documentChanges: true,
resourceOperations: ["create", "rename", "delete"],
failureHandling: "textOnlyTransactional",
},
configuration: true,
symbol: {
dynamicRegistration: false,
symbolKind: {
valueSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26],
},
},
},
experimental: {
snippetTextEdit: true,
},
};
// =============================================================================
// LSP Message Protocol
// =============================================================================
function parseMessage(
buffer: Buffer,
): { message: LspJsonRpcResponse | LspJsonRpcNotification; remaining: Buffer } | null {
const headerEndIndex = findHeaderEnd(buffer);
if (headerEndIndex === -1) return null;
const headerText = new TextDecoder().decode(buffer.slice(0, headerEndIndex));
const contentLengthMatch = headerText.match(/Content-Length: (\d+)/i);
if (!contentLengthMatch) return null;
const contentLength = Number.parseInt(contentLengthMatch[1], 10);
const messageStart = headerEndIndex + 4; // Skip \r\n\r\n
const messageEnd = messageStart + contentLength;
if (buffer.length < messageEnd) return null;
const messageBytes = buffer.subarray(messageStart, messageEnd);
const messageText = new TextDecoder().decode(messageBytes);
const remaining = buffer.subarray(messageEnd);
return {
message: JSON.parse(messageText),
remaining,
};
}
function findHeaderEnd(buffer: Uint8Array): number {
for (let i = 0; i < buffer.length - 3; i++) {
if (buffer[i] === 13 && buffer[i + 1] === 10 && buffer[i + 2] === 13 && buffer[i + 3] === 10) {
return i;
}
}
return -1;
}
async function writeMessage(
sink: Bun.FileSink,
message: LspJsonRpcRequest | LspJsonRpcNotification | LspJsonRpcResponse,
): Promise<void> {
const content = JSON.stringify(message);
sink.write(`Content-Length: ${Buffer.byteLength(content, "utf-8")}\r\n\r\n`);
sink.write(content);
await sink.flush();
}
// =============================================================================
// Message Reader
// =============================================================================
async function startMessageReader(client: LspClient): Promise<void> {
if (client.isReading) return;
client.isReading = true;
const reader = (client.proc.stdout as ReadableStream<Uint8Array>).getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const currentBuffer: Buffer = Buffer.concat([client.messageBuffer, value]);
client.messageBuffer = currentBuffer;
let workingBuffer = currentBuffer;
let parsed = parseMessage(workingBuffer);
while (parsed) {
const { message, remaining } = parsed;
workingBuffer = remaining;
if ("id" in message && message.id !== undefined) {
const pending = client.pendingRequests.get(message.id);
if (pending) {
client.pendingRequests.delete(message.id);
if ("error" in message && message.error) {
pending.reject(new Error(`LSP error: ${message.error.message}`));
} else {
pending.resolve(message.result);
}
} else if ("method" in message) {
await handleServerRequest(client, message as LspJsonRpcRequest);
}
} else if ("method" in message) {
if (message.method === "textDocument/publishDiagnostics" && message.params) {
const params = message.params as { uri: string; diagnostics: Diagnostic[] };
client.diagnostics.set(params.uri, params.diagnostics);
client.diagnosticsVersion += 1;
}
}
parsed = parseMessage(workingBuffer);
}
client.messageBuffer = workingBuffer;
}
} catch (err) {
for (const pending of Array.from(client.pendingRequests.values())) {
pending.reject(new Error(`LSP connection closed: ${err}`));
}
client.pendingRequests.clear();
} finally {
reader.releaseLock();
client.isReading = false;
}
}
// =============================================================================
// Server Request Handlers
// =============================================================================
async function handleConfigurationRequest(client: LspClient, message: LspJsonRpcRequest): Promise<void> {
if (typeof message.id !== "number") return;
const params = message.params as { items?: Array<{ section?: string }> };
const items = params?.items ?? [];
const result = items.map(item => {
const section = item.section ?? "";
return client.config.settings?.[section] ?? {};
});
await sendResponse(client, message.id, result, "workspace/configuration");
}
async function handleApplyEditRequest(client: LspClient, message: LspJsonRpcRequest): Promise<void> {
if (typeof message.id !== "number") return;
const params = message.params as { edit?: WorkspaceEdit };
if (!params?.edit) {
await sendResponse(
client,
message.id,
{ applied: false, failureReason: "No edit provided" },
"workspace/applyEdit",
);
return;
}
try {
await applyWorkspaceEdit(params.edit, client.cwd);
await sendResponse(client, message.id, { applied: true }, "workspace/applyEdit");
} catch (err) {
await sendResponse(client, message.id, { applied: false, failureReason: String(err) }, "workspace/applyEdit");
}
}
async function handleServerRequest(client: LspClient, message: LspJsonRpcRequest): Promise<void> {
if (message.method === "workspace/configuration") {
await handleConfigurationRequest(client, message);
return;
}
if (message.method === "workspace/applyEdit") {
await handleApplyEditRequest(client, message);
return;
}
if (typeof message.id !== "number") return;
await sendResponse(client, message.id, null, message.method, {
code: -32601,
message: `Method not found: ${message.method}`,
});
}
async function sendResponse(
client: LspClient,
id: number,
result: unknown,
_method: string,
error?: { code: number; message: string; data?: unknown },
): Promise<void> {
const response: LspJsonRpcResponse = {
jsonrpc: "2.0",
id,
...(error ? { error } : { result }),
};
try {
await writeMessage(client.proc.stdin, response);
} catch {
// Failed to respond to server request
}
}
// =============================================================================
// Stderr Buffer
// =============================================================================
async function startStderrReader(client: LspClient): Promise<void> {
const reader = (client.proc.stderr as ReadableStream<Uint8Array>).getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Keep only the last 4KB of stderr
client.stderrBuffer += decoder.decode(value, { stream: true });
if (client.stderrBuffer.length > 4096) {
client.stderrBuffer = client.stderrBuffer.slice(-4096);
}
}
} catch {
// stderr stream closed
} finally {
reader.releaseLock();
}
}
// =============================================================================
// Client Management
// =============================================================================
/** Timeout for warmup initialize requests (5 seconds) */
export const WARMUP_TIMEOUT_MS = 5000;
/**
* Get or create an LSP client for the given server configuration and working directory.
*/
export async function getOrCreateClient(config: ServerConfig, cwd: string, initTimeoutMs?: number): Promise<LspClient> {
const key = `${config.command}:${cwd}`;
const existingClient = clients.get(key);
if (existingClient) {
existingClient.lastActivity = Date.now();
return existingClient;
}
const existingLock = clientLocks.get(key);
if (existingLock) {
return existingLock;
}
const clientPromise = (async () => {
const baseCommand = config.resolvedCommand ?? config.command;
const baseArgs = config.args ?? [];
// Wrap with lspmux if available and supported
const { command, args, env } = isLspmuxSupported(baseCommand)
? await getLspmuxCommand(baseCommand, baseArgs)
: { command: baseCommand, args: baseArgs };
const proc = Bun.spawn([command, ...args], {
cwd,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
env: env ? { ...Bun.env, ...env } : undefined,
});
const client: LspClient = {
name: key,
cwd,
proc: {
stdin: proc.stdin as unknown as Bun.FileSink,
stdout: proc.stdout as ReadableStream<Uint8Array>,
stderr: proc.stderr as ReadableStream<Uint8Array>,
pid: proc.pid,
exitCode: null,
exited: proc.exited,
kill: (signal?: number) => proc.kill(signal),
},
config,
requestId: 0,
diagnostics: new Map(),
diagnosticsVersion: 0,
openFiles: new Map(),
pendingRequests: new Map(),
messageBuffer: new Uint8Array(0),
isReading: false,
lastActivity: Date.now(),
stderrBuffer: "",
};
clients.set(key, client);
// Register crash recovery
proc.exited.then(code => {
client.proc.exitCode = code;
clients.delete(key);
clientLocks.delete(key);
if (client.pendingRequests.size > 0) {
const stderr = client.stderrBuffer.trim();
const err = new Error(
stderr ? `LSP server exited (code ${code}): ${stderr}` : `LSP server exited unexpectedly (code ${code})`,
);
for (const pending of client.pendingRequests.values()) {
pending.reject(err);
}
client.pendingRequests.clear();
}
});
// Start background readers
startMessageReader(client);
startStderrReader(client);
try {
const initResult = (await sendRequest(
client,
"initialize",
{
processId: process.pid,
rootUri: fileToUri(cwd),
rootPath: cwd,
capabilities: CLIENT_CAPABILITIES,
initializationOptions: config.initOptions ?? {},
workspaceFolders: [{ uri: fileToUri(cwd), name: cwd.split("/").pop() ?? "workspace" }],
},
undefined, // signal
initTimeoutMs,
)) as { capabilities?: unknown };
if (!initResult) {
throw new Error("Failed to initialize LSP: no response");
}
client.serverCapabilities = initResult.capabilities as LspClient["serverCapabilities"];
await sendNotification(client, "initialized", {});
return client;
} catch (err) {
clients.delete(key);
clientLocks.delete(key);
try {
killProcessTree(proc.pid);
} catch {
proc.kill();
}
throw err;
} finally {
clientLocks.delete(key);
}
})();
clientLocks.set(key, clientPromise);
return clientPromise;
}
/**
* Ensure a file is opened in the LSP client.
*/
export async function ensureFileOpen(client: LspClient, filePath: string, signal?: AbortSignal): Promise<void> {
throwIfAborted(signal);
const uri = fileToUri(filePath);
const lockKey = `${client.name}:${uri}`;
if (client.openFiles.has(uri)) {
return;
}
const existingLock = fileOperationLocks.get(lockKey);
if (existingLock) {
await untilAborted(signal, () => existingLock);
return;
}
const openPromise = (async () => {
throwIfAborted(signal);
if (client.openFiles.has(uri)) {
return;
}
let content: string;
try {
content = await Bun.file(filePath).text();
throwIfAborted(signal);
} catch (err) {
if (isEnoent(err)) return;
throw err;
}
const languageId = detectLanguageId(filePath);
throwIfAborted(signal);
await sendNotification(client, "textDocument/didOpen", {
textDocument: {
uri,
languageId,
version: 1,
text: content,
},
});
client.openFiles.set(uri, { version: 1, languageId });
client.lastActivity = Date.now();
})();
fileOperationLocks.set(lockKey, openPromise);
try {
await openPromise;
} finally {
fileOperationLocks.delete(lockKey);
}
}
/**
* Sync in-memory content to the LSP client without reading from disk.
*/
export async function syncContent(
client: LspClient,
filePath: string,
content: string,
signal?: AbortSignal,
): Promise<void> {
const uri = fileToUri(filePath);
const lockKey = `${client.name}:${uri}`;
throwIfAborted(signal);
const existingLock = fileOperationLocks.get(lockKey);
if (existingLock) {
await untilAborted(signal, () => existingLock);
}
const syncPromise = (async () => {
client.diagnostics.delete(uri);
const info = client.openFiles.get(uri);
if (!info) {
const languageId = detectLanguageId(filePath);
throwIfAborted(signal);
await sendNotification(client, "textDocument/didOpen", {
textDocument: {
uri,
languageId,
version: 1,
text: content,
},
});
client.openFiles.set(uri, { version: 1, languageId });
client.lastActivity = Date.now();
return;
}
const version = ++info.version;
throwIfAborted(signal);
await sendNotification(client, "textDocument/didChange", {
textDocument: { uri, version },
contentChanges: [{ text: content }],
});
client.lastActivity = Date.now();
})();
fileOperationLocks.set(lockKey, syncPromise);
try {
await syncPromise;
} finally {
fileOperationLocks.delete(lockKey);
}
}
/**
* Notify LSP that a file was saved.
*/
export async function notifySaved(client: LspClient, filePath: string, signal?: AbortSignal): Promise<void> {
const uri = fileToUri(filePath);
const info = client.openFiles.get(uri);
if (!info) return;
throwIfAborted(signal);
await sendNotification(client, "textDocument/didSave", {
textDocument: { uri },
});
client.lastActivity = Date.now();
}
/**
* Refresh a file in the LSP client.
*/
export async function refreshFile(client: LspClient, filePath: string, signal?: AbortSignal): Promise<void> {
throwIfAborted(signal);
const uri = fileToUri(filePath);
const lockKey = `${client.name}:${uri}`;
const existingLock = fileOperationLocks.get(lockKey);
if (existingLock) {
await untilAborted(signal, () => existingLock);
}
const refreshPromise = (async () => {
throwIfAborted(signal);
const info = client.openFiles.get(uri);
if (!info) {
await ensureFileOpen(client, filePath, signal);
return;
}
let content: string;
try {
content = await Bun.file(filePath).text();
throwIfAborted(signal);
} catch (err) {
if (isEnoent(err)) return;
throw err;
}
const version = ++info.version;
throwIfAborted(signal);
await sendNotification(client, "textDocument/didChange", {
textDocument: { uri, version },
contentChanges: [{ text: content }],
});
throwIfAborted(signal);
await sendNotification(client, "textDocument/didSave", {
textDocument: { uri },
text: content,
});
client.lastActivity = Date.now();
})();
fileOperationLocks.set(lockKey, refreshPromise);
try {
await refreshPromise;
} finally {
fileOperationLocks.delete(lockKey);
}
}
/**
* Shutdown a specific client by key.
*/
export function shutdownClient(key: string): void {
const client = clients.get(key);
if (!client) return;
for (const pending of Array.from(client.pendingRequests.values())) {
pending.reject(new Error("LSP client shutdown"));
}
client.pendingRequests.clear();
sendRequest(client, "shutdown", null).catch(() => {});
try {
killProcessTree(client.proc.pid);
} catch {
client.proc.kill();
}
clients.delete(key);
}
// =============================================================================
// LSP Protocol Methods
// =============================================================================
const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
export async function sendRequest(
client: LspClient,
method: string,
params: unknown,
signal?: AbortSignal,
timeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,
): Promise<unknown> {
const id = ++client.requestId;
if (signal?.aborted) {
const reason = signal.reason instanceof Error ? signal.reason : new ToolAbortError();
return Promise.reject(reason);
}
const request: LspJsonRpcRequest = {
jsonrpc: "2.0",
id,
method,
params,
};
client.lastActivity = Date.now();
const { promise, resolve, reject } = Promise.withResolvers<unknown>();
let timeout: NodeJS.Timeout | undefined;
const cleanup = () => {
if (signal) {
signal.removeEventListener("abort", abortHandler);
}
};
const abortHandler = () => {
if (client.pendingRequests.has(id)) {
client.pendingRequests.delete(id);
}
void sendNotification(client, "$/cancelRequest", { id }).catch(() => {});
if (timeout) clearTimeout(timeout);
cleanup();
const reason = signal?.reason instanceof Error ? signal.reason : new ToolAbortError();
reject(reason);
};
timeout = setTimeout(() => {
if (client.pendingRequests.has(id)) {
client.pendingRequests.delete(id);
const err = new Error(`LSP request ${method} timed out after ${timeoutMs}ms`);
cleanup();
reject(err);
}
}, timeoutMs);
if (signal) {
signal.addEventListener("abort", abortHandler, { once: true });
if (signal.aborted) {
abortHandler();
return promise;
}
}
client.pendingRequests.set(id, {
resolve: result => {
if (timeout) clearTimeout(timeout);
cleanup();
resolve(result);
},
reject: err => {
if (timeout) clearTimeout(timeout);
cleanup();
reject(err);
},
method,
});
writeMessage(client.proc.stdin, request).catch(err => {
if (timeout) clearTimeout(timeout);
client.pendingRequests.delete(id);
cleanup();
reject(err);
});
return promise;
}
export async function sendNotification(client: LspClient, method: string, params: unknown): Promise<void> {
const notification: LspJsonRpcNotification = {
jsonrpc: "2.0",
method,
params,
};
client.lastActivity = Date.now();
await writeMessage(client.proc.stdin, notification);
}
/**
* Shutdown all LSP clients.
*/
export function shutdownAll(): void {
const clientsToShutdown = Array.from(clients.values());
clients.clear();
const err = new Error("LSP client shutdown");
for (const client of clientsToShutdown) {
const reqs = Array.from(client.pendingRequests.values());
client.pendingRequests.clear();
for (const pending of reqs) {
pending.reject(err);
}
void (async () => {
const timeout = Bun.sleep(5_000);
const result = sendRequest(client, "shutdown", null).catch(() => {});
await Promise.race([result, timeout]);
try {
killProcessTree(client.proc.pid);
} catch {
client.proc.kill();
}
})().catch(() => {});
}
}
/** Status of an LSP server */
export interface LspServerStatus {
name: string;
status: "connecting" | "ready" | "error";
fileTypes: string[];
error?: string;
}
export function getActiveClients(): LspServerStatus[] {
return Array.from(clients.values()).map(client => ({
name: client.config.command,
status: "ready" as const,
fileTypes: client.config.fileTypes,
}));
}
// =============================================================================
// Process Cleanup
// =============================================================================
if (typeof process !== "undefined") {
process.on("beforeExit", shutdownAll);
process.on("SIGINT", () => {
shutdownAll();
process.exit(0);
});
process.on("SIGTERM", () => {
shutdownAll();
process.exit(0);
});
}

View file

@ -0,0 +1,314 @@
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { YAML } from "bun";
import { CONFIG_DIR_NAME } from "../../config.js";
import { isRecord } from "./helpers";
import DEFAULTS from "./defaults.json" with { type: "json" };
import type { ServerConfig } from "./types";
export interface LspConfig {
servers: Record<string, ServerConfig>;
/** Idle timeout in milliseconds. If set, LSP clients will be shutdown after this period of inactivity. Disabled by default. */
idleTimeoutMs?: number;
}
// =============================================================================
// Default Server Configuration Loading
// =============================================================================
const PID_TOKEN = "$PID";
interface NormalizedConfig {
servers: Record<string, Partial<ServerConfig>>;
idleTimeoutMs?: number;
}
function parseConfigContent(content: string, filePath: string): unknown {
const extension = path.extname(filePath).toLowerCase();
if (extension === ".yaml" || extension === ".yml") {
return YAML.parse(content) as unknown;
}
return JSON.parse(content) as unknown;
}
function normalizeConfig(value: unknown): NormalizedConfig | null {
if (!isRecord(value)) return null;
const idleTimeoutMs = typeof value.idleTimeoutMs === "number" ? value.idleTimeoutMs : undefined;
const rawServers = value.servers;
if (isRecord(rawServers)) {
return { servers: rawServers as Record<string, Partial<ServerConfig>>, idleTimeoutMs };
}
const servers = Object.fromEntries(Object.entries(value).filter(([key]) => key !== "idleTimeoutMs")) as Record<
string,
Partial<ServerConfig>
>;
return { servers, idleTimeoutMs };
}
function normalizeStringArray(value: unknown): string[] | null {
if (!Array.isArray(value)) return null;
const items = value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
return items.length > 0 ? items : null;
}
function normalizeServerConfig(name: string, config: Partial<ServerConfig>): ServerConfig | null {
const command = typeof config.command === "string" && config.command.length > 0 ? config.command : null;
const fileTypes = normalizeStringArray(config.fileTypes);
const rootMarkers = normalizeStringArray(config.rootMarkers);
if (!command || !fileTypes || !rootMarkers) {
return null;
}
const args = Array.isArray(config.args)
? config.args.filter((entry): entry is string => typeof entry === "string")
: undefined;
return {
...config,
command,
args,
fileTypes,
rootMarkers,
};
}
function readConfigFile(filePath: string): NormalizedConfig | null {
try {
const content = fs.readFileSync(filePath, "utf-8");
const parsed = parseConfigContent(content, filePath);
return normalizeConfig(parsed);
} catch {
return null;
}
}
function coerceServerConfigs(servers: Record<string, Partial<ServerConfig>>): Record<string, ServerConfig> {
const result: Record<string, ServerConfig> = {};
for (const [name, config] of Object.entries(servers)) {
const normalized = normalizeServerConfig(name, config);
if (normalized) {
result[name] = normalized;
}
}
return result;
}
function mergeServers(
base: Record<string, ServerConfig>,
overrides: Record<string, Partial<ServerConfig>>,
): Record<string, ServerConfig> {
const merged: Record<string, ServerConfig> = { ...base };
for (const [name, config] of Object.entries(overrides)) {
if (merged[name]) {
const candidate = { ...merged[name], ...config };
const normalized = normalizeServerConfig(name, candidate);
if (normalized) {
merged[name] = normalized;
}
} else {
const normalized = normalizeServerConfig(name, config);
if (normalized) {
merged[name] = normalized;
}
}
}
return merged;
}
function applyRuntimeDefaults(servers: Record<string, ServerConfig>): Record<string, ServerConfig> {
const updated: Record<string, ServerConfig> = { ...servers };
if (updated.omnisharp?.args) {
const args = updated.omnisharp.args.map(arg => (arg === PID_TOKEN ? String(process.pid) : arg));
updated.omnisharp = { ...updated.omnisharp, args };
}
return updated;
}
// =============================================================================
// Configuration Loading
// =============================================================================
export function hasRootMarkers(cwd: string, markers: string[]): boolean {
for (const marker of markers) {
if (marker.includes("*")) {
try {
const scan = new Bun.Glob(marker).scanSync({ cwd, onlyFiles: false });
for (const _ of scan) {
return true;
}
} catch {
// Failed to resolve glob root marker
}
continue;
}
const filePath = path.join(cwd, marker);
if (fs.existsSync(filePath)) {
return true;
}
}
return false;
}
// =============================================================================
// Local Binary Resolution
// =============================================================================
const LOCAL_BIN_PATHS: Array<{ markers: string[]; binDir: string }> = [
{ markers: ["package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"], binDir: "node_modules/.bin" },
{ markers: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"], binDir: ".venv/bin" },
{ markers: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"], binDir: "venv/bin" },
{ markers: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"], binDir: ".env/bin" },
{ markers: ["Gemfile", "Gemfile.lock"], binDir: "vendor/bundle/bin" },
{ markers: ["Gemfile", "Gemfile.lock"], binDir: "bin" },
{ markers: ["go.mod", "go.sum"], binDir: "bin" },
];
export function resolveCommand(command: string, cwd: string): string | null {
for (const { markers, binDir } of LOCAL_BIN_PATHS) {
if (hasRootMarkers(cwd, markers)) {
const localPath = path.join(cwd, binDir, command);
if (fs.existsSync(localPath)) {
return localPath;
}
}
}
return Bun.which(command);
}
/**
* Configuration file search paths (in priority order).
*/
function getConfigPaths(cwd: string): string[] {
const filenames = ["lsp.json", ".lsp.json", "lsp.yaml", ".lsp.yaml", "lsp.yml", ".lsp.yml"];
const paths: string[] = [];
// Project root files (highest priority)
for (const filename of filenames) {
paths.push(path.join(cwd, filename));
}
// Project config directory
const projectConfigDir = path.join(cwd, CONFIG_DIR_NAME);
for (const filename of filenames) {
paths.push(path.join(projectConfigDir, filename));
}
// User config directory
const userConfigDir = path.join(os.homedir(), CONFIG_DIR_NAME, "agent");
for (const filename of filenames) {
paths.push(path.join(userConfigDir, filename));
}
// User home root files (lowest priority fallback)
for (const filename of filenames) {
paths.push(path.join(os.homedir(), filename));
}
return paths;
}
/**
* Load LSP configuration.
*
* Priority (highest to lowest):
* 1. Project root: lsp.json/.lsp.json/lsp.yml/.lsp.yml/lsp.yaml/.lsp.yaml
* 2. Project config dir: {CONFIG_DIR_NAME}/lsp.* (+ hidden variants)
* 3. User config dir: ~/{CONFIG_DIR_NAME}/agent/lsp.* (+ hidden variants)
* 4. User home root: ~/lsp.*, ~/.lsp.*
* 5. Auto-detect from project markers + available binaries
*/
export function loadConfig(cwd: string): LspConfig {
let mergedServers = coerceServerConfigs(DEFAULTS);
const configPaths = getConfigPaths(cwd).reverse();
let hasOverrides = false;
let idleTimeoutMs: number | undefined;
for (const configPath of configPaths) {
const parsed = readConfigFile(configPath);
if (!parsed) continue;
const hasServerOverrides = Object.keys(parsed.servers).length > 0;
if (hasServerOverrides) {
hasOverrides = true;
mergedServers = mergeServers(mergedServers, parsed.servers);
}
if (parsed.idleTimeoutMs !== undefined) {
idleTimeoutMs = parsed.idleTimeoutMs;
}
}
if (!hasOverrides) {
const detected: Record<string, ServerConfig> = {};
const defaultsWithRuntime = applyRuntimeDefaults(mergedServers);
for (const [name, config] of Object.entries(defaultsWithRuntime)) {
if (!hasRootMarkers(cwd, config.rootMarkers)) continue;
const resolved = resolveCommand(config.command, cwd);
if (!resolved) continue;
detected[name] = { ...config, resolvedCommand: resolved };
}
return { servers: detected, idleTimeoutMs };
}
const mergedWithRuntime = applyRuntimeDefaults(mergedServers);
const available: Record<string, ServerConfig> = {};
for (const [name, config] of Object.entries(mergedWithRuntime)) {
if (config.disabled) continue;
const resolved = resolveCommand(config.command, cwd);
if (!resolved) continue;
available[name] = { ...config, resolvedCommand: resolved };
}
return { servers: available, idleTimeoutMs };
}
// =============================================================================
// Server Selection
// =============================================================================
export function getServersForFile(config: LspConfig, filePath: string): Array<[string, ServerConfig]> {
const ext = path.extname(filePath).toLowerCase();
const fileName = path.basename(filePath).toLowerCase();
const matches: Array<[string, ServerConfig]> = [];
for (const [name, serverConfig] of Object.entries(config.servers)) {
const supportsFile = serverConfig.fileTypes.some(fileType => {
const normalized = fileType.toLowerCase();
return normalized === ext || normalized === fileName;
});
if (supportsFile) {
matches.push([name, serverConfig]);
}
}
// Sort: primary servers (non-linters) first, then linters
return matches.sort((a, b) => {
const aIsLinter = a[1].isLinter ? 1 : 0;
const bIsLinter = b[1].isLinter ? 1 : 0;
return aIsLinter - bIsLinter;
});
}
export function getServerForFile(config: LspConfig, filePath: string): [string, ServerConfig] | null {
const servers = getServersForFile(config, filePath);
return servers.length > 0 ? servers[0] : null;
}
export function hasCapability(
config: ServerConfig,
capability: keyof NonNullable<ServerConfig["capabilities"]>,
): boolean {
return config.capabilities?.[capability] === true;
}

View file

@ -0,0 +1,456 @@
{
"rust-analyzer": {
"command": "rust-analyzer",
"args": [],
"fileTypes": [".rs"],
"rootMarkers": ["Cargo.toml", "rust-analyzer.toml"],
"initOptions": {},
"settings": {},
"capabilities": {
"flycheck": true,
"ssr": true,
"expandMacro": true,
"runnables": true,
"relatedTests": true
}
},
"clangd": {
"command": "clangd",
"args": ["--background-index", "--clang-tidy", "--header-insertion=iwyu"],
"fileTypes": [".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hxx", ".m", ".mm"],
"rootMarkers": ["compile_commands.json", "CMakeLists.txt", ".clangd", ".clang-format", "Makefile"]
},
"zls": {
"command": "zls",
"args": [],
"fileTypes": [".zig"],
"rootMarkers": ["build.zig", "build.zig.zon", "zls.json"]
},
"gopls": {
"command": "gopls",
"args": ["serve"],
"fileTypes": [".go", ".mod", ".sum"],
"rootMarkers": ["go.mod", "go.work", "go.sum"],
"settings": {
"gopls": {
"analyses": { "unusedparams": true, "shadow": true },
"staticcheck": true,
"gofumpt": true
}
}
},
"typescript-language-server": {
"command": "typescript-language-server",
"args": ["--stdio"],
"fileTypes": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
"rootMarkers": ["package.json", "tsconfig.json", "jsconfig.json"],
"initOptions": {
"hostInfo": "gsd-coding-agent",
"preferences": {
"includeInlayParameterNameHints": "all",
"includeInlayVariableTypeHints": true,
"includeInlayFunctionParameterTypeHints": true
}
}
},
"biome": {
"command": "biome",
"args": ["lsp-proxy"],
"fileTypes": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".jsonc"],
"rootMarkers": ["biome.json", "biome.jsonc"],
"isLinter": true
},
"eslint": {
"command": "vscode-eslint-language-server",
"args": ["--stdio"],
"fileTypes": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"],
"rootMarkers": [
".eslintrc",
".eslintrc.js",
".eslintrc.json",
".eslintrc.yml",
"eslint.config.js",
"eslint.config.mjs"
],
"isLinter": true,
"settings": {
"validate": "on",
"run": "onType"
}
},
"denols": {
"command": "deno",
"args": ["lsp"],
"fileTypes": [".ts", ".tsx", ".js", ".jsx"],
"rootMarkers": ["deno.json", "deno.jsonc", "deno.lock"],
"initOptions": {
"enable": true,
"lint": true,
"unstable": true
}
},
"vscode-html-language-server": {
"command": "vscode-html-language-server",
"args": ["--stdio"],
"fileTypes": [".html", ".htm"],
"rootMarkers": ["package.json", ".git"],
"initOptions": {
"provideFormatter": true
}
},
"vscode-css-language-server": {
"command": "vscode-css-language-server",
"args": ["--stdio"],
"fileTypes": [".css", ".scss", ".sass", ".less"],
"rootMarkers": ["package.json", ".git"],
"initOptions": {
"provideFormatter": true
}
},
"vscode-json-language-server": {
"command": "vscode-json-language-server",
"args": ["--stdio"],
"fileTypes": [".json", ".jsonc"],
"rootMarkers": ["package.json", ".git"],
"initOptions": {
"provideFormatter": true
}
},
"tailwindcss": {
"command": "tailwindcss-language-server",
"args": ["--stdio"],
"fileTypes": [".html", ".css", ".scss", ".js", ".jsx", ".ts", ".tsx", ".vue", ".svelte"],
"rootMarkers": ["tailwind.config.js", "tailwind.config.ts", "tailwind.config.mjs", "tailwind.config.cjs"]
},
"svelte": {
"command": "svelteserver",
"args": ["--stdio"],
"fileTypes": [".svelte"],
"rootMarkers": ["svelte.config.js", "svelte.config.mjs", "package.json"]
},
"vue-language-server": {
"command": "vue-language-server",
"args": ["--stdio"],
"fileTypes": [".vue"],
"rootMarkers": ["vue.config.js", "nuxt.config.js", "nuxt.config.ts", "package.json"]
},
"astro": {
"command": "astro-ls",
"args": ["--stdio"],
"fileTypes": [".astro"],
"rootMarkers": ["astro.config.mjs", "astro.config.js", "astro.config.ts"]
},
"pyright": {
"command": "pyright-langserver",
"args": ["--stdio"],
"fileTypes": [".py", ".pyi"],
"rootMarkers": ["pyproject.toml", "pyrightconfig.json", "setup.py", "setup.cfg", "requirements.txt", "Pipfile"],
"settings": {
"python": {
"analysis": {
"autoSearchPaths": true,
"diagnosticMode": "openFilesOnly",
"useLibraryCodeForTypes": true
}
}
}
},
"basedpyright": {
"command": "basedpyright-langserver",
"args": ["--stdio"],
"fileTypes": [".py", ".pyi"],
"rootMarkers": ["pyproject.toml", "pyrightconfig.json", "setup.py", "requirements.txt"],
"settings": {
"basedpyright": {
"analysis": {
"autoSearchPaths": true,
"diagnosticMode": "openFilesOnly",
"useLibraryCodeForTypes": true
}
}
}
},
"pylsp": {
"command": "pylsp",
"args": [],
"fileTypes": [".py"],
"rootMarkers": ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile"]
},
"ruff": {
"command": "ruff",
"args": ["server"],
"fileTypes": [".py", ".pyi"],
"rootMarkers": ["pyproject.toml", "ruff.toml", ".ruff.toml"],
"isLinter": true
},
"jdtls": {
"command": "jdtls",
"args": [],
"fileTypes": [".java"],
"rootMarkers": ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", ".project"]
},
"kotlin-language-server": {
"command": "kotlin-language-server",
"args": [],
"fileTypes": [".kt", ".kts"],
"rootMarkers": ["build.gradle", "build.gradle.kts", "pom.xml", "settings.gradle", "settings.gradle.kts"]
},
"metals": {
"command": "metals",
"args": [],
"fileTypes": [".scala", ".sbt", ".sc"],
"rootMarkers": ["build.sbt", "build.sc", "build.gradle", "pom.xml"],
"initOptions": {
"statusBarProvider": "show-message",
"isHttpEnabled": true
}
},
"hls": {
"command": "haskell-language-server-wrapper",
"args": ["--lsp"],
"fileTypes": [".hs", ".lhs"],
"rootMarkers": ["stack.yaml", "cabal.project", "hie.yaml", "package.yaml", "*.cabal"],
"settings": {
"haskell": {
"formattingProvider": "ormolu",
"checkProject": true
}
}
},
"ocamllsp": {
"command": "ocamllsp",
"args": [],
"fileTypes": [".ml", ".mli", ".mll", ".mly"],
"rootMarkers": ["dune-project", "dune-workspace", "*.opam", ".ocamlformat"]
},
"elixirls": {
"command": "elixir-ls",
"args": [],
"fileTypes": [".ex", ".exs", ".heex", ".eex"],
"rootMarkers": ["mix.exs", "mix.lock"],
"settings": {
"elixirLS": {
"dialyzerEnabled": true,
"fetchDeps": false
}
}
},
"erlangls": {
"command": "erlang_ls",
"args": [],
"fileTypes": [".erl", ".hrl"],
"rootMarkers": ["rebar.config", "erlang.mk", "rebar.lock"]
},
"gleam": {
"command": "gleam",
"args": ["lsp"],
"fileTypes": [".gleam"],
"rootMarkers": ["gleam.toml"]
},
"solargraph": {
"command": "solargraph",
"args": ["stdio"],
"fileTypes": [".rb", ".rake", ".gemspec"],
"rootMarkers": ["Gemfile", ".solargraph.yml", "Rakefile"],
"initOptions": {
"formatting": true
},
"settings": {
"solargraph": {
"diagnostics": true,
"completion": true,
"hover": true,
"formatting": true,
"references": true,
"rename": true,
"symbols": true
}
}
},
"ruby-lsp": {
"command": "ruby-lsp",
"args": [],
"fileTypes": [".rb", ".rake", ".gemspec", ".erb"],
"rootMarkers": ["Gemfile", ".ruby-version", ".ruby-gemset"],
"initOptions": {
"formatter": "auto"
}
},
"rubocop": {
"command": "rubocop",
"args": ["--lsp"],
"fileTypes": [".rb", ".rake"],
"rootMarkers": [".rubocop.yml", "Gemfile"],
"isLinter": true
},
"bashls": {
"command": "bash-language-server",
"args": ["start"],
"fileTypes": [".sh", ".bash", ".zsh"],
"rootMarkers": [".git"],
"settings": {
"bashIde": {
"globPattern": "*@(.sh|.inc|.bash|.command)"
}
}
},
"lua-language-server": {
"command": "lua-language-server",
"args": [],
"fileTypes": [".lua"],
"rootMarkers": [".luarc.json", ".luarc.jsonc", ".luacheckrc", ".stylua.toml", "stylua.toml"],
"settings": {
"Lua": {
"runtime": { "version": "LuaJIT" },
"diagnostics": { "globals": ["vim"] },
"workspace": { "checkThirdParty": false },
"telemetry": { "enable": false }
}
}
},
"intelephense": {
"command": "intelephense",
"args": ["--stdio"],
"fileTypes": [".php", ".phtml"],
"rootMarkers": ["composer.json", "composer.lock", ".git"]
},
"phpactor": {
"command": "phpactor",
"args": ["language-server"],
"fileTypes": [".php"],
"rootMarkers": ["composer.json", ".phpactor.json", ".phpactor.yml"]
},
"omnisharp": {
"command": "omnisharp",
"args": ["-z", "--hostPID", "$PID", "--encoding", "utf-8", "--languageserver"],
"fileTypes": [".cs", ".csx"],
"rootMarkers": ["*.sln", "*.csproj", "omnisharp.json", ".git"],
"settings": {
"FormattingOptions": { "EnableEditorConfigSupport": true },
"RoslynExtensionsOptions": { "EnableAnalyzersSupport": true }
}
},
"yamlls": {
"command": "yaml-language-server",
"args": ["--stdio"],
"fileTypes": [".yaml", ".yml"],
"rootMarkers": [".git"],
"settings": {
"yaml": {
"validate": true,
"format": { "enable": true },
"hover": true,
"completion": true
},
"redhat": { "telemetry": { "enabled": false } }
}
},
"taplo": {
"command": "taplo",
"args": ["lsp", "stdio"],
"fileTypes": [".toml"],
"rootMarkers": [".taplo.toml", "taplo.toml", ".git"]
},
"terraformls": {
"command": "terraform-ls",
"args": ["serve"],
"fileTypes": [".tf", ".tfvars"],
"rootMarkers": [".terraform", "terraform.tfstate", "*.tf"]
},
"dockerls": {
"command": "docker-langserver",
"args": ["--stdio"],
"fileTypes": [".dockerfile", "Dockerfile"],
"rootMarkers": ["Dockerfile", "docker-compose.yml", "docker-compose.yaml", ".dockerignore"]
},
"helm-ls": {
"command": "helm_ls",
"args": ["serve"],
"fileTypes": [".yaml", ".yml", ".tpl"],
"rootMarkers": ["Chart.yaml", "Chart.yml"]
},
"nixd": {
"command": "nixd",
"args": [],
"fileTypes": [".nix"],
"rootMarkers": ["flake.nix", "default.nix", "shell.nix"]
},
"nil": {
"command": "nil",
"args": [],
"fileTypes": [".nix"],
"rootMarkers": ["flake.nix", "default.nix", "shell.nix"]
},
"ols": {
"command": "ols",
"args": [],
"fileTypes": [".odin"],
"rootMarkers": ["ols.json", ".git"]
},
"dartls": {
"command": "dart",
"args": ["language-server", "--protocol=lsp"],
"fileTypes": [".dart"],
"rootMarkers": ["pubspec.yaml", "pubspec.lock"],
"initOptions": {
"closingLabels": true,
"flutterOutline": true,
"outline": true
}
},
"marksman": {
"command": "marksman",
"args": ["server"],
"fileTypes": [".md", ".markdown"],
"rootMarkers": [".marksman.toml", ".git"]
},
"texlab": {
"command": "texlab",
"args": [],
"fileTypes": [".tex", ".bib", ".sty", ".cls"],
"rootMarkers": [".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot", "Tectonic.toml"],
"settings": {
"texlab": {
"build": {
"executable": "latexmk",
"args": ["-pdf", "-interaction=nonstopmode", "-synctex=1", "%f"]
},
"chktex": { "onOpenAndSave": true }
}
}
},
"graphql": {
"command": "graphql-lsp",
"args": ["server", "-m", "stream"],
"fileTypes": [".graphql", ".gql"],
"rootMarkers": [".graphqlrc", ".graphqlrc.json", ".graphqlrc.yml", ".graphqlrc.yaml", "graphql.config.js"]
},
"prismals": {
"command": "prisma-language-server",
"args": ["--stdio"],
"fileTypes": [".prisma"],
"rootMarkers": ["schema.prisma", "prisma/schema.prisma"]
},
"vimls": {
"command": "vim-language-server",
"args": ["--stdio"],
"fileTypes": [".vim", ".vimrc"],
"rootMarkers": [".git"],
"initOptions": {
"isNeovim": true,
"diagnostic": { "enable": true }
}
},
"emmet-language-server": {
"command": "emmet-language-server",
"args": ["--stdio"],
"fileTypes": [".html", ".css", ".scss", ".less", ".jsx", ".tsx", ".vue", ".svelte"],
"rootMarkers": [".git"]
},
"sourcekit-lsp": {
"command": "sourcekit-lsp",
"args": [],
"fileTypes": [".swift"],
"rootMarkers": ["Package.swift", "*.xcodeproj", "*.xcworkspace", "project.yml", ".swiftpm"]
}
}

View file

@ -0,0 +1,109 @@
import * as fs from "node:fs/promises";
import path from "node:path";
import type { CreateFile, DeleteFile, RenameFile, TextDocumentEdit, TextEdit, WorkspaceEdit } from "./types";
import { uriToFile } from "./utils";
// =============================================================================
// Text Edit Application
// =============================================================================
/**
* Apply text edits to a string in-memory.
* Edits are applied in reverse order (bottom-to-top) to preserve line/character indices.
*/
export function applyTextEditsToString(content: string, edits: TextEdit[]): string {
const lines = content.split("\n");
// Sort edits in reverse order (bottom-to-top, right-to-left)
const sortedEdits = [...edits].sort((a, b) => {
if (a.range.start.line !== b.range.start.line) {
return b.range.start.line - a.range.start.line;
}
return b.range.start.character - a.range.start.character;
});
for (const edit of sortedEdits) {
const { start, end } = edit.range;
// Single-line edit: replace substring within same line
if (start.line === end.line) {
const line = lines[start.line] || "";
lines[start.line] = line.slice(0, start.character) + edit.newText + line.slice(end.character);
} else {
// Multi-line edit: splice across multiple lines
const startLine = lines[start.line] || "";
const endLine = lines[end.line] || "";
const newContent = startLine.slice(0, start.character) + edit.newText + endLine.slice(end.character);
lines.splice(start.line, end.line - start.line + 1, ...newContent.split("\n"));
}
}
return lines.join("\n");
}
/**
* Apply text edits to a file.
* Edits are applied in reverse order (bottom-to-top) to preserve line/character indices.
*/
export async function applyTextEdits(filePath: string, edits: TextEdit[]): Promise<void> {
const content = await Bun.file(filePath).text();
const result = applyTextEditsToString(content, edits);
await Bun.write(filePath, result);
}
// =============================================================================
// Workspace Edit Application
// =============================================================================
/**
* Apply a workspace edit (collection of file changes).
* Returns array of applied change descriptions.
*/
export async function applyWorkspaceEdit(edit: WorkspaceEdit, cwd: string): Promise<string[]> {
const applied: string[] = [];
// Handle changes map (legacy format)
if (edit.changes) {
for (const [uri, textEdits] of Object.entries(edit.changes)) {
const filePath = uriToFile(uri);
await applyTextEdits(filePath, textEdits);
applied.push(`Applied ${textEdits.length} edit(s) to ${path.relative(cwd, filePath)}`);
}
}
// Handle documentChanges array (modern format)
if (edit.documentChanges) {
for (const change of edit.documentChanges) {
if ("textDocument" in change && change.textDocument && "edits" in change && change.edits) {
// TextDocumentEdit
const docChange = change as TextDocumentEdit;
const filePath = uriToFile(docChange.textDocument.uri);
const textEdits = docChange.edits.filter((e): e is TextEdit => "range" in e && "newText" in e);
await applyTextEdits(filePath, textEdits);
applied.push(`Applied ${textEdits.length} edit(s) to ${path.relative(cwd, filePath)}`);
} else if ("kind" in change && change.kind) {
// Resource operations
if (change.kind === "create") {
const createOp = change as CreateFile;
const filePath = uriToFile(createOp.uri);
await Bun.write(filePath, "");
applied.push(`Created ${path.relative(cwd, filePath)}`);
} else if (change.kind === "rename") {
const renameOp = change as RenameFile;
const oldPath = uriToFile(renameOp.oldUri);
const newPath = uriToFile(renameOp.newUri);
await fs.mkdir(path.dirname(newPath), { recursive: true });
await fs.rename(oldPath, newPath);
applied.push(`Renamed ${path.relative(cwd, oldPath)}${path.relative(cwd, newPath)}`);
} else if (change.kind === "delete") {
const deleteOp = change as DeleteFile;
const filePath = uriToFile(deleteOp.uri);
await fs.rm(filePath, { recursive: true });
applied.push(`Deleted ${path.relative(cwd, filePath)}`);
}
}
}
}
return applied;
}

View file

@ -0,0 +1,54 @@
/**
* Local helpers replacing @oh-my-pi/pi-utils and tool-errors/tool-timeouts imports.
*/
export class ToolAbortError extends Error {
constructor() {
super("Tool execution aborted");
this.name = "ToolAbortError";
}
}
export function throwIfAborted(signal?: AbortSignal): void {
if (signal?.aborted) {
throw new ToolAbortError();
}
}
export function isEnoent(err: unknown): boolean {
return (err as any)?.code === "ENOENT";
}
export function isRecord(v: unknown): v is Record<string, unknown> {
return typeof v === "object" && v !== null && !Array.isArray(v);
}
export function clampTimeout(timeout?: number): number {
return Math.max(5, Math.min(60, timeout ?? 20));
}
/**
* Run a promise, rejecting if the signal aborts.
*/
export async function untilAborted<T>(signal: AbortSignal | undefined, fn: () => Promise<T>): Promise<T> {
if (signal?.aborted) {
throw new ToolAbortError();
}
if (!signal) {
return fn();
}
return new Promise<T>((resolve, reject) => {
const onAbort = () => reject(new ToolAbortError());
signal.addEventListener("abort", onAbort, { once: true });
fn().then(
result => {
signal.removeEventListener("abort", onAbort);
resolve(result);
},
err => {
signal.removeEventListener("abort", onAbort);
reject(err);
},
);
});
}

View file

@ -0,0 +1,928 @@
import * as fs from "node:fs";
import path from "node:path";
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@gsd/pi-agent-core";
import {
ensureFileOpen,
getActiveClients,
getOrCreateClient,
type LspServerStatus,
refreshFile,
sendRequest,
setIdleTimeout,
WARMUP_TIMEOUT_MS,
} from "./client";
import { getServersForFile, type LspConfig, loadConfig } from "./config";
import { applyWorkspaceEdit } from "./edits";
import { ToolAbortError, clampTimeout, throwIfAborted } from "./helpers";
import lspDescription from "./lsp.md" with { type: "text" };
import { detectLspmux } from "./lspmux";
import {
type CodeAction,
type CodeActionContext,
type Command,
type Diagnostic,
type DocumentSymbol,
type Hover,
type Location,
type LocationLink,
type LspClient,
type LspParams,
type LspToolDetails,
lspSchema,
type ServerConfig,
type SymbolInformation,
type WorkspaceEdit,
} from "./types";
import {
applyCodeAction,
collectGlobMatches,
dedupeWorkspaceSymbols,
extractHoverText,
fileToUri,
filterWorkspaceSymbols,
formatCodeAction,
formatDiagnostic,
formatDiagnosticsSummary,
formatDocumentSymbol,
formatGroupedDiagnosticMessages,
formatLocation,
formatSymbolInformation,
formatWorkspaceEdit,
hasGlobPattern,
readLocationContext,
resolveSymbolColumn,
sortDiagnostics,
symbolKindToIcon,
uriToFile,
} from "./utils";
export type { LspServerStatus } from "./client";
export type { LspToolDetails } from "./types";
export { lspSchema } from "./types";
// =============================================================================
// Warmup API
// =============================================================================
export interface LspWarmupResult {
servers: Array<{
name: string;
status: "ready" | "error";
fileTypes: string[];
error?: string;
}>;
}
export async function warmupLspServers(cwd: string): Promise<LspWarmupResult> {
const config = loadConfig(cwd);
setIdleTimeout(config.idleTimeoutMs);
const servers: LspWarmupResult["servers"] = [];
const lspServers = getLspServers(config);
const results = await Promise.allSettled(
lspServers.map(async ([name, serverConfig]) => {
const client = await getOrCreateClient(serverConfig, cwd, serverConfig.warmupTimeoutMs ?? WARMUP_TIMEOUT_MS);
return { name, client, fileTypes: serverConfig.fileTypes };
}),
);
for (let i = 0; i < results.length; i++) {
const result = results[i];
const [name, serverConfig] = lspServers[i];
if (result.status === "fulfilled") {
servers.push({
name: result.value.name,
status: "ready",
fileTypes: result.value.fileTypes,
});
} else {
servers.push({
name,
status: "error",
fileTypes: serverConfig.fileTypes,
error: result.reason?.message ?? String(result.reason),
});
}
}
return { servers };
}
export function getLspStatus(): LspServerStatus[] {
return getActiveClients();
}
// =============================================================================
// Internal Helpers
// =============================================================================
const configCache = new Map<string, LspConfig>();
function getConfig(cwd: string): LspConfig {
let config = configCache.get(cwd);
if (!config) {
config = loadConfig(cwd);
setIdleTimeout(config.idleTimeoutMs);
configCache.set(cwd, config);
}
return config;
}
function getLspServers(config: LspConfig): Array<[string, ServerConfig]> {
return Object.entries(config.servers) as Array<[string, ServerConfig]>;
}
function getLspServersForFile(config: LspConfig, filePath: string): Array<[string, ServerConfig]> {
return getServersForFile(config, filePath);
}
function getLspServerForFile(config: LspConfig, filePath: string): [string, ServerConfig] | null {
const servers = getLspServersForFile(config, filePath);
return servers.length > 0 ? servers[0] : null;
}
const DIAGNOSTIC_MESSAGE_LIMIT = 50;
const SINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS = 3000;
const BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS = 400;
const MAX_GLOB_DIAGNOSTIC_TARGETS = 20;
const WORKSPACE_SYMBOL_LIMIT = 200;
function limitDiagnosticMessages(messages: string[]): string[] {
if (messages.length <= DIAGNOSTIC_MESSAGE_LIMIT) {
return messages;
}
return messages.slice(0, DIAGNOSTIC_MESSAGE_LIMIT);
}
const LOCATION_CONTEXT_LINES = 1;
const REFERENCE_CONTEXT_LIMIT = 50;
function normalizeLocationResult(result: Location | Location[] | LocationLink | LocationLink[] | null): Location[] {
if (!result) return [];
const raw = Array.isArray(result) ? result : [result];
return raw.flatMap(loc => {
if ("uri" in loc) {
return [loc as Location];
}
if ("targetUri" in loc) {
const link = loc as LocationLink;
return [{ uri: link.targetUri, range: link.targetSelectionRange ?? link.targetRange }];
}
return [];
});
}
async function formatLocationWithContext(location: Location, cwd: string): Promise<string> {
const header = ` ${formatLocation(location, cwd)}`;
const context = await readLocationContext(
uriToFile(location.uri),
location.range.start.line + 1,
LOCATION_CONTEXT_LINES,
);
if (context.length === 0) {
return header;
}
return `${header}\n${context.map(lineText => ` ${lineText}`).join("\n")}`;
}
async function reloadServer(client: LspClient, serverName: string, signal?: AbortSignal): Promise<string> {
let output = `Restarted ${serverName}`;
const reloadMethods = ["rust-analyzer/reloadWorkspace", "workspace/didChangeConfiguration"];
for (const method of reloadMethods) {
try {
await sendRequest(client, method, method.includes("Configuration") ? { settings: {} } : null, signal);
output = `Reloaded ${serverName}`;
break;
} catch {
// Method not supported, try next
}
}
if (output.startsWith("Restarted")) {
client.proc.kill();
}
return output;
}
async function waitForDiagnostics(
client: LspClient,
uri: string,
timeoutMs = 3000,
signal?: AbortSignal,
minVersion?: number,
): Promise<Diagnostic[]> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
throwIfAborted(signal);
const diagnostics = client.diagnostics.get(uri);
const versionOk = minVersion === undefined || client.diagnosticsVersion > minVersion;
if (diagnostics !== undefined && versionOk) return diagnostics;
await Bun.sleep(100);
}
return client.diagnostics.get(uri) ?? [];
}
// =============================================================================
// Workspace Diagnostics
// =============================================================================
interface ProjectType {
type: "rust" | "typescript" | "go" | "python" | "unknown";
command?: string[];
description: string;
}
function detectProjectType(cwd: string): ProjectType {
if (fs.existsSync(path.join(cwd, "Cargo.toml"))) {
return { type: "rust", command: ["cargo", "check", "--message-format=short"], description: "Rust (cargo check)" };
}
if (fs.existsSync(path.join(cwd, "tsconfig.json"))) {
return { type: "typescript", command: ["npx", "tsc", "--noEmit"], description: "TypeScript (tsc --noEmit)" };
}
if (fs.existsSync(path.join(cwd, "go.mod"))) {
return { type: "go", command: ["go", "build", "./..."], description: "Go (go build)" };
}
if (fs.existsSync(path.join(cwd, "pyproject.toml")) || fs.existsSync(path.join(cwd, "pyrightconfig.json"))) {
return { type: "python", command: ["pyright"], description: "Python (pyright)" };
}
return { type: "unknown", description: "Unknown project type" };
}
async function runWorkspaceDiagnostics(
cwd: string,
signal?: AbortSignal,
): Promise<{ output: string; projectType: ProjectType }> {
throwIfAborted(signal);
const projectType = detectProjectType(cwd);
if (!projectType.command) {
return {
output: "Cannot detect project type. Supported: Rust (Cargo.toml), TypeScript (tsconfig.json), Go (go.mod), Python (pyproject.toml)",
projectType,
};
}
const proc = Bun.spawn(projectType.command, {
cwd,
stdout: "pipe",
stderr: "pipe",
windowsHide: true,
});
const abortHandler = () => {
proc.kill();
};
if (signal) {
signal.addEventListener("abort", abortHandler, { once: true });
}
try {
const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
await proc.exited;
throwIfAborted(signal);
const combined = (stdout + stderr).trim();
if (!combined) {
return { output: "No issues found", projectType };
}
const lines = combined.split("\n");
if (lines.length > 50) {
return { output: `${lines.slice(0, 50).join("\n")}\n... and ${lines.length - 50} more lines`, projectType };
}
return { output: combined, projectType };
} catch (e) {
if (signal?.aborted) {
throw new ToolAbortError();
}
return { output: `Failed to run ${projectType.command.join(" ")}: ${e}`, projectType };
} finally {
signal?.removeEventListener("abort", abortHandler);
}
}
// =============================================================================
// Path Resolution
// =============================================================================
function resolveToCwd(file: string, cwd: string): string {
return path.resolve(cwd, file);
}
// =============================================================================
// Tool Factory
// =============================================================================
/**
* Create an LSP tool configured for a specific working directory.
*/
export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolDetails> {
return {
name: "lsp",
label: "LSP",
description: lspDescription,
parameters: lspSchema,
async execute(
_toolCallId: string,
params: LspParams,
signal?: AbortSignal,
_onUpdate?: AgentToolUpdateCallback<LspToolDetails>,
): Promise<AgentToolResult<LspToolDetails>> {
const { action, file, line, symbol, occurrence, query, new_name, apply, timeout } = params;
const timeoutSec = clampTimeout(timeout);
const timeoutSignal = AbortSignal.timeout(timeoutSec * 1000);
signal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
throwIfAborted(signal);
const config = getConfig(cwd);
// Status action doesn't need a file
if (action === "status") {
const servers = Object.keys(config.servers);
const lspmuxState = await detectLspmux();
const lspmuxStatus = lspmuxState.available
? lspmuxState.running
? "lspmux: active (multiplexing enabled)"
: "lspmux: installed but server not running"
: "";
const serverStatus =
servers.length > 0
? `Active language servers: ${servers.join(", ")}`
: "No language servers configured for this project";
const output = lspmuxStatus ? `${serverStatus}\n${lspmuxStatus}` : serverStatus;
return {
content: [{ type: "text", text: output }],
details: { action, success: true, request: params },
};
}
// Diagnostics can be batch or single-file
if (action === "diagnostics") {
if (!file) {
const result = await runWorkspaceDiagnostics(cwd, signal);
return {
content: [
{
type: "text",
text: `Workspace diagnostics (${result.projectType.description}):\n${result.output}`,
},
],
details: { action, success: true, request: params },
};
}
let targets: string[];
let truncatedGlobTargets = false;
if (hasGlobPattern(file)) {
const globMatches = await collectGlobMatches(file, cwd, MAX_GLOB_DIAGNOSTIC_TARGETS);
targets = globMatches.matches;
truncatedGlobTargets = globMatches.truncated;
} else {
targets = [file];
}
if (targets.length === 0) {
return {
content: [{ type: "text", text: `No files matched pattern: ${file}` }],
details: { action, success: true, request: params },
};
}
const detailed = targets.length > 1 || truncatedGlobTargets;
const diagnosticsWaitTimeoutMs = detailed
? Math.min(BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS, timeoutSec * 1000)
: Math.min(SINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS, timeoutSec * 1000);
const results: string[] = [];
const allServerNames = new Set<string>();
if (truncatedGlobTargets) {
results.push(
`[W] Pattern matched more than ${MAX_GLOB_DIAGNOSTIC_TARGETS} files; showing first ${MAX_GLOB_DIAGNOSTIC_TARGETS}. Narrow the glob or use workspace diagnostics.`,
);
}
for (const target of targets) {
throwIfAborted(signal);
const resolved = resolveToCwd(target, cwd);
const servers = getServersForFile(config, resolved);
if (servers.length === 0) {
results.push(`[E] ${target}: No language server found`);
continue;
}
const uri = fileToUri(resolved);
const relPath = path.relative(cwd, resolved);
const allDiagnostics: Diagnostic[] = [];
for (const [serverName, serverConfig] of servers) {
allServerNames.add(serverName);
try {
throwIfAborted(signal);
const client = await getOrCreateClient(serverConfig, cwd);
const minVersion = client.diagnosticsVersion;
await refreshFile(client, resolved, signal);
const diagnostics = await waitForDiagnostics(
client,
uri,
diagnosticsWaitTimeoutMs,
signal,
minVersion,
);
allDiagnostics.push(...diagnostics);
} catch (err) {
if (err instanceof ToolAbortError || signal?.aborted) {
throw err;
}
}
}
// Deduplicate
const seen = new Set<string>();
const uniqueDiagnostics: Diagnostic[] = [];
for (const d of allDiagnostics) {
const key = `${d.range.start.line}:${d.range.start.character}:${d.range.end.line}:${d.range.end.character}:${d.message}`;
if (!seen.has(key)) {
seen.add(key);
uniqueDiagnostics.push(d);
}
}
sortDiagnostics(uniqueDiagnostics);
if (!detailed && targets.length === 1) {
if (uniqueDiagnostics.length === 0) {
return {
content: [{ type: "text", text: "No diagnostics" }],
details: { action, serverName: Array.from(allServerNames).join(", "), success: true },
};
}
const summary = formatDiagnosticsSummary(uniqueDiagnostics);
const formatted = uniqueDiagnostics.map(d => formatDiagnostic(d, relPath));
const output = `${summary}:\n${formatGroupedDiagnosticMessages(formatted)}`;
return {
content: [{ type: "text", text: output }],
details: { action, serverName: Array.from(allServerNames).join(", "), success: true },
};
}
if (uniqueDiagnostics.length === 0) {
results.push(`OK ${relPath}: no issues`);
} else {
const summary = formatDiagnosticsSummary(uniqueDiagnostics);
results.push(`[E] ${relPath}: ${summary}`);
const formatted = uniqueDiagnostics.map(d => formatDiagnostic(d, relPath));
results.push(formatGroupedDiagnosticMessages(formatted));
}
}
return {
content: [{ type: "text", text: results.join("\n") }],
details: { action, serverName: Array.from(allServerNames).join(", "), success: true },
};
}
const requiresFile = !file && action !== "symbols" && action !== "reload";
if (requiresFile) {
return {
content: [{ type: "text", text: "Error: file parameter required for this action" }],
details: { action, success: false },
};
}
const resolvedFile = file ? resolveToCwd(file, cwd) : null;
// Workspace symbol search (no file)
if (action === "symbols" && !resolvedFile) {
const normalizedQuery = query?.trim();
if (!normalizedQuery) {
return {
content: [{ type: "text", text: "Error: query parameter required for workspace symbol search" }],
details: { action, success: false, request: params },
};
}
const servers = getLspServers(config);
if (servers.length === 0) {
return {
content: [{ type: "text", text: "No language server found for this action" }],
details: { action, success: false, request: params },
};
}
const aggregatedSymbols: SymbolInformation[] = [];
const respondingServers = new Set<string>();
for (const [workspaceServerName, workspaceServerConfig] of servers) {
throwIfAborted(signal);
try {
const workspaceClient = await getOrCreateClient(workspaceServerConfig, cwd);
const workspaceResult = (await sendRequest(
workspaceClient,
"workspace/symbol",
{ query: normalizedQuery },
signal,
)) as SymbolInformation[] | null;
if (!workspaceResult || workspaceResult.length === 0) {
continue;
}
respondingServers.add(workspaceServerName);
aggregatedSymbols.push(...filterWorkspaceSymbols(workspaceResult, normalizedQuery));
} catch (err) {
if (err instanceof ToolAbortError || signal?.aborted) {
throw err;
}
}
}
const dedupedSymbols = dedupeWorkspaceSymbols(aggregatedSymbols);
if (dedupedSymbols.length === 0) {
return {
content: [{ type: "text", text: `No symbols matching "${normalizedQuery}"` }],
details: {
action,
serverName: Array.from(respondingServers).join(", "),
success: true,
request: params,
},
};
}
const limitedSymbols = dedupedSymbols.slice(0, WORKSPACE_SYMBOL_LIMIT);
const lines = limitedSymbols.map(s => formatSymbolInformation(s, cwd));
const truncationLine =
dedupedSymbols.length > WORKSPACE_SYMBOL_LIMIT
? `\n... ${dedupedSymbols.length - WORKSPACE_SYMBOL_LIMIT} additional symbol(s) omitted`
: "";
return {
content: [
{
type: "text",
text: `Found ${dedupedSymbols.length} symbol(s) matching "${normalizedQuery}":\n${lines.map(l => ` ${l}`).join("\n")}${truncationLine}`,
},
],
details: {
action,
serverName: Array.from(respondingServers).join(", "),
success: true,
request: params,
},
};
}
// Reload all servers (no file)
if (action === "reload" && !resolvedFile) {
const servers = getLspServers(config);
if (servers.length === 0) {
return {
content: [{ type: "text", text: "No language server found for this action" }],
details: { action, success: false, request: params },
};
}
const outputs: string[] = [];
for (const [workspaceServerName, workspaceServerConfig] of servers) {
throwIfAborted(signal);
try {
const workspaceClient = await getOrCreateClient(workspaceServerConfig, cwd);
outputs.push(await reloadServer(workspaceClient, workspaceServerName, signal));
} catch (err) {
if (err instanceof ToolAbortError || signal?.aborted) {
throw err;
}
const errorMessage = err instanceof Error ? err.message : String(err);
outputs.push(`Failed to reload ${workspaceServerName}: ${errorMessage}`);
}
}
return {
content: [{ type: "text", text: outputs.join("\n") }],
details: { action, serverName: servers.map(([name]) => name).join(", "), success: true, request: params },
};
}
// File-specific actions
const serverInfo = resolvedFile ? getLspServerForFile(config, resolvedFile) : null;
if (!serverInfo) {
return {
content: [{ type: "text", text: "No language server found for this action" }],
details: { action, success: false },
};
}
const [serverName, serverConfig] = serverInfo;
try {
const client = await getOrCreateClient(serverConfig, cwd);
const targetFile = resolvedFile;
if (targetFile) {
await ensureFileOpen(client, targetFile, signal);
}
const uri = targetFile ? fileToUri(targetFile) : "";
const resolvedLine = line ?? 1;
const resolvedCharacter = targetFile
? await resolveSymbolColumn(targetFile, resolvedLine, symbol, occurrence)
: 0;
const position = { line: resolvedLine - 1, character: resolvedCharacter };
let output: string;
switch (action) {
case "definition": {
const result = (await sendRequest(
client,
"textDocument/definition",
{
textDocument: { uri },
position,
},
signal,
)) as Location | Location[] | LocationLink | LocationLink[] | null;
const locations = normalizeLocationResult(result);
if (locations.length === 0) {
output = "No definition found";
} else {
const lines = await Promise.all(
locations.map(location => formatLocationWithContext(location, cwd)),
);
output = `Found ${locations.length} definition(s):\n${lines.join("\n")}`;
}
break;
}
case "type_definition": {
const result = (await sendRequest(
client,
"textDocument/typeDefinition",
{
textDocument: { uri },
position,
},
signal,
)) as Location | Location[] | LocationLink | LocationLink[] | null;
const locations = normalizeLocationResult(result);
if (locations.length === 0) {
output = "No type definition found";
} else {
const lines = await Promise.all(
locations.map(location => formatLocationWithContext(location, cwd)),
);
output = `Found ${locations.length} type definition(s):\n${lines.join("\n")}`;
}
break;
}
case "implementation": {
const result = (await sendRequest(
client,
"textDocument/implementation",
{
textDocument: { uri },
position,
},
signal,
)) as Location | Location[] | LocationLink | LocationLink[] | null;
const locations = normalizeLocationResult(result);
if (locations.length === 0) {
output = "No implementation found";
} else {
const lines = await Promise.all(
locations.map(location => formatLocationWithContext(location, cwd)),
);
output = `Found ${locations.length} implementation(s):\n${lines.join("\n")}`;
}
break;
}
case "references": {
const result = (await sendRequest(
client,
"textDocument/references",
{
textDocument: { uri },
position,
context: { includeDeclaration: true },
},
signal,
)) as Location[] | null;
if (!result || result.length === 0) {
output = "No references found";
} else {
const contextualReferences = result.slice(0, REFERENCE_CONTEXT_LIMIT);
const plainReferences = result.slice(REFERENCE_CONTEXT_LIMIT);
const contextualLines = await Promise.all(
contextualReferences.map(location => formatLocationWithContext(location, cwd)),
);
const plainLines = plainReferences.map(location => ` ${formatLocation(location, cwd)}`);
const lines = plainLines.length
? [
...contextualLines,
` ... ${plainLines.length} additional reference(s) shown without context`,
...plainLines,
]
: contextualLines;
output = `Found ${result.length} reference(s):\n${lines.join("\n")}`;
}
break;
}
case "hover": {
const result = (await sendRequest(
client,
"textDocument/hover",
{
textDocument: { uri },
position,
},
signal,
)) as Hover | null;
if (!result || !result.contents) {
output = "No hover information";
} else {
output = extractHoverText(result.contents);
}
break;
}
case "code_actions": {
const diagnostics = client.diagnostics.get(uri) ?? [];
const context: CodeActionContext = {
diagnostics,
only: !apply && query ? [query] : undefined,
triggerKind: 1,
};
const result = (await sendRequest(
client,
"textDocument/codeAction",
{
textDocument: { uri },
range: { start: position, end: position },
context,
},
signal,
)) as (CodeAction | Command)[] | null;
if (!result || result.length === 0) {
output = "No code actions available";
break;
}
if (apply === true && query) {
const normalizedQuery = query.trim();
if (normalizedQuery.length === 0) {
output = "Error: query parameter required when apply=true for code_actions";
break;
}
const parsedIndex = /^\d+$/.test(normalizedQuery) ? Number.parseInt(normalizedQuery, 10) : null;
const selectedAction = result.find(
(actionItem, index) =>
(parsedIndex !== null && index === parsedIndex) ||
actionItem.title.toLowerCase().includes(normalizedQuery.toLowerCase()),
);
if (!selectedAction) {
const actionLines = result.map((actionItem, index) => ` ${formatCodeAction(actionItem, index)}`);
output = `No code action matches "${normalizedQuery}". Available actions:\n${actionLines.join("\n")}`;
break;
}
const appliedAction = await applyCodeAction(selectedAction, {
resolveCodeAction: async actionItem =>
(await sendRequest(client, "codeAction/resolve", actionItem, signal)) as CodeAction,
applyWorkspaceEdit: async edit => applyWorkspaceEdit(edit, cwd),
executeCommand: async commandItem => {
await sendRequest(
client,
"workspace/executeCommand",
{
command: commandItem.command,
arguments: commandItem.arguments ?? [],
},
signal,
);
},
});
if (!appliedAction) {
output = `Action "${selectedAction.title}" has no workspace edit or command to apply`;
break;
}
const summaryLines: string[] = [];
if (appliedAction.edits.length > 0) {
summaryLines.push(" Workspace edit:");
summaryLines.push(...appliedAction.edits.map(item => ` ${item}`));
}
if (appliedAction.executedCommands.length > 0) {
summaryLines.push(" Executed command(s):");
summaryLines.push(...appliedAction.executedCommands.map(commandName => ` ${commandName}`));
}
output = `Applied "${appliedAction.title}":\n${summaryLines.join("\n")}`;
break;
}
const actionLines = result.map((actionItem, index) => ` ${formatCodeAction(actionItem, index)}`);
output = `${result.length} code action(s):\n${actionLines.join("\n")}`;
break;
}
case "symbols": {
if (!targetFile) {
output = "Error: file parameter required for document symbols";
break;
}
const result = (await sendRequest(
client,
"textDocument/documentSymbol",
{
textDocument: { uri },
},
signal,
)) as (DocumentSymbol | SymbolInformation)[] | null;
if (!result || result.length === 0) {
output = "No symbols found";
} else {
const relPath = path.relative(cwd, targetFile);
if ("selectionRange" in result[0]) {
const lines = (result as DocumentSymbol[]).flatMap(s => formatDocumentSymbol(s));
output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
} else {
const lines = (result as SymbolInformation[]).map(s => {
const line = s.location.range.start.line + 1;
const icon = symbolKindToIcon(s.kind);
return `${icon} ${s.name} @ line ${line}`;
});
output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
}
}
break;
}
case "rename": {
if (!new_name) {
return {
content: [{ type: "text", text: "Error: new_name parameter required for rename" }],
details: { action, serverName, success: false },
};
}
const result = (await sendRequest(
client,
"textDocument/rename",
{
textDocument: { uri },
position,
newName: new_name,
},
signal,
)) as WorkspaceEdit | null;
if (!result) {
output = "Rename returned no edits";
} else {
const shouldApply = apply !== false;
if (shouldApply) {
const applied = await applyWorkspaceEdit(result, cwd);
output = `Applied rename:\n${applied.map(a => ` ${a}`).join("\n")}`;
} else {
const preview = formatWorkspaceEdit(result, cwd);
output = `Rename preview:\n${preview.map(p => ` ${p}`).join("\n")}`;
}
}
break;
}
case "reload": {
output = await reloadServer(client, serverName, signal);
break;
}
default:
output = `Unknown action: ${action}`;
}
return {
content: [{ type: "text", text: output }],
details: { serverName, action, success: true, request: params },
};
} catch (err) {
if (err instanceof ToolAbortError || signal?.aborted) {
throw new ToolAbortError();
}
const errorMessage = err instanceof Error ? err.message : String(err);
return {
content: [{ type: "text", text: `LSP error: ${errorMessage}` }],
details: { serverName, action, success: false, request: params },
};
}
},
};
}
/**
* Default LSP tool using process.cwd().
*/
export const lspTool = createLspTool(process.cwd());

View file

@ -0,0 +1,33 @@
Interacts with Language Server Protocol servers for code intelligence.
<operations>
- `diagnostics`: Get errors/warnings for file, glob, or entire workspace (no file)
- `definition`: Go to symbol definition → file path + position + 3-line source context
- `type_definition`: Go to symbol type definition → file path + position + 3-line source context
- `implementation`: Find concrete implementations → file path + position + 3-line source context
- `references`: Find references → locations with 3-line source context (first 50), remaining location-only
- `hover`: Get type info and documentation → type signature + docs
- `symbols`: List symbols in file, or search workspace (with query, no file)
- `rename`: Rename symbol across codebase → preview or apply edits
- `code_actions`: List available quick-fixes/refactors/import actions; apply one when `apply: true` and `query` matches title or index
- `status`: Show active language servers
- `reload`: Restart the language server
</operations>
<parameters>
- `file`: File path; for diagnostics it may be a glob pattern (e.g., `src/**/*.ts`)
- `line`: 1-indexed line number for position-based actions
- `symbol`: Substring on the target line used to resolve column automatically
- `occurrence`: 1-indexed match index when `symbol` appears multiple times on the same line
- `query`: Symbol search query, code-action kind filter (list mode), or code-action selector (apply mode)
- `new_name`: Required for rename
- `apply`: Apply edits for rename/code_actions (default true for rename, list mode for code_actions unless explicitly true)
- `timeout`: Request timeout in seconds (clamped to 5-60, default 20)
</parameters>
<caution>
- Requires running LSP server for target language
- Some operations require file to be saved to disk
- Diagnostics glob mode samples up to 20 files per request to avoid long-running stalls on broad patterns
- When `symbol` is provided for position-based actions, missing symbols or out-of-bounds `occurrence` values return an explicit error instead of silently falling back
</caution>

View file

@ -0,0 +1,179 @@
import * as os from "node:os";
import * as path from "node:path";
import { TOML } from "bun";
/**
* lspmux integration for LSP server multiplexing.
*
* When lspmux is available and running, this module wraps supported LSP server
* commands to use lspmux client mode, enabling server instance sharing across
* multiple editor windows.
*
* Integration is transparent: if lspmux is unavailable, falls back to direct spawning.
*/
// =============================================================================
// Types
// =============================================================================
interface LspmuxConfig {
instance_timeout?: number;
gc_interval?: number;
listen?: [string, number] | string;
connect?: [string, number] | string;
log_filters?: string;
pass_environment?: string[];
}
interface LspmuxState {
available: boolean;
running: boolean;
binaryPath: string | null;
config: LspmuxConfig | null;
}
// =============================================================================
// Constants
// =============================================================================
const DEFAULT_SUPPORTED_SERVERS = new Set([
"rust-analyzer",
]);
const LIVENESS_TIMEOUT_MS = 1000;
const STATE_CACHE_TTL_MS = 5 * 60 * 1000;
// =============================================================================
// Config Path
// =============================================================================
function getConfigPath(): string {
const home = os.homedir();
switch (os.platform()) {
case "win32":
return path.join(Bun.env.APPDATA ?? path.join(home, "AppData", "Roaming"), "lspmux", "config.toml");
case "darwin":
return path.join(home, "Library", "Application Support", "lspmux", "config.toml");
default:
return path.join(Bun.env.XDG_CONFIG_HOME ?? path.join(home, ".config"), "lspmux", "config.toml");
}
}
// =============================================================================
// State Management
// =============================================================================
let cachedState: LspmuxState | null = null;
let cacheTimestamp = 0;
async function parseConfig(): Promise<LspmuxConfig | null> {
try {
const file = Bun.file(getConfigPath());
if (!(await file.exists())) {
return null;
}
return TOML.parse(await file.text()) as LspmuxConfig;
} catch {
return null;
}
}
async function checkServerRunning(binaryPath: string): Promise<boolean> {
try {
const proc = Bun.spawn([binaryPath, "status"], {
stdout: "pipe",
stderr: "pipe",
windowsHide: true,
});
const exited = await Promise.race([
proc.exited,
new Promise<null>(resolve => setTimeout(() => resolve(null), LIVENESS_TIMEOUT_MS)),
]);
if (exited === null) {
proc.kill();
return false;
}
return exited === 0;
} catch {
return false;
}
}
export async function detectLspmux(): Promise<LspmuxState> {
const now = Date.now();
if (cachedState && now - cacheTimestamp < STATE_CACHE_TTL_MS) {
return cachedState;
}
if (Bun.env.PI_DISABLE_LSPMUX === "1" || Bun.env.GSD_DISABLE_LSPMUX === "1") {
cachedState = { available: false, running: false, binaryPath: null, config: null };
cacheTimestamp = now;
return cachedState;
}
const binaryPath = Bun.which("lspmux");
if (!binaryPath) {
cachedState = { available: false, running: false, binaryPath: null, config: null };
cacheTimestamp = now;
return cachedState;
}
const [config, running] = await Promise.all([parseConfig(), checkServerRunning(binaryPath)]);
cachedState = { available: true, running, binaryPath, config };
cacheTimestamp = now;
return cachedState;
}
// =============================================================================
// Command Wrapping
// =============================================================================
export function isLspmuxSupported(command: string): boolean {
const baseName = command.split("/").pop() ?? command;
return DEFAULT_SUPPORTED_SERVERS.has(baseName);
}
export interface LspmuxWrappedCommand {
command: string;
args: string[];
env?: Record<string, string>;
}
export function wrapWithLspmux(
originalCommand: string,
originalArgs: string[] | undefined,
state: LspmuxState,
): LspmuxWrappedCommand {
if (!state.available || !state.running || !state.binaryPath) {
return { command: originalCommand, args: originalArgs ?? [] };
}
if (!isLspmuxSupported(originalCommand)) {
return { command: originalCommand, args: originalArgs ?? [] };
}
const baseName = originalCommand.split("/").pop() ?? originalCommand;
const isDefaultRustAnalyzer = baseName === "rust-analyzer" && originalCommand === "rust-analyzer";
const hasArgs = originalArgs && originalArgs.length > 0;
if (isDefaultRustAnalyzer && !hasArgs) {
return { command: state.binaryPath, args: [] };
}
const args = hasArgs ? ["client", "--", ...originalArgs] : ["client"];
return {
command: state.binaryPath,
args,
env: { LSPMUX_SERVER: originalCommand },
};
}
export async function getLspmuxCommand(command: string, args?: string[]): Promise<LspmuxWrappedCommand> {
const state = await detectLspmux();
return wrapWithLspmux(command, args, state);
}

View file

@ -0,0 +1,420 @@
import { type Static, type TUnsafe, Type } from "@sinclair/typebox";
function StringEnum<T extends readonly string[]>(
values: T,
options?: { description?: string; default?: T[number] },
): TUnsafe<T[number]> {
return Type.Unsafe<T[number]>({
type: "string",
enum: values as any,
...(options?.description && { description: options.description }),
...(options?.default && { default: options.default }),
});
}
// =============================================================================
// Tool Schema
// =============================================================================
export const lspSchema = Type.Object({
action: StringEnum(
[
"diagnostics",
"definition",
"references",
"hover",
"symbols",
"rename",
"code_actions",
"type_definition",
"implementation",
"status",
"reload",
],
{ description: "LSP operation" },
),
file: Type.Optional(Type.String({ description: "File path" })),
line: Type.Optional(Type.Number({ description: "Line number (1-indexed)" })),
symbol: Type.Optional(
Type.String({ description: "Symbol/substring to locate on the line (used to compute column)" }),
),
occurrence: Type.Optional(Type.Number({ description: "Symbol occurrence on line (1-indexed, default: 1)" })),
query: Type.Optional(Type.String({ description: "Search query or SSR pattern" })),
new_name: Type.Optional(Type.String({ description: "New name for rename" })),
apply: Type.Optional(Type.Boolean({ description: "Apply edits (default: true)" })),
timeout: Type.Optional(Type.Number({ description: "Request timeout in seconds" })),
});
export type LspParams = Static<typeof lspSchema>;
export interface LspToolDetails {
serverName?: string;
action: string;
success: boolean;
request?: LspParams;
}
// =============================================================================
// Core LSP Protocol Types
// =============================================================================
export interface Position {
line: number;
character: number;
}
export interface Range {
start: Position;
end: Position;
}
export interface Location {
uri: string;
range: Range;
}
export interface LocationLink {
originSelectionRange?: Range;
targetUri: string;
targetRange: Range;
targetSelectionRange: Range;
}
// =============================================================================
// Diagnostics
// =============================================================================
export type DiagnosticSeverity = 1 | 2 | 3 | 4; // error, warning, info, hint
export interface DiagnosticRelatedInformation {
location: Location;
message: string;
}
export interface Diagnostic {
range: Range;
severity?: DiagnosticSeverity;
code?: string | number;
codeDescription?: { href: string };
source?: string;
message: string;
tags?: number[];
relatedInformation?: DiagnosticRelatedInformation[];
data?: unknown;
}
// =============================================================================
// Text Edits
// =============================================================================
export interface TextEdit {
range: Range;
newText: string;
}
export interface AnnotatedTextEdit extends TextEdit {
annotationId?: string;
}
export interface TextDocumentIdentifier {
uri: string;
}
export interface VersionedTextDocumentIdentifier extends TextDocumentIdentifier {
version: number | null;
}
export interface OptionalVersionedTextDocumentIdentifier extends TextDocumentIdentifier {
version?: number | null;
}
export interface TextDocumentEdit {
textDocument: OptionalVersionedTextDocumentIdentifier;
edits: (TextEdit | AnnotatedTextEdit)[];
}
// =============================================================================
// Resource Operations
// =============================================================================
export interface CreateFileOptions {
overwrite?: boolean;
ignoreIfExists?: boolean;
}
export interface CreateFile {
kind: "create";
uri: string;
options?: CreateFileOptions;
}
export interface RenameFileOptions {
overwrite?: boolean;
ignoreIfExists?: boolean;
}
export interface RenameFile {
kind: "rename";
oldUri: string;
newUri: string;
options?: RenameFileOptions;
}
export interface DeleteFileOptions {
recursive?: boolean;
ignoreIfNotExists?: boolean;
}
export interface DeleteFile {
kind: "delete";
uri: string;
options?: DeleteFileOptions;
}
export type DocumentChange = TextDocumentEdit | CreateFile | RenameFile | DeleteFile;
export interface WorkspaceEdit {
changes?: Record<string, TextEdit[]>;
documentChanges?: DocumentChange[];
changeAnnotations?: Record<string, { label: string; needsConfirmation?: boolean; description?: string }>;
}
// =============================================================================
// Code Actions
// =============================================================================
export type CodeActionKind =
| "quickfix"
| "refactor"
| "refactor.extract"
| "refactor.inline"
| "refactor.rewrite"
| "source"
| "source.organizeImports"
| "source.fixAll"
| string;
export interface Command {
title: string;
command: string;
arguments?: unknown[];
}
export interface CodeAction {
title: string;
kind?: CodeActionKind;
diagnostics?: Diagnostic[];
isPreferred?: boolean;
disabled?: { reason: string };
edit?: WorkspaceEdit;
command?: Command;
data?: unknown;
}
export interface CodeActionContext {
diagnostics: Diagnostic[];
only?: CodeActionKind[];
triggerKind?: 1 | 2; // Invoked = 1, Automatic = 2
}
// =============================================================================
// Symbols
// =============================================================================
export type SymbolKind =
| 1 // File
| 2 // Module
| 3 // Namespace
| 4 // Package
| 5 // Class
| 6 // Method
| 7 // Property
| 8 // Field
| 9 // Constructor
| 10 // Enum
| 11 // Interface
| 12 // Function
| 13 // Variable
| 14 // Constant
| 15 // String
| 16 // Number
| 17 // Boolean
| 18 // Array
| 19 // Object
| 20 // Key
| 21 // Null
| 22 // EnumMember
| 23 // Struct
| 24 // Event
| 25 // Operator
| 26; // TypeParameter
export const SYMBOL_KIND_NAMES: Record<SymbolKind, string> = {
1: "File",
2: "Module",
3: "Namespace",
4: "Package",
5: "Class",
6: "Method",
7: "Property",
8: "Field",
9: "Constructor",
10: "Enum",
11: "Interface",
12: "Function",
13: "Variable",
14: "Constant",
15: "String",
16: "Number",
17: "Boolean",
18: "Array",
19: "Object",
20: "Key",
21: "Null",
22: "EnumMember",
23: "Struct",
24: "Event",
25: "Operator",
26: "TypeParameter",
};
export interface DocumentSymbol {
name: string;
detail?: string;
kind: SymbolKind;
tags?: number[];
deprecated?: boolean;
range: Range;
selectionRange: Range;
children?: DocumentSymbol[];
}
export interface SymbolInformation {
name: string;
kind: SymbolKind;
tags?: number[];
deprecated?: boolean;
location: Location;
containerName?: string;
}
// =============================================================================
// Hover
// =============================================================================
export interface MarkupContent {
kind: "plaintext" | "markdown";
value: string;
}
export type MarkedString = string | { language: string; value: string };
export interface Hover {
contents: MarkupContent | MarkedString | MarkedString[];
range?: Range;
}
// =============================================================================
// Server Configuration
// =============================================================================
export interface ServerCapabilities {
flycheck?: boolean;
ssr?: boolean;
expandMacro?: boolean;
runnables?: boolean;
relatedTests?: boolean;
}
export interface ServerConfig {
command: string;
args?: string[];
fileTypes: string[];
rootMarkers: string[];
initOptions?: Record<string, unknown>;
settings?: Record<string, unknown>;
disabled?: boolean;
/** Per-server warmup timeout in milliseconds. */
warmupTimeoutMs?: number;
capabilities?: ServerCapabilities;
/** If true, this is a linter/formatter server — used only for diagnostics/actions, not type intelligence */
isLinter?: boolean;
/** Resolved absolute path to the command binary (set during config loading) */
resolvedCommand?: string;
}
// =============================================================================
// Client State
// =============================================================================
export interface OpenFile {
version: number;
languageId: string;
}
export interface PendingRequest {
resolve: (result: unknown) => void;
reject: (error: Error) => void;
method: string;
}
export interface LspServerCapabilities {
renameProvider?: boolean | { prepareProvider?: boolean };
codeActionProvider?: boolean | { resolveProvider?: boolean };
hoverProvider?: boolean;
definitionProvider?: boolean;
referencesProvider?: boolean;
documentSymbolProvider?: boolean;
documentFormattingProvider?: boolean;
workspaceSymbolProvider?: boolean;
[key: string]: unknown;
}
export interface LspClient {
name: string;
cwd: string;
config: ServerConfig;
proc: {
stdin: Bun.FileSink;
stdout: ReadableStream<Uint8Array>;
stderr: ReadableStream<Uint8Array>;
pid: number;
exitCode: number | null;
exited: Promise<number>;
kill(signal?: number): void;
};
requestId: number;
diagnostics: Map<string, Diagnostic[]>;
diagnosticsVersion: number;
openFiles: Map<string, OpenFile>;
pendingRequests: Map<number, PendingRequest>;
messageBuffer: Uint8Array;
isReading: boolean;
serverCapabilities?: LspServerCapabilities;
lastActivity: number;
stderrBuffer: string;
}
// =============================================================================
// JSON-RPC Protocol Types
// =============================================================================
export interface LspJsonRpcRequest {
jsonrpc: "2.0";
id: number;
method: string;
params: unknown;
}
export interface LspJsonRpcResponse {
jsonrpc: "2.0";
id?: number;
result?: unknown;
error?: { code: number; message: string; data?: unknown };
}
export interface LspJsonRpcNotification {
jsonrpc: "2.0";
method: string;
params?: unknown;
}

View file

@ -0,0 +1,683 @@
import path from "node:path";
import { isEnoent } from "./helpers";
import type {
CodeAction,
Command,
Diagnostic,
DiagnosticSeverity,
DocumentSymbol,
Location,
SymbolInformation,
SymbolKind,
TextEdit,
WorkspaceEdit,
} from "./types";
// =============================================================================
// Language Detection
// =============================================================================
const LANGUAGE_MAP: Record<string, string> = {
// TypeScript/JavaScript
".ts": "typescript",
".tsx": "typescriptreact",
".js": "javascript",
".jsx": "javascriptreact",
".mjs": "javascript",
".cjs": "javascript",
".mts": "typescript",
".cts": "typescript",
// Systems languages
".rs": "rust",
".go": "go",
".c": "c",
".h": "c",
".cpp": "cpp",
".cc": "cpp",
".cxx": "cpp",
".hpp": "cpp",
".hxx": "cpp",
".zig": "zig",
// Scripting languages
".py": "python",
".rb": "ruby",
".lua": "lua",
".sh": "shellscript",
".bash": "shellscript",
".zsh": "shellscript",
".fish": "fish",
".pl": "perl",
".php": "php",
// JVM languages
".java": "java",
".kt": "kotlin",
".kts": "kotlin",
".scala": "scala",
".groovy": "groovy",
".clj": "clojure",
// .NET languages
".cs": "csharp",
".fs": "fsharp",
".vb": "vb",
// Web
".html": "html",
".htm": "html",
".css": "css",
".scss": "scss",
".sass": "sass",
".less": "less",
".vue": "vue",
".svelte": "svelte",
// Data formats
".json": "json",
".jsonc": "jsonc",
".yaml": "yaml",
".yml": "yaml",
".toml": "toml",
".xml": "xml",
".ini": "ini",
// Documentation
".md": "markdown",
".markdown": "markdown",
".rst": "restructuredtext",
".adoc": "asciidoc",
".tex": "latex",
// Other
".sql": "sql",
".graphql": "graphql",
".gql": "graphql",
".proto": "protobuf",
".dockerfile": "dockerfile",
".tf": "terraform",
".hcl": "hcl",
".nix": "nix",
".ex": "elixir",
".exs": "elixir",
".erl": "erlang",
".hrl": "erlang",
".hs": "haskell",
".ml": "ocaml",
".mli": "ocaml",
".swift": "swift",
".r": "r",
".R": "r",
".jl": "julia",
".dart": "dart",
".elm": "elm",
".v": "v",
".nim": "nim",
".cr": "crystal",
".d": "d",
".pas": "pascal",
".pp": "pascal",
".lisp": "lisp",
".lsp": "lisp",
".rkt": "racket",
".scm": "scheme",
".ps1": "powershell",
".psm1": "powershell",
".bat": "bat",
".cmd": "bat",
};
/**
* Detect language ID from file path.
*/
export function detectLanguageId(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
const basename = path.basename(filePath).toLowerCase();
if (basename === "dockerfile" || basename.startsWith("dockerfile.")) {
return "dockerfile";
}
if (basename === "makefile" || basename === "gnumakefile") {
return "makefile";
}
if (basename === "cmakelists.txt" || ext === ".cmake") {
return "cmake";
}
return LANGUAGE_MAP[ext] ?? "plaintext";
}
// =============================================================================
// URI Handling (Cross-Platform)
// =============================================================================
export function fileToUri(filePath: string): string {
const resolved = path.resolve(filePath);
if (process.platform === "win32") {
return `file:///${resolved.replace(/\\/g, "/")}`;
}
return `file://${resolved}`;
}
export function uriToFile(uri: string): string {
if (!uri.startsWith("file://")) {
return uri;
}
let filePath = decodeURIComponent(uri.slice(7));
if (process.platform === "win32" && filePath.startsWith("/") && /^[A-Za-z]:/.test(filePath.slice(1))) {
filePath = filePath.slice(1);
}
return filePath;
}
// =============================================================================
// Diagnostic Formatting
// =============================================================================
const SEVERITY_NAMES: Record<DiagnosticSeverity, string> = {
1: "error",
2: "warning",
3: "info",
4: "hint",
};
export function severityToString(severity?: DiagnosticSeverity): string {
return SEVERITY_NAMES[severity ?? 1] ?? "unknown";
}
export function sortDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
return diagnostics.sort((a, b) => {
const aSeverity = a.severity ?? 1;
const bSeverity = b.severity ?? 1;
if (aSeverity !== bSeverity) return aSeverity - bSeverity;
const aLine = a.range.start.line;
const bLine = b.range.start.line;
if (aLine !== bLine) return aLine - bLine;
const aCol = a.range.start.character;
const bCol = b.range.start.character;
if (aCol !== bCol) return aCol - bCol;
return a.message.localeCompare(b.message);
});
}
export function severityToIcon(severity?: DiagnosticSeverity): string {
switch (severity ?? 1) {
case 1:
return "[E]";
case 2:
return "[W]";
case 3:
return "[I]";
case 4:
return "[H]";
default:
return "[E]";
}
}
function stripDiagnosticNoise(message: string): string {
return message
.split("\n")
.filter(line => {
const trimmed = line.trim();
if (trimmed.startsWith("for further information visit")) return false;
if (/^https?:\/\//.test(trimmed)) return false;
return true;
})
.join("\n")
.trim();
}
export function formatDiagnostic(diagnostic: Diagnostic, filePath: string): string {
const severity = severityToString(diagnostic.severity);
const line = diagnostic.range.start.line + 1;
const col = diagnostic.range.start.character + 1;
const source = diagnostic.source ? `[${diagnostic.source}] ` : "";
const code = diagnostic.code ? ` (${diagnostic.code})` : "";
const message = stripDiagnosticNoise(diagnostic.message);
return `${filePath}:${line}:${col} [${severity}] ${source}${message}${code}`;
}
const DIAG_PATH_RE = /^(.+?):(\d+:\d+\s+.*)$/;
export function formatGroupedDiagnosticMessages(messages: string[]): string {
const diagnosticsByFile = new Map<string, string[]>();
const fileOrder: string[] = [];
const ungrouped: string[] = [];
for (const msg of messages) {
const match = DIAG_PATH_RE.exec(msg);
if (!match) {
ungrouped.push(msg);
continue;
}
const [, rawFilePath, rest] = match;
const filePath = rawFilePath.replace(/\\/g, "/");
if (!diagnosticsByFile.has(filePath)) {
diagnosticsByFile.set(filePath, []);
fileOrder.push(filePath);
}
diagnosticsByFile.get(filePath)?.push(rest);
}
if (diagnosticsByFile.size === 0) {
return ungrouped.join("\n");
}
const filesByDirectory = new Map<string, string[]>();
for (const filePath of fileOrder) {
const directory = path.dirname(filePath).replace(/\\/g, "/");
if (!filesByDirectory.has(directory)) {
filesByDirectory.set(directory, []);
}
filesByDirectory.get(directory)?.push(filePath);
}
const lines: string[] = [];
for (const [directory, directoryFiles] of filesByDirectory) {
if (directory === ".") {
for (const filePath of directoryFiles) {
if (lines.length > 0) {
lines.push("");
}
lines.push(`# ${path.basename(filePath)}`);
for (const diagnostic of diagnosticsByFile.get(filePath) ?? []) {
lines.push(` ${diagnostic}`);
}
}
continue;
}
if (lines.length > 0) {
lines.push("");
}
lines.push(`# ${directory}`);
for (const filePath of directoryFiles) {
lines.push(`## └─ ${path.basename(filePath)}`);
for (const diagnostic of diagnosticsByFile.get(filePath) ?? []) {
lines.push(` ${diagnostic}`);
}
}
}
if (ungrouped.length > 0) {
lines.push("");
for (const msg of ungrouped) {
lines.push(msg);
}
}
return lines.join("\n");
}
export function formatDiagnosticsSummary(diagnostics: Diagnostic[]): string {
const counts = { error: 0, warning: 0, info: 0, hint: 0 };
for (const d of diagnostics) {
const sev = severityToString(d.severity);
if (sev in counts) {
counts[sev as keyof typeof counts]++;
}
}
const parts: string[] = [];
if (counts.error > 0) parts.push(`${counts.error} error(s)`);
if (counts.warning > 0) parts.push(`${counts.warning} warning(s)`);
if (counts.info > 0) parts.push(`${counts.info} info(s)`);
if (counts.hint > 0) parts.push(`${counts.hint} hint(s)`);
return parts.length > 0 ? parts.join(", ") : "no issues";
}
// =============================================================================
// Location Formatting
// =============================================================================
export function formatLocation(location: Location, cwd: string): string {
const file = path.relative(cwd, uriToFile(location.uri));
const line = location.range.start.line + 1;
const col = location.range.start.character + 1;
return `${file}:${line}:${col}`;
}
export function formatPosition(line: number, col: number): string {
return `${line}:${col}`;
}
// =============================================================================
// WorkspaceEdit Formatting
// =============================================================================
export function formatWorkspaceEdit(edit: WorkspaceEdit, cwd: string): string[] {
const results: string[] = [];
if (edit.changes) {
for (const [uri, textEdits] of Object.entries(edit.changes)) {
const file = path.relative(cwd, uriToFile(uri));
results.push(`${file}: ${textEdits.length} edit${textEdits.length > 1 ? "s" : ""}`);
}
}
if (edit.documentChanges) {
for (const change of edit.documentChanges) {
if ("edits" in change && change.textDocument) {
const file = path.relative(cwd, uriToFile(change.textDocument.uri));
results.push(`${file}: ${change.edits.length} edit${change.edits.length > 1 ? "s" : ""}`);
} else if ("kind" in change) {
switch (change.kind) {
case "create":
results.push(`CREATE: ${path.relative(cwd, uriToFile(change.uri))}`);
break;
case "rename":
results.push(
`RENAME: ${path.relative(cwd, uriToFile(change.oldUri))} -> ${path.relative(cwd, uriToFile(change.newUri))}`,
);
break;
case "delete":
results.push(`DELETE: ${path.relative(cwd, uriToFile(change.uri))}`);
break;
}
}
}
}
return results;
}
export function formatTextEdit(edit: TextEdit, maxLength = 50): string {
const range = `${edit.range.start.line + 1}:${edit.range.start.character + 1}`;
const preview =
edit.newText.length > maxLength
? `${edit.newText.slice(0, maxLength).replace(/\n/g, "\\n")}`
: edit.newText.replace(/\n/g, "\\n");
return `line ${range} -> "${preview}"`;
}
// =============================================================================
// Symbol Formatting
// =============================================================================
const SYMBOL_KIND_LABELS: Record<number, string> = {
1: "File",
2: "Module",
3: "Namespace",
4: "Package",
5: "Class",
6: "Method",
7: "Property",
8: "Field",
9: "Constructor",
10: "Enum",
11: "Interface",
12: "Function",
13: "Variable",
14: "Constant",
15: "String",
16: "Number",
17: "Boolean",
18: "Array",
19: "Object",
20: "Key",
21: "Null",
22: "EnumMember",
23: "Struct",
24: "Event",
25: "Operator",
26: "TypeParameter",
};
export function symbolKindToIcon(kind: SymbolKind): string {
return `[${SYMBOL_KIND_LABELS[kind] ?? "?"}]`;
}
export function symbolKindToName(kind: SymbolKind): string {
return SYMBOL_KIND_LABELS[kind] ?? "Unknown";
}
export function formatDocumentSymbol(symbol: DocumentSymbol, indent = 0): string[] {
const prefix = " ".repeat(indent);
const icon = symbolKindToIcon(symbol.kind);
const line = symbol.range.start.line + 1;
const detail = symbol.detail ? ` ${symbol.detail}` : "";
const results = [`${prefix}${icon} ${symbol.name}${detail} @ line ${line}`];
if (symbol.children) {
for (const child of symbol.children) {
results.push(...formatDocumentSymbol(child, indent + 1));
}
}
return results;
}
export function formatSymbolInformation(symbol: SymbolInformation, cwd: string): string {
const icon = symbolKindToIcon(symbol.kind);
const location = formatLocation(symbol.location, cwd);
const container = symbol.containerName ? ` (${symbol.containerName})` : "";
return `${icon} ${symbol.name}${container} @ ${location}`;
}
export function filterWorkspaceSymbols(symbols: SymbolInformation[], query: string): SymbolInformation[] {
const needle = query.trim().toLowerCase();
if (!needle) return symbols;
return symbols.filter(symbol => {
const fields = [symbol.name, symbol.containerName ?? "", uriToFile(symbol.location.uri)];
return fields.some(field => field.toLowerCase().includes(needle));
});
}
export function dedupeWorkspaceSymbols(symbols: SymbolInformation[]): SymbolInformation[] {
const seen = new Set<string>();
const unique: SymbolInformation[] = [];
for (const symbol of symbols) {
const key = [
symbol.name,
symbol.containerName ?? "",
symbol.kind,
symbol.location.uri,
symbol.location.range.start.line,
symbol.location.range.start.character,
].join(":");
if (seen.has(key)) continue;
seen.add(key);
unique.push(symbol);
}
return unique;
}
export function formatCodeAction(action: CodeAction | Command, index: number): string {
const kind = "kind" in action && action.kind ? action.kind : "action";
const preferred = "isPreferred" in action && action.isPreferred ? " (preferred)" : "";
const disabled = "disabled" in action && action.disabled ? ` (disabled: ${action.disabled.reason})` : "";
return `${index}: [${kind}] ${action.title}${preferred}${disabled}`;
}
export interface CodeActionApplyDependencies {
resolveCodeAction?: (action: CodeAction) => Promise<CodeAction>;
applyWorkspaceEdit: (edit: WorkspaceEdit) => Promise<string[]>;
executeCommand: (command: Command) => Promise<void>;
}
export interface AppliedCodeActionResult {
title: string;
edits: string[];
executedCommands: string[];
}
function isCommandItem(action: CodeAction | Command): action is Command {
return typeof action.command === "string";
}
export async function applyCodeAction(
action: CodeAction | Command,
dependencies: CodeActionApplyDependencies,
): Promise<AppliedCodeActionResult | null> {
if (isCommandItem(action)) {
await dependencies.executeCommand(action);
return { title: action.title, edits: [], executedCommands: [action.command] };
}
let resolvedAction = action;
if (!resolvedAction.edit && dependencies.resolveCodeAction) {
try {
resolvedAction = await dependencies.resolveCodeAction(resolvedAction);
} catch {
// Resolve is optional; continue with unresolved action.
}
}
const edits = resolvedAction.edit ? await dependencies.applyWorkspaceEdit(resolvedAction.edit) : [];
const executedCommands: string[] = [];
if (resolvedAction.command) {
await dependencies.executeCommand(resolvedAction.command);
executedCommands.push(resolvedAction.command.command);
}
if (edits.length === 0 && executedCommands.length === 0) {
return null;
}
return { title: resolvedAction.title, edits, executedCommands };
}
const GLOB_PATTERN_CHARS = /[*?[{]/;
export function hasGlobPattern(value: string): boolean {
return GLOB_PATTERN_CHARS.test(value);
}
export async function collectGlobMatches(
pattern: string,
cwd: string,
maxMatches: number,
): Promise<{ matches: string[]; truncated: boolean }> {
const normalizedLimit = Number.isFinite(maxMatches) ? Math.max(1, Math.trunc(maxMatches)) : 1;
const matches: string[] = [];
for await (const match of new Bun.Glob(pattern).scan({ cwd })) {
if (matches.length >= normalizedLimit) {
return { matches, truncated: true };
}
matches.push(match);
}
return { matches, truncated: false };
}
// =============================================================================
// Hover Content Extraction
// =============================================================================
export function extractHoverText(
contents: string | { kind: string; value: string } | { language: string; value: string } | unknown[],
): string {
if (typeof contents === "string") {
return contents;
}
if (Array.isArray(contents)) {
return contents.map(c => extractHoverText(c as string | { kind: string; value: string })).join("\n\n");
}
if (typeof contents === "object" && contents !== null) {
if ("value" in contents && typeof contents.value === "string") {
return contents.value;
}
}
return String(contents);
}
// =============================================================================
// General Utilities
// =============================================================================
function firstNonWhitespaceColumn(lineText: string): number {
const match = lineText.match(/\S/);
return match ? (match.index ?? 0) : 0;
}
function findSymbolMatchIndexes(lineText: string, symbol: string, caseInsensitive = false): number[] {
if (symbol.length === 0) return [];
const haystack = caseInsensitive ? lineText.toLowerCase() : lineText;
const needle = caseInsensitive ? symbol.toLowerCase() : symbol;
const indexes: number[] = [];
let fromIndex = 0;
while (fromIndex <= haystack.length - needle.length) {
const matchIndex = haystack.indexOf(needle, fromIndex);
if (matchIndex === -1) break;
indexes.push(matchIndex);
fromIndex = matchIndex + needle.length;
}
return indexes;
}
function normalizeOccurrence(occurrence?: number): number {
if (occurrence === undefined || !Number.isFinite(occurrence)) return 1;
return Math.max(1, Math.trunc(occurrence));
}
export async function resolveSymbolColumn(
filePath: string,
line: number,
symbol?: string,
occurrence?: number,
): Promise<number> {
const lineNumber = Math.max(1, line);
const matchOccurrence = normalizeOccurrence(occurrence);
try {
const fileText = await Bun.file(filePath).text();
const lines = fileText.split("\n");
const targetLine = lines[lineNumber - 1] ?? "";
if (!symbol) {
return firstNonWhitespaceColumn(targetLine);
}
const exactIndexes = findSymbolMatchIndexes(targetLine, symbol);
const fallbackIndexes = exactIndexes.length > 0 ? exactIndexes : findSymbolMatchIndexes(targetLine, symbol, true);
if (fallbackIndexes.length === 0) {
throw new Error(`Symbol "${symbol}" not found on line ${lineNumber}`);
}
if (matchOccurrence > fallbackIndexes.length) {
throw new Error(
`Symbol "${symbol}" occurrence ${matchOccurrence} is out of bounds on line ${lineNumber} (found ${fallbackIndexes.length})`,
);
}
return fallbackIndexes[matchOccurrence - 1];
} catch (error) {
if (isEnoent(error)) {
throw new Error(`File not found: ${filePath}`);
}
throw error;
}
}
export async function readLocationContext(filePath: string, line: number, contextLines = 1): Promise<string[]> {
const targetLine = Math.max(1, line);
const surrounding = Math.max(0, contextLines);
try {
const fileText = await Bun.file(filePath).text();
const lines = fileText.split("\n");
if (lines.length === 0) return [];
const startLine = Math.max(1, targetLine - surrounding);
const endLine = Math.min(lines.length, targetLine + surrounding);
const context: string[] = [];
for (let currentLine = startLine; currentLine <= endLine; currentLine++) {
const content = lines[currentLine - 1] ?? "";
context.push(`${currentLine}: ${content}`);
}
return context;
} catch (error) {
if (isEnoent(error)) {
return [];
}
throw error;
}
}

View file

@ -65,6 +65,13 @@ export {
type WriteToolOptions,
writeTool,
} from "./write.js";
export {
createLspTool,
type LspToolDetails,
lspSchema,
lspTool,
} from "../lsp/index.js";
export type { LspServerStatus } from "../lsp/client.js";
import type { AgentTool } from "@gsd/pi-agent-core";
import { type BashToolOptions, bashTool, createBashTool } from "./bash.js";
@ -74,6 +81,7 @@ import { createGrepTool, grepTool } from "./grep.js";
import { createLsTool, lsTool } from "./ls.js";
import { createReadTool, type ReadToolOptions, readTool } from "./read.js";
import { createWriteTool, writeTool } from "./write.js";
import { createLspTool, lspTool } from "../lsp/index.js";
/** Tool type (AgentTool from pi-ai) */
export type Tool = AgentTool<any>;
@ -93,6 +101,7 @@ export const allTools = {
grep: grepTool,
find: findTool,
ls: lsTool,
lsp: lspTool,
};
export type ToolName = keyof typeof allTools;
@ -135,5 +144,6 @@ export function createAllTools(cwd: string, options?: ToolsOptions): Record<Tool
grep: createGrepTool(cwd),
find: createFindTool(cwd),
ls: createLsTool(cwd),
lsp: createLspTool(cwd),
};
}