Merge pull request #214 from gsd-build/feat/lsp-tool
feat: port LSP tool from Oh My Pi
This commit is contained in:
commit
9d8eca488d
13 changed files with 5247 additions and 0 deletions
728
bun.lock
Normal file
728
bun.lock
Normal 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=="],
|
||||
}
|
||||
}
|
||||
880
packages/pi-coding-agent/src/core/lsp/client.ts
Normal file
880
packages/pi-coding-agent/src/core/lsp/client.ts
Normal file
|
|
@ -0,0 +1,880 @@
|
|||
import { spawn } from "node:child_process";
|
||||
import * as fsPromises from "node:fs/promises";
|
||||
import type { Writable } from "node:stream";
|
||||
import { killProcessTree } from "../../utils/shell.js";
|
||||
import { ToolAbortError, isEnoent, throwIfAborted, untilAborted } from "./helpers.js";
|
||||
import { applyWorkspaceEdit } from "./edits.js";
|
||||
import { getLspmuxCommand, isLspmuxSupported } from "./lspmux.js";
|
||||
import type {
|
||||
Diagnostic,
|
||||
LspClient,
|
||||
LspJsonRpcNotification,
|
||||
LspJsonRpcRequest,
|
||||
LspJsonRpcResponse,
|
||||
ServerConfig,
|
||||
WorkspaceEdit,
|
||||
} from "./types.js";
|
||||
import { detectLanguageId, fileToUri } from "./utils.js";
|
||||
|
||||
// =============================================================================
|
||||
// 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: ReturnType<typeof setInterval> | 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 | null; 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.from(buffer.subarray(messageEnd));
|
||||
|
||||
let message: LspJsonRpcResponse | LspJsonRpcNotification;
|
||||
try {
|
||||
message = JSON.parse(messageText);
|
||||
} catch {
|
||||
// Malformed JSON from LSP server — skip this message and advance past it
|
||||
return { message: null, remaining };
|
||||
}
|
||||
|
||||
return { message, 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(
|
||||
stdin: Writable | null,
|
||||
message: LspJsonRpcRequest | LspJsonRpcNotification | LspJsonRpcResponse,
|
||||
): Promise<void> {
|
||||
if (!stdin) {
|
||||
throw new Error("LSP process stdin is not available");
|
||||
}
|
||||
const content = JSON.stringify(message);
|
||||
const header = `Content-Length: ${Buffer.byteLength(content, "utf-8")}\r\n\r\n`;
|
||||
return new Promise((resolve, reject) => {
|
||||
stdin.write(header + content, (err?: Error | null) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Message Reader
|
||||
// =============================================================================
|
||||
|
||||
async function startMessageReader(client: LspClient): Promise<void> {
|
||||
if (client.isReading) return;
|
||||
client.isReading = true;
|
||||
|
||||
const stdout = client.proc.stdout;
|
||||
if (!stdout) {
|
||||
client.isReading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
stdout.on("data", async (chunk: Buffer) => {
|
||||
const currentBuffer: Buffer = Buffer.concat([client.messageBuffer, chunk]);
|
||||
client.messageBuffer = currentBuffer;
|
||||
|
||||
let workingBuffer = currentBuffer;
|
||||
let parsed = parseMessage(workingBuffer);
|
||||
while (parsed) {
|
||||
const { message, remaining } = parsed;
|
||||
workingBuffer = remaining;
|
||||
|
||||
if (!message) {
|
||||
parsed = parseMessage(workingBuffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
stdout.on("end", () => {
|
||||
client.isReading = false;
|
||||
resolve();
|
||||
});
|
||||
|
||||
stdout.on("error", () => {
|
||||
client.isReading = false;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 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: unknown) {
|
||||
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 stderr = client.proc.stderr;
|
||||
if (!stderr) return;
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
stderr.on("data", (chunk: Buffer) => {
|
||||
const text = chunk.toString("utf-8");
|
||||
client.stderrBuffer += text;
|
||||
if (client.stderrBuffer.length > 4096) {
|
||||
client.stderrBuffer = client.stderrBuffer.slice(-4096);
|
||||
}
|
||||
});
|
||||
|
||||
stderr.on("end", () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
stderr.on("error", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 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 = spawn(command, args, {
|
||||
cwd,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
env: env ? { ...process.env, ...env } : undefined,
|
||||
});
|
||||
|
||||
const exitedPromise = new Promise<number>((resolve) => {
|
||||
proc.on("exit", (code: number | null) => resolve(code ?? 1));
|
||||
});
|
||||
|
||||
const client: LspClient = {
|
||||
name: key,
|
||||
cwd,
|
||||
proc: {
|
||||
stdin: proc.stdin,
|
||||
stdout: proc.stdout,
|
||||
stderr: proc.stderr,
|
||||
pid: proc.pid ?? 0,
|
||||
exitCode: null,
|
||||
exited: exitedPromise,
|
||||
kill: (signal?: number) => proc.kill(signal),
|
||||
},
|
||||
config,
|
||||
requestId: 0,
|
||||
diagnostics: new Map(),
|
||||
diagnosticsVersion: 0,
|
||||
openFiles: new Map(),
|
||||
pendingRequests: new Map(),
|
||||
messageBuffer: Buffer.alloc(0),
|
||||
isReading: false,
|
||||
lastActivity: Date.now(),
|
||||
stderrBuffer: "",
|
||||
};
|
||||
clients.set(key, client);
|
||||
|
||||
// Register crash recovery
|
||||
exitedPromise.then((code: number) => {
|
||||
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 ?? 0);
|
||||
} 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 fsPromises.readFile(filePath, "utf-8");
|
||||
throwIfAborted(signal);
|
||||
} catch (err: unknown) {
|
||||
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 fsPromises.readFile(filePath, "utf-8");
|
||||
throwIfAborted(signal);
|
||||
} catch (err: unknown) {
|
||||
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: unknown) => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
cleanup();
|
||||
resolve(result);
|
||||
},
|
||||
reject: (err: Error) => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
cleanup();
|
||||
reject(err);
|
||||
},
|
||||
method,
|
||||
});
|
||||
|
||||
writeMessage(client.proc.stdin, request).catch((err: Error) => {
|
||||
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 = new Promise<void>(resolve => setTimeout(resolve, 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);
|
||||
});
|
||||
}
|
||||
325
packages/pi-coding-agent/src/core/lsp/config.ts
Normal file
325
packages/pi-coding-agent/src/core/lsp/config.ts
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
import * as fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import YAML from "yaml";
|
||||
import { globSync } from "glob";
|
||||
import { CONFIG_DIR_NAME } from "../../config.js";
|
||||
import { isRecord } from "./helpers.js";
|
||||
import type { ServerConfig } from "./types.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const DEFAULTS = require("./defaults.json") as Record<string, Partial<ServerConfig>>;
|
||||
|
||||
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: string) => (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 matches = globSync(marker, { cwd, nodir: false });
|
||||
if (matches.length > 0) {
|
||||
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" },
|
||||
];
|
||||
|
||||
function which(command: string): string | null {
|
||||
const result = spawnSync("which", [command], { encoding: "utf-8" });
|
||||
if (result.status !== 0) return null;
|
||||
return result.stdout.trim() || null;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
456
packages/pi-coding-agent/src/core/lsp/defaults.json
Normal file
456
packages/pi-coding-agent/src/core/lsp/defaults.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
109
packages/pi-coding-agent/src/core/lsp/edits.ts
Normal file
109
packages/pi-coding-agent/src/core/lsp/edits.ts
Normal 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.js";
|
||||
import { uriToFile } from "./utils.js";
|
||||
|
||||
// =============================================================================
|
||||
// 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 fs.readFile(filePath, "utf-8");
|
||||
const result = applyTextEditsToString(content, edits);
|
||||
await fs.writeFile(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 fs.writeFile(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;
|
||||
}
|
||||
54
packages/pi-coding-agent/src/core/lsp/helpers.ts
Normal file
54
packages/pi-coding-agent/src/core/lsp/helpers.ts
Normal 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);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
943
packages/pi-coding-agent/src/core/lsp/index.ts
Normal file
943
packages/pi-coding-agent/src/core/lsp/index.ts
Normal file
|
|
@ -0,0 +1,943 @@
|
|||
import * as fs from "node:fs";
|
||||
import * as fsSync from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@gsd/pi-agent-core";
|
||||
import {
|
||||
ensureFileOpen,
|
||||
getActiveClients,
|
||||
getOrCreateClient,
|
||||
type LspServerStatus,
|
||||
refreshFile,
|
||||
sendRequest,
|
||||
setIdleTimeout,
|
||||
WARMUP_TIMEOUT_MS,
|
||||
} from "./client.js";
|
||||
import { getServersForFile, type LspConfig, loadConfig } from "./config.js";
|
||||
import { applyWorkspaceEdit } from "./edits.js";
|
||||
import { ToolAbortError, clampTimeout, throwIfAborted } from "./helpers.js";
|
||||
import { detectLspmux } from "./lspmux.js";
|
||||
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.js";
|
||||
import {
|
||||
applyCodeAction,
|
||||
collectGlobMatches,
|
||||
dedupeWorkspaceSymbols,
|
||||
extractHoverText,
|
||||
fileToUri,
|
||||
filterWorkspaceSymbols,
|
||||
formatCodeAction,
|
||||
formatDiagnostic,
|
||||
formatDiagnosticsSummary,
|
||||
formatDocumentSymbol,
|
||||
formatGroupedDiagnosticMessages,
|
||||
formatLocation,
|
||||
formatSymbolInformation,
|
||||
formatWorkspaceEdit,
|
||||
hasGlobPattern,
|
||||
readLocationContext,
|
||||
resolveSymbolColumn,
|
||||
sortDiagnostics,
|
||||
symbolKindToIcon,
|
||||
uriToFile,
|
||||
} from "./utils.js";
|
||||
|
||||
export type { LspServerStatus } from "./client.js";
|
||||
export type { LspToolDetails } from "./types.js";
|
||||
export { lspSchema } from "./types.js";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const lspDescription = fsSync.readFileSync(path.join(__dirname, "lsp.md"), "utf-8");
|
||||
|
||||
// =============================================================================
|
||||
// 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 new Promise<void>(resolve => setTimeout(resolve, 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 [cmd, ...cmdArgs] = projectType.command;
|
||||
const proc = spawn(cmd, cmdArgs, {
|
||||
cwd,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
const abortHandler = () => {
|
||||
proc.kill();
|
||||
};
|
||||
if (signal) {
|
||||
signal.addEventListener("abort", abortHandler, { once: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const stdoutChunks: Buffer[] = [];
|
||||
const stderrChunks: Buffer[] = [];
|
||||
|
||||
proc.stdout?.on("data", (chunk: Buffer) => stdoutChunks.push(chunk));
|
||||
proc.stderr?.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
|
||||
|
||||
const exitCode = await new Promise<number>((resolve) => {
|
||||
proc.on("exit", (code: number | null) => resolve(code ?? 1));
|
||||
});
|
||||
|
||||
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
||||
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
||||
|
||||
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: unknown) {
|
||||
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: unknown) {
|
||||
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: unknown) {
|
||||
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: unknown) {
|
||||
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: CodeAction) =>
|
||||
(await sendRequest(client, "codeAction/resolve", actionItem, signal)) as CodeAction,
|
||||
applyWorkspaceEdit: async (edit: WorkspaceEdit) => applyWorkspaceEdit(edit, cwd),
|
||||
executeCommand: async (commandItem: Command) => {
|
||||
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: unknown) {
|
||||
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());
|
||||
407
packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts
Normal file
407
packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
/**
|
||||
* Integration test for the LSP tool port.
|
||||
*
|
||||
* Spins up typescript-language-server against a temp TypeScript project
|
||||
* and exercises: initialize, didOpen, hover, definition, references,
|
||||
* documentSymbol, diagnostics, and shutdown.
|
||||
*
|
||||
* Run: node --experimental-strip-types --test src/core/lsp/lsp-integration.test.ts
|
||||
* (from packages/pi-coding-agent/)
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { spawn } from "node:child_process";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers — lightweight JSON-RPC over stdio (no dependency on our LSP code)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface JsonRpcRequest {
|
||||
jsonrpc: "2.0";
|
||||
id: number;
|
||||
method: string;
|
||||
params: unknown;
|
||||
}
|
||||
|
||||
interface JsonRpcNotification {
|
||||
jsonrpc: "2.0";
|
||||
method: string;
|
||||
params?: unknown;
|
||||
}
|
||||
|
||||
interface JsonRpcResponse {
|
||||
jsonrpc: "2.0";
|
||||
id?: number;
|
||||
result?: unknown;
|
||||
error?: { code: number; message: string };
|
||||
}
|
||||
|
||||
function encodeMessage(msg: JsonRpcRequest | JsonRpcNotification | JsonRpcResponse): string {
|
||||
const body = JSON.stringify(msg);
|
||||
return `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n${body}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal LSP harness: spawns a language server, sends requests, collects responses.
|
||||
*/
|
||||
class LspHarness {
|
||||
private proc;
|
||||
private nextId = 1;
|
||||
private buffer = Buffer.alloc(0);
|
||||
private pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
|
||||
private notifications: Array<{ method: string; params: unknown }> = [];
|
||||
|
||||
constructor(command: string, args: string[], cwd: string) {
|
||||
this.proc = spawn(command, args, {
|
||||
cwd,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
this.proc.stdout!.on("data", (chunk: Buffer) => {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
this.drain();
|
||||
});
|
||||
|
||||
this.proc.stderr!.on("data", (chunk: Buffer) => {
|
||||
// Swallow stderr (server logs)
|
||||
});
|
||||
}
|
||||
|
||||
private drain(): void {
|
||||
while (true) {
|
||||
const headerEnd = this.findHeaderEnd();
|
||||
if (headerEnd === -1) return;
|
||||
|
||||
const headerText = this.buffer.subarray(0, headerEnd).toString("utf-8");
|
||||
const match = headerText.match(/Content-Length:\s*(\d+)/i);
|
||||
if (!match) return;
|
||||
|
||||
const contentLength = parseInt(match[1], 10);
|
||||
const messageStart = headerEnd + 4; // past \r\n\r\n
|
||||
const messageEnd = messageStart + contentLength;
|
||||
if (this.buffer.length < messageEnd) return;
|
||||
|
||||
const body = this.buffer.subarray(messageStart, messageEnd).toString("utf-8");
|
||||
this.buffer = Buffer.from(this.buffer.subarray(messageEnd));
|
||||
|
||||
const msg = JSON.parse(body) as JsonRpcResponse & { method?: string; params?: unknown };
|
||||
|
||||
if (msg.id !== undefined && this.pending.has(msg.id)) {
|
||||
const p = this.pending.get(msg.id)!;
|
||||
this.pending.delete(msg.id);
|
||||
if (msg.error) {
|
||||
p.reject(new Error(`LSP error ${msg.error.code}: ${msg.error.message}`));
|
||||
} else {
|
||||
p.resolve(msg.result);
|
||||
}
|
||||
} else if (msg.method) {
|
||||
// Server request or notification
|
||||
this.notifications.push({ method: msg.method, params: msg.params });
|
||||
// Auto-respond to server requests that have an id
|
||||
if (msg.id !== undefined) {
|
||||
this.respond(msg.id, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private findHeaderEnd(): number {
|
||||
for (let i = 0; i < this.buffer.length - 3; i++) {
|
||||
if (
|
||||
this.buffer[i] === 13 &&
|
||||
this.buffer[i + 1] === 10 &&
|
||||
this.buffer[i + 2] === 13 &&
|
||||
this.buffer[i + 3] === 10
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private respond(id: number, result: unknown): void {
|
||||
const msg: JsonRpcResponse = { jsonrpc: "2.0", id, result };
|
||||
this.proc.stdin!.write(encodeMessage(msg));
|
||||
}
|
||||
|
||||
async request(method: string, params: unknown, timeoutMs = 15000): Promise<unknown> {
|
||||
const id = this.nextId++;
|
||||
const msg: JsonRpcRequest = { jsonrpc: "2.0", id, method, params };
|
||||
this.proc.stdin!.write(encodeMessage(msg));
|
||||
|
||||
return new Promise<unknown>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
this.pending.delete(id);
|
||||
reject(new Error(`Request ${method} timed out after ${timeoutMs}ms`));
|
||||
}, timeoutMs);
|
||||
|
||||
this.pending.set(id, {
|
||||
resolve: (v) => {
|
||||
clearTimeout(timer);
|
||||
resolve(v);
|
||||
},
|
||||
reject: (e) => {
|
||||
clearTimeout(timer);
|
||||
reject(e);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
notify(method: string, params: unknown): void {
|
||||
const msg: JsonRpcNotification = { jsonrpc: "2.0", method, params };
|
||||
this.proc.stdin!.write(encodeMessage(msg));
|
||||
}
|
||||
|
||||
getNotifications(method?: string): Array<{ method: string; params: unknown }> {
|
||||
if (!method) return this.notifications;
|
||||
return this.notifications.filter((n) => n.method === method);
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
try {
|
||||
await this.request("shutdown", null, 5000);
|
||||
this.notify("exit", null);
|
||||
} catch {
|
||||
// Best effort
|
||||
}
|
||||
this.proc.kill();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test fixtures
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createTempProject(): { dir: string; cleanup: () => void } {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lsp-test-"));
|
||||
|
||||
// tsconfig.json
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "tsconfig.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
compilerOptions: {
|
||||
target: "ES2022",
|
||||
module: "commonjs",
|
||||
strict: true,
|
||||
outDir: "./dist",
|
||||
rootDir: "./src",
|
||||
},
|
||||
include: ["src/**/*.ts"],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
// package.json
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "package.json"),
|
||||
JSON.stringify({ name: "lsp-test-project", version: "1.0.0" }, null, 2),
|
||||
);
|
||||
|
||||
fs.mkdirSync(path.join(dir, "src"));
|
||||
|
||||
// src/math.ts — module with exported functions
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "src", "math.ts"),
|
||||
`export function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
export function subtract(a: number, b: number): number {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
export interface Calculator {
|
||||
add(a: number, b: number): number;
|
||||
subtract(a: number, b: number): number;
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
// src/main.ts — imports from math, has a type error
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "src", "main.ts"),
|
||||
`import { add, subtract, Calculator } from "./math";
|
||||
|
||||
const result: number = add(1, 2);
|
||||
const diff: number = subtract(5, 3);
|
||||
|
||||
// Intentional type error: string assigned to number
|
||||
const bad: number = "not a number";
|
||||
|
||||
export function compute(calc: Calculator): number {
|
||||
return calc.add(1, 2) + calc.subtract(5, 3);
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
return {
|
||||
dir,
|
||||
cleanup: () => fs.rmSync(dir, { recursive: true, force: true }),
|
||||
};
|
||||
}
|
||||
|
||||
function fileToUri(filePath: string): string {
|
||||
return `file://${path.resolve(filePath)}`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("LSP integration: typescript-language-server", async (t) => {
|
||||
const { dir, cleanup } = createTempProject();
|
||||
const mainPath = path.join(dir, "src", "main.ts");
|
||||
const mathPath = path.join(dir, "src", "math.ts");
|
||||
const mainUri = fileToUri(mainPath);
|
||||
const mathUri = fileToUri(mathPath);
|
||||
|
||||
const lsp = new LspHarness("typescript-language-server", ["--stdio"], dir);
|
||||
|
||||
try {
|
||||
// ---- Initialize ----
|
||||
await t.test("initialize handshake", async () => {
|
||||
const result = (await lsp.request("initialize", {
|
||||
processId: process.pid,
|
||||
rootUri: fileToUri(dir),
|
||||
rootPath: dir,
|
||||
capabilities: {
|
||||
textDocument: {
|
||||
hover: { contentFormat: ["markdown", "plaintext"] },
|
||||
definition: { linkSupport: true },
|
||||
references: {},
|
||||
documentSymbol: { hierarchicalDocumentSymbolSupport: true },
|
||||
publishDiagnostics: { relatedInformation: true },
|
||||
},
|
||||
},
|
||||
workspaceFolders: [{ uri: fileToUri(dir), name: "test" }],
|
||||
})) as { capabilities?: Record<string, unknown> };
|
||||
|
||||
assert.ok(result, "initialize should return a result");
|
||||
assert.ok(result.capabilities, "result should have capabilities");
|
||||
assert.ok(result.capabilities.hoverProvider !== undefined, "should support hover");
|
||||
assert.ok(result.capabilities.definitionProvider !== undefined, "should support definition");
|
||||
});
|
||||
|
||||
lsp.notify("initialized", {});
|
||||
|
||||
// Open both files
|
||||
const mainContent = fs.readFileSync(mainPath, "utf-8");
|
||||
const mathContent = fs.readFileSync(mathPath, "utf-8");
|
||||
|
||||
lsp.notify("textDocument/didOpen", {
|
||||
textDocument: { uri: mainUri, languageId: "typescript", version: 1, text: mainContent },
|
||||
});
|
||||
lsp.notify("textDocument/didOpen", {
|
||||
textDocument: { uri: mathUri, languageId: "typescript", version: 1, text: mathContent },
|
||||
});
|
||||
|
||||
// Give the server time to index
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
|
||||
// ---- Hover ----
|
||||
await t.test("hover on 'add' call", async () => {
|
||||
const result = (await lsp.request("textDocument/hover", {
|
||||
textDocument: { uri: mainUri },
|
||||
position: { line: 2, character: 24 }, // on 'add' in "add(1, 2)"
|
||||
})) as { contents?: unknown } | null;
|
||||
|
||||
assert.ok(result, "hover should return a result");
|
||||
assert.ok(result.contents, "hover should have contents");
|
||||
const text = JSON.stringify(result.contents);
|
||||
assert.ok(
|
||||
text.includes("add") || text.includes("number"),
|
||||
`hover text should mention 'add' or 'number', got: ${text.slice(0, 200)}`,
|
||||
);
|
||||
});
|
||||
|
||||
// ---- Go to Definition ----
|
||||
await t.test("go to definition of 'add'", async () => {
|
||||
const result = (await lsp.request("textDocument/definition", {
|
||||
textDocument: { uri: mainUri },
|
||||
position: { line: 2, character: 24 }, // on 'add'
|
||||
})) as unknown;
|
||||
|
||||
assert.ok(result, "definition should return a result");
|
||||
const locations = Array.isArray(result) ? result : [result];
|
||||
assert.ok(locations.length > 0, "should find at least one definition");
|
||||
// Response can be Location (uri) or LocationLink (targetUri)
|
||||
const loc = locations[0] as Record<string, unknown>;
|
||||
const uri = (loc.uri ?? loc.targetUri) as string;
|
||||
assert.ok(uri, `definition should have uri or targetUri, got keys: ${Object.keys(loc).join(", ")}`);
|
||||
assert.ok(
|
||||
uri.includes("math.ts"),
|
||||
`definition should point to math.ts, got: ${uri}`,
|
||||
);
|
||||
});
|
||||
|
||||
// ---- References ----
|
||||
await t.test("find references of 'add'", async () => {
|
||||
const result = (await lsp.request("textDocument/references", {
|
||||
textDocument: { uri: mathUri },
|
||||
position: { line: 0, character: 16 }, // on 'add' definition
|
||||
context: { includeDeclaration: true },
|
||||
})) as Array<{ uri: string; range: unknown }> | null;
|
||||
|
||||
assert.ok(result, "references should return a result");
|
||||
assert.ok(result.length >= 2, `should find at least 2 references (decl + usage), got ${result.length}`);
|
||||
});
|
||||
|
||||
// ---- Document Symbols ----
|
||||
await t.test("document symbols in math.ts", async () => {
|
||||
const result = (await lsp.request("textDocument/documentSymbol", {
|
||||
textDocument: { uri: mathUri },
|
||||
})) as Array<{ name: string; kind: number }> | null;
|
||||
|
||||
assert.ok(result, "documentSymbol should return a result");
|
||||
assert.ok(result.length >= 2, `should find at least 2 symbols, got ${result.length}`);
|
||||
const names = result.map((s) => s.name);
|
||||
assert.ok(names.includes("add"), `symbols should include 'add', got: ${names.join(", ")}`);
|
||||
assert.ok(names.includes("subtract"), `symbols should include 'subtract', got: ${names.join(", ")}`);
|
||||
});
|
||||
|
||||
// ---- Diagnostics (published via notification) ----
|
||||
await t.test("diagnostics for type error", async () => {
|
||||
// Wait a bit more for diagnostics to arrive
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
|
||||
const diagNotifications = lsp.getNotifications("textDocument/publishDiagnostics");
|
||||
const mainDiags = diagNotifications.filter(
|
||||
(n) => (n.params as { uri: string }).uri === mainUri,
|
||||
);
|
||||
|
||||
assert.ok(mainDiags.length > 0, "should receive diagnostics for main.ts");
|
||||
|
||||
const lastDiag = mainDiags[mainDiags.length - 1];
|
||||
const diagnostics = (lastDiag.params as { diagnostics: Array<{ message: string; range: unknown }> })
|
||||
.diagnostics;
|
||||
|
||||
// Should catch the type error: string assigned to number
|
||||
const typeError = diagnostics.find(
|
||||
(d) => d.message.includes("not assignable") || d.message.includes("Type"),
|
||||
);
|
||||
assert.ok(
|
||||
typeError,
|
||||
`should find type error diagnostic, got: ${diagnostics.map((d) => d.message).join("; ")}`,
|
||||
);
|
||||
});
|
||||
|
||||
// ---- Shutdown ----
|
||||
await t.test("clean shutdown", async () => {
|
||||
// Should not throw
|
||||
await lsp.shutdown();
|
||||
});
|
||||
} catch (err) {
|
||||
await lsp.shutdown().catch(() => {});
|
||||
cleanup();
|
||||
throw err;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
});
|
||||
33
packages/pi-coding-agent/src/core/lsp/lsp.md
Normal file
33
packages/pi-coding-agent/src/core/lsp/lsp.md
Normal 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>
|
||||
199
packages/pi-coding-agent/src/core/lsp/lspmux.ts
Normal file
199
packages/pi-coding-agent/src/core/lsp/lspmux.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import { execSync, spawn } from "node:child_process";
|
||||
import * as fsPromises from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// =============================================================================
|
||||
// Helpers
|
||||
// =============================================================================
|
||||
|
||||
function which(command: string): string | null {
|
||||
try {
|
||||
return execSync(`which ${command}`, { encoding: "utf-8" }).trim() || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Config Path
|
||||
// =============================================================================
|
||||
|
||||
function getConfigPath(): string {
|
||||
const home = os.homedir();
|
||||
switch (os.platform()) {
|
||||
case "win32":
|
||||
return path.join(process.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(process.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 configPath = getConfigPath();
|
||||
// lspmux config uses TOML, but since we're stripping TOML support,
|
||||
// attempt a simple key=value parse for the config file.
|
||||
// If the config file exists but can't be parsed, return null.
|
||||
try {
|
||||
await fsPromises.access(configPath);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
// Config exists but we can't parse TOML without a dependency.
|
||||
// Return an empty config object to indicate the file exists.
|
||||
return {} as LspmuxConfig;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkServerRunning(binaryPath: string): Promise<boolean> {
|
||||
try {
|
||||
const proc = spawn(binaryPath, ["status"], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
const exited = await Promise.race([
|
||||
new Promise<number>((resolve) => {
|
||||
proc.on("exit", (code: number | null) => resolve(code ?? 1));
|
||||
}),
|
||||
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 (process.env.PI_DISABLE_LSPMUX === "1" || process.env.GSD_DISABLE_LSPMUX === "1") {
|
||||
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
||||
cacheTimestamp = now;
|
||||
return cachedState;
|
||||
}
|
||||
|
||||
const binaryPath = 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);
|
||||
}
|
||||
421
packages/pi-coding-agent/src/core/lsp/types.ts
Normal file
421
packages/pi-coding-agent/src/core/lsp/types.ts
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
import { type Static, type TUnsafe, Type } from "@sinclair/typebox";
|
||||
import type { ChildProcess } from "node:child_process";
|
||||
|
||||
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: ChildProcess["stdin"];
|
||||
stdout: ChildProcess["stdout"];
|
||||
stderr: ChildProcess["stderr"];
|
||||
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: Buffer;
|
||||
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;
|
||||
}
|
||||
682
packages/pi-coding-agent/src/core/lsp/utils.ts
Normal file
682
packages/pi-coding-agent/src/core/lsp/utils.ts
Normal file
|
|
@ -0,0 +1,682 @@
|
|||
import * as fsPromises from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { glob } from "glob";
|
||||
import { isEnoent } from "./helpers.js";
|
||||
import type {
|
||||
CodeAction,
|
||||
Command,
|
||||
Diagnostic,
|
||||
DiagnosticSeverity,
|
||||
DocumentSymbol,
|
||||
Location,
|
||||
SymbolInformation,
|
||||
SymbolKind,
|
||||
TextEdit,
|
||||
WorkspaceEdit,
|
||||
} from "./types.js";
|
||||
|
||||
// =============================================================================
|
||||
// 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 !== undefined ? ` (${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 allMatches = await glob(pattern, { cwd });
|
||||
if (allMatches.length > normalizedLimit) {
|
||||
return { matches: allMatches.slice(0, normalizedLimit), truncated: true };
|
||||
}
|
||||
return { matches: allMatches, 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 fsPromises.readFile(filePath, "utf-8");
|
||||
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: unknown) {
|
||||
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 fsPromises.readFile(filePath, "utf-8");
|
||||
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: unknown) {
|
||||
if (isEnoent(error)) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue