diff --git a/bun.lock b/bun.lock index 1b0d1620c..573c0eaf7 100644 --- a/bun.lock +++ b/bun.lock @@ -5,18 +5,29 @@ "": { "name": "@gitlawb/openclaude", "dependencies": { + "@orama/orama": "^3.1.18", + "@orama/plugin-data-persistence": "^3.1.18", + "@vscode/ripgrep": "^1.17.1", + }, + "devDependencies": { "@alcalzone/ansi-tokenize": "0.3.0", "@anthropic-ai/bedrock-sdk": "0.29.1", "@anthropic-ai/foundry-sdk": "0.2.3", "@anthropic-ai/sandbox-runtime": "0.0.55", "@anthropic-ai/sdk": "0.94.0", + "@aws-sdk/client-bedrock": "3.1047.0", + "@aws-sdk/client-sts": "3.1047.0", + "@aws-sdk/credential-provider-node": "3.972.41", + "@azure/identity": "^4.13.1", "@commander-js/extra-typings": "12.1.0", "@grpc/grpc-js": "^1.14.3", "@grpc/proto-loader": "^0.8.0", "@modelcontextprotocol/sdk": "1.29.0", - "@orama/orama": "^3.1.18", - "@orama/plugin-data-persistence": "^3.1.18", - "@vscode/ripgrep": "^1.17.1", + "@smithy/core": "3.24.3", + "@smithy/node-http-handler": "4.7.3", + "@types/bun": "1.3.11", + "@types/node": "25.5.0", + "@types/react": "19.2.14", "ajv": "8.18.0", "auto-bind": "5.0.1", "axios": "1.16.0", @@ -44,6 +55,7 @@ "indent-string": "5.0.0", "js-tiktoken": "1.0.21", "jsonc-parser": "3.3.1", + "knip": "^6.16.1", "lodash-es": "4.18.1", "lru-cache": "11.2.7", "marked": "15.0.12", @@ -63,6 +75,7 @@ "tree-sitter-wasms": "0.1.13", "turndown": "7.2.2", "type-fest": "4.41.0", + "typescript": "5.9.3", "undici": "7.28.0", "usehooks-ts": "3.1.1", "vscode-languageserver-protocol": "3.17.5", @@ -73,13 +86,18 @@ "yaml": "2.8.3", "zod": "3.25.76", }, - "devDependencies": { - "@types/bun": "1.3.11", - "@types/node": "25.5.0", - "@types/react": "19.2.14", - "knip": "^6.16.1", - "typescript": "5.9.3", + "peerDependencies": { + "@anthropic-ai/sdk": "^0.94.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "react": "^19.0.0", + "react-reconciler": "^0.33.0", }, + "optionalPeers": [ + "@anthropic-ai/sdk", + "@modelcontextprotocol/sdk", + "react", + "react-reconciler", + ], }, }, "overrides": { @@ -109,10 +127,14 @@ "@aws-crypto/util": ["@aws-crypto/util@4.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ=="], + "@aws-sdk/client-bedrock": ["@aws-sdk/client-bedrock@3.1047.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.10", "@aws-sdk/credential-provider-node": "^3.972.41", "@aws-sdk/middleware-host-header": "^3.972.11", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.12", "@aws-sdk/middleware-user-agent": "^3.972.40", "@aws-sdk/region-config-resolver": "^3.972.14", "@aws-sdk/token-providers": "3.1047.0", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.9", "@aws-sdk/util-user-agent-browser": "^3.972.11", "@aws-sdk/util-user-agent-node": "^3.973.26", "@smithy/core": "^3.24.1", "@smithy/fetch-http-handler": "^5.4.1", "@smithy/node-http-handler": "^4.7.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-t1nSLT/FOv9KCKcrq1/KemqrPCwJUQS2gsN/6C3quZjLMAty16rqiDYLaBYfSQ43uciEi3BRC128vggXSn0YLw=="], + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1047.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.10", "@aws-sdk/credential-provider-node": "^3.972.41", "@aws-sdk/eventstream-handler-node": "^3.972.15", "@aws-sdk/middleware-eventstream": "^3.972.11", "@aws-sdk/middleware-host-header": "^3.972.11", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.12", "@aws-sdk/middleware-user-agent": "^3.972.40", "@aws-sdk/middleware-websocket": "^3.972.18", "@aws-sdk/region-config-resolver": "^3.972.14", "@aws-sdk/token-providers": "3.1047.0", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.9", "@aws-sdk/util-user-agent-browser": "^3.972.11", "@aws-sdk/util-user-agent-node": "^3.973.26", "@smithy/core": "^3.24.1", "@smithy/fetch-http-handler": "^5.4.1", "@smithy/node-http-handler": "^4.7.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-b9yUBRqYQ9ADb1ta8nLXKby9pWo0lKozsauPeAT3IemBfReoY7PG7bSqGCoXkljL/H2mjBQXAbMinPaw6xhigA=="], "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1047.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.10", "@aws-sdk/credential-provider-node": "^3.972.41", "@aws-sdk/middleware-host-header": "^3.972.11", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.12", "@aws-sdk/middleware-user-agent": "^3.972.40", "@aws-sdk/region-config-resolver": "^3.972.14", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.9", "@aws-sdk/util-user-agent-browser": "^3.972.11", "@aws-sdk/util-user-agent-node": "^3.973.26", "@smithy/core": "^3.24.1", "@smithy/fetch-http-handler": "^5.4.1", "@smithy/node-http-handler": "^4.7.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Uu8cmia5ePWyimT5vXbViF2fP8/RT8261zlfz0dzgAELjCpdyT71B6JObY4IZ5tcKa/IiuKrC5ntRVfNbJFh9w=="], + "@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.1047.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.10", "@aws-sdk/credential-provider-node": "^3.972.41", "@aws-sdk/middleware-host-header": "^3.972.11", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.12", "@aws-sdk/middleware-user-agent": "^3.972.40", "@aws-sdk/region-config-resolver": "^3.972.14", "@aws-sdk/signature-v4-multi-region": "^3.996.26", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.9", "@aws-sdk/util-user-agent-browser": "^3.972.11", "@aws-sdk/util-user-agent-node": "^3.973.26", "@smithy/core": "^3.24.1", "@smithy/fetch-http-handler": "^5.4.1", "@smithy/node-http-handler": "^4.7.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-LLeyHPEYlo16wfteaAkYKZx8BrvF+ZqSkYSWodLmtk8xxCAONLOkeHyAofOswQrSetz3BWiu+sd9WNXEvucoPg=="], + "@aws-sdk/core": ["@aws-sdk/core@3.974.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.24", "@smithy/core": "^3.24.1", "@smithy/signature-v4": "^5.4.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-ZGFFlYynBR78Y/F8b/7y4i4sgW/iGwJSjoM7AZo5Et6vyr4/L0bunN+uzKMsvecCZyqcPp4RRK7Rs17l0kMujg=="], "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.33", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.8", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-yPrIm0tgMN44utkOdKe1Bkvt/NRxcWJFdhbC98tTWxWGEBAzB3lksPhGuB8TtKC0/XyfXcGrBPzx/OKXXexa4w=="], @@ -173,6 +195,28 @@ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], + + "@azure/core-client": ["@azure/core-client@1.10.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ=="], + + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.24.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg=="], + + "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], + + "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], + + "@azure/identity": ["@azure/identity@4.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", "@azure/msal-node": "^5.1.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw=="], + + "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], + + "@azure/msal-browser": ["@azure/msal-browser@5.16.0", "", { "dependencies": { "@azure/msal-common": "16.11.0" } }, "sha512-Wc75FGnQgYpsm5jsOqn1H8AXsh8vXruA6vwip1nhjrJxwby7juxKAIVLr7csepmHiwdZGr6EwI5BlSc3PizEtQ=="], + + "@azure/msal-common": ["@azure/msal-common@16.11.0", "", {}, "sha512-UikJOtMwkFpZNzTH6Dqk8UTUPbow15zH3e0UjGYZy69lYENW/S05gMLhbxI2eonz66uALhIljvhsSMEb6+O30g=="], + + "@azure/msal-node": ["@azure/msal-node@5.3.1", "", { "dependencies": { "@azure/msal-common": "16.11.0", "jsonwebtoken": "^9.0.0" } }, "sha512-sqqv3L1UOI4KDXonNtbxPYUgbSWVXqxvmmb6BUw9n4P/UXgG+cVur3dLWQN4Cz7qQ+UJROCCxMXlksm7gIq0Sw=="], + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], "@commander-js/extra-typings": ["@commander-js/extra-typings@12.1.0", "", { "peerDependencies": { "commander": "~12.1.0" } }, "sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg=="], @@ -427,6 +471,8 @@ "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="], + "@vscode/ripgrep": ["@vscode/ripgrep@1.18.0", "", { "optionalDependencies": { "@vscode/ripgrep-darwin-arm64": "1.18.0", "@vscode/ripgrep-darwin-x64": "1.18.0", "@vscode/ripgrep-linux-arm": "1.18.0", "@vscode/ripgrep-linux-arm64": "1.18.0", "@vscode/ripgrep-linux-ia32": "1.18.0", "@vscode/ripgrep-linux-ppc64": "1.18.0", "@vscode/ripgrep-linux-riscv64": "1.18.0", "@vscode/ripgrep-linux-s390x": "1.18.0", "@vscode/ripgrep-linux-x64": "1.18.0", "@vscode/ripgrep-win32-arm64": "1.18.0", "@vscode/ripgrep-win32-ia32": "1.18.0", "@vscode/ripgrep-win32-x64": "1.18.0" } }, "sha512-ns5lWe44tSfbTMbVUsyB+I1819PVSw4AdpgK0RNkzfWfwy6+3IUNSxwSrfTno1/oWaS/hERNz+XLWVyga2aJBQ=="], "@vscode/ripgrep-darwin-arm64": ["@vscode/ripgrep-darwin-arm64@1.18.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-r3ktHSvbFycQNF6sl7sNDPocpsI7J+mEzh1IaZFkY0spm3k2Z9t8hPAeOK7+p0l6p6/swkQC14XWX01low+94Q=="], @@ -489,6 +535,8 @@ "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -537,6 +585,12 @@ "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -681,6 +735,8 @@ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + "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=="], "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], @@ -697,8 +753,12 @@ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], @@ -707,6 +767,8 @@ "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], @@ -725,6 +787,8 @@ "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], + "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=="], @@ -739,6 +803,20 @@ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], @@ -781,6 +859,8 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "oxc-parser": ["oxc-parser@0.133.0", "", { "dependencies": { "@oxc-project/types": "^0.133.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.133.0", "@oxc-parser/binding-android-arm64": "0.133.0", "@oxc-parser/binding-darwin-arm64": "0.133.0", "@oxc-parser/binding-darwin-x64": "0.133.0", "@oxc-parser/binding-freebsd-x64": "0.133.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.133.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.133.0", "@oxc-parser/binding-linux-arm64-gnu": "0.133.0", "@oxc-parser/binding-linux-arm64-musl": "0.133.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-musl": "0.133.0", "@oxc-parser/binding-linux-s390x-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-musl": "0.133.0", "@oxc-parser/binding-openharmony-arm64": "0.133.0", "@oxc-parser/binding-wasm32-wasi": "0.133.0", "@oxc-parser/binding-win32-arm64-msvc": "0.133.0", "@oxc-parser/binding-win32-ia32-msvc": "0.133.0", "@oxc-parser/binding-win32-x64-msvc": "0.133.0" } }, "sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw=="], "oxc-resolver": ["oxc-resolver@11.20.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.20.0", "@oxc-resolver/binding-android-arm64": "11.20.0", "@oxc-resolver/binding-darwin-arm64": "11.20.0", "@oxc-resolver/binding-darwin-x64": "11.20.0", "@oxc-resolver/binding-freebsd-x64": "11.20.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.20.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.20.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.20.0", "@oxc-resolver/binding-linux-arm64-musl": "11.20.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.20.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.20.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.20.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.20.0", "@oxc-resolver/binding-linux-x64-gnu": "11.20.0", "@oxc-resolver/binding-linux-x64-musl": "11.20.0", "@oxc-resolver/binding-openharmony-arm64": "11.20.0", "@oxc-resolver/binding-wasm32-wasi": "11.20.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.20.0", "@oxc-resolver/binding-win32-x64-msvc": "11.20.0" } }, "sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g=="], @@ -855,6 +935,8 @@ "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], @@ -925,7 +1007,7 @@ "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], - "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "turndown": ["turndown@7.2.2", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ=="], @@ -971,6 +1053,8 @@ "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="], @@ -993,232 +1077,118 @@ "@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], - "@aws-crypto/crc32/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-crypto/sha256-browser/@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/sha256-browser/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], "@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/sha256-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@aws-crypto/sha256-js/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - "@aws-crypto/supports-web-crypto/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@aws-crypto/util/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + + "@aws-sdk/client-bedrock/@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-sdk/client-bedrock/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], "@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], "@aws-sdk/client-bedrock-runtime/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/client-bedrock-runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/client-cognito-identity/@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-sdk/client-cognito-identity/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/client-cognito-identity/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@aws-sdk/client-sts/@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-sdk/client-sts/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], "@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], "@aws-sdk/core/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-cognito-identity/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-cognito-identity/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-env/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-env/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-http/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-http/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-ini/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-ini/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-login/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-login/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-node/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-process/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-process/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-sso/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-sso/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-provider-web-identity/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-provider-web-identity/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/credential-providers/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/credential-providers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/eventstream-handler-node/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/eventstream-handler-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/middleware-eventstream/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/middleware-eventstream/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/middleware-host-header/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/middleware-host-header/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/middleware-logger/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/middleware-logger/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/middleware-recursion-detection/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/middleware-recursion-detection/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/middleware-user-agent/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/middleware-user-agent/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/middleware-websocket/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], "@aws-sdk/middleware-websocket/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/middleware-websocket/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/nested-clients/@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-sdk/nested-clients/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/nested-clients/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/region-config-resolver/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/region-config-resolver/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], "@aws-sdk/signature-v4-multi-region/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/signature-v4-multi-region/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/token-providers/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/token-providers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/types/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/util-endpoints/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/util-endpoints/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@aws-sdk/util-locate-window/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/util-user-agent-browser/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/util-user-agent-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/util-user-agent-node/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/util-user-agent-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@aws-sdk/util-utf8-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@aws-sdk/xml-builder/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@aws-sdk/xml-builder/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@emnapi/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/abort-controller/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/core/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@smithy/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/credential-provider-imds/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@smithy/credential-provider-imds/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/eventstream-codec/@aws-crypto/crc32": ["@aws-crypto/crc32@3.0.0", "", { "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" } }, "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA=="], "@smithy/eventstream-codec/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ=="], - "@smithy/eventstream-codec/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/eventstream-serde-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/eventstream-serde-universal/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/fetch-http-handler/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@smithy/fetch-http-handler/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/is-array-buffer/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/middleware-endpoint/@smithy/util-middleware": ["@smithy/util-middleware@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw=="], - "@smithy/middleware-endpoint/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/middleware-serde/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/middleware-stack/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/node-config-provider/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/node-http-handler/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], - "@smithy/node-http-handler/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/property-provider/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/protocol-http/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA=="], - "@smithy/querystring-builder/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/querystring-parser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/shared-ini-file-loader/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/signature-v4/@smithy/types": ["@smithy/types@3.7.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg=="], - "@smithy/signature-v4/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/smithy-client/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/url-parser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/util-base64/@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=="], - "@smithy/util-base64/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@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=="], - "@smithy/util-buffer-from/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/util-hex-encoding/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/util-middleware/@smithy/types": ["@smithy/types@3.7.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg=="], - "@smithy/util-middleware/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@2.5.0", "", { "dependencies": { "@smithy/protocol-http": "^3.3.0", "@smithy/querystring-builder": "^2.2.0", "@smithy/types": "^2.12.0", "@smithy/util-base64": "^2.3.0", "tslib": "^2.6.2" } }, "sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw=="], "@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@2.5.0", "", { "dependencies": { "@smithy/abort-controller": "^2.2.0", "@smithy/protocol-http": "^3.3.0", "@smithy/querystring-builder": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA=="], @@ -1227,16 +1197,8 @@ "@smithy/util-stream/@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=="], - "@smithy/util-stream/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@smithy/util-uri-escape/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@3.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA=="], - "@smithy/util-utf8/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "cli-highlight/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=="], @@ -1275,8 +1237,12 @@ "@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + "@aws-sdk/client-bedrock/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + "@aws-sdk/client-cognito-identity/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + "@aws-sdk/client-sts/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + "@aws-sdk/nested-clients/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], "@smithy/eventstream-codec/@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@3.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w=="], @@ -1317,8 +1283,12 @@ "@aws-sdk/client-bedrock-runtime/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/client-bedrock/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/client-cognito-identity/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/client-sts/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/nested-clients/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "cli-highlight/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 1c366f190..37bbd20d4 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -98,10 +98,22 @@ third-party gateways. Authentication uses Google Application Default Credentials through `google-auth-library`. There is no `OPENAI_API_KEY`-style API key for this -route. Authenticate with either a service-account file or local ADC: +route. **For global npm installs, install the auth package on demand** (it is +not bundled by default — see [Optional provider packages](#optional-provider-packages)): ```bash +npm i -g google-auth-library +``` + +Authenticate with either local Application Default Credentials (ADC) or a +service-account key file: + +```bash +# Option 1 — local ADC (interactive, uses your own Google account): gcloud auth application-default login + +# Option 2 — service-account key file (headless / CI): +export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json ``` Minimal setup: @@ -366,6 +378,24 @@ export OPENAI_MODEL=accounts/fireworks/models/llama-v3p1-70b-instruct The **OpenClaude VS Code extension** can store the key in Secret Storage and set these variables for you when you launch from the Control Center. See `vscode-extension/openclaude-vscode/README.md`. +## Optional provider packages + +To keep the default `npm i -g @gitlawb/openclaude` install small and +warning-free, a few provider SDKs and the native image library are **not +bundled**. They are loaded on demand, and the CLI prints an `npm install ` +hint (add `-g` for the global CLI) if you enable a feature whose package is +missing. Install only what you need: + +| Feature | Trigger | Install | +| --- | --- | --- | +| AWS Bedrock | `CLAUDE_CODE_USE_BEDROCK=1` | `npm i -g @anthropic-ai/bedrock-sdk`. Profile-based auth (`~/.aws/credentials`) additionally needs `@aws-sdk/credential-providers` and `@aws-sdk/client-sts`; model listing needs `@aws-sdk/client-bedrock`. Proxy and skip-auth setups may also need `@aws-sdk/credential-provider-node`, `@smithy/node-http-handler`, or `@smithy/core`. The CLI prints the exact missing package if you hit one. | +| Azure Foundry | `CLAUDE_CODE_USE_FOUNDRY=1` | `npm i -g @anthropic-ai/foundry-sdk @azure/identity` | +| Claude on Vertex AI / Gemini ADC | `CLAUDE_CODE_USE_VERTEX=1` / Gemini ADC auth | `npm i -g google-auth-library` | +| Reading/processing images | reading an image file | `npm i -g sharp` | + +When installing OpenClaude from source (`bun install`), all of these are +already present as dev dependencies, so source/dev builds need no extra steps. + ## Environment Variables | Variable | Required | Description | diff --git a/knip.json b/knip.json index 95ca84bc4..f788b5258 100644 --- a/knip.json +++ b/knip.json @@ -32,6 +32,7 @@ ], "ignoreDependencies": [ "@types/bun", - "@anthropic-ai/foundry-sdk" + "@anthropic-ai/foundry-sdk", + "google-auth-library" ] } diff --git a/package.json b/package.json index 24daf0eb7..875f90d09 100644 --- a/package.json +++ b/package.json @@ -75,18 +75,29 @@ "prepack": "npm run build" }, "dependencies": { + "@orama/orama": "^3.1.18", + "@orama/plugin-data-persistence": "^3.1.18", + "@vscode/ripgrep": "^1.17.1" + }, + "devDependencies": { "@alcalzone/ansi-tokenize": "0.3.0", "@anthropic-ai/bedrock-sdk": "0.29.1", "@anthropic-ai/foundry-sdk": "0.2.3", "@anthropic-ai/sandbox-runtime": "0.0.55", "@anthropic-ai/sdk": "0.94.0", + "@aws-sdk/client-bedrock": "3.1047.0", + "@aws-sdk/client-sts": "3.1047.0", + "@aws-sdk/credential-provider-node": "3.972.41", + "@azure/identity": "^4.13.1", "@commander-js/extra-typings": "12.1.0", "@grpc/grpc-js": "^1.14.3", "@grpc/proto-loader": "^0.8.0", "@modelcontextprotocol/sdk": "1.29.0", - "@orama/orama": "^3.1.18", - "@orama/plugin-data-persistence": "^3.1.18", - "@vscode/ripgrep": "^1.17.1", + "@smithy/core": "3.24.3", + "@smithy/node-http-handler": "4.7.3", + "@types/bun": "1.3.11", + "@types/node": "25.5.0", + "@types/react": "19.2.14", "ajv": "8.18.0", "auto-bind": "5.0.1", "axios": "1.16.0", @@ -114,6 +125,7 @@ "indent-string": "5.0.0", "js-tiktoken": "1.0.21", "jsonc-parser": "3.3.1", + "knip": "^6.16.1", "lodash-es": "4.18.1", "lru-cache": "11.2.7", "marked": "15.0.12", @@ -133,6 +145,7 @@ "tree-sitter-wasms": "0.1.13", "turndown": "7.2.2", "type-fest": "4.41.0", + "typescript": "5.9.3", "undici": "7.28.0", "usehooks-ts": "3.1.1", "web-tree-sitter": "0.25.10", @@ -143,12 +156,17 @@ "yaml": "2.8.3", "zod": "3.25.76" }, - "devDependencies": { - "@types/bun": "1.3.11", - "@types/node": "25.5.0", - "@types/react": "19.2.14", - "knip": "^6.16.1", - "typescript": "5.9.3" + "peerDependencies": { + "@anthropic-ai/sdk": "^0.94.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "react": "^19.0.0", + "react-reconciler": "^0.33.0" + }, + "peerDependenciesMeta": { + "@anthropic-ai/sdk": { "optional": true }, + "@modelcontextprotocol/sdk": { "optional": true }, + "react": { "optional": true }, + "react-reconciler": { "optional": true } }, "engines": { "node": ">=22.0.0" diff --git a/scripts/externals.ts b/scripts/externals.ts index b2551c455..11af315fa 100644 --- a/scripts/externals.ts +++ b/scripts/externals.ts @@ -6,7 +6,12 @@ * added here (large packages, native modules, or packages with many exports). */ -// Packages that should be kept external in ALL bundles (CLI + SDK) +// Packages that should be kept external in ALL bundles (CLI + SDK). +// NOTE: some entries here are ALSO in OPTIONAL_RUNTIME_EXTERNALS below +// (sharp, google-auth-library, @aws-sdk/*, @azure/identity). That overlap is +// intentional: membership here means "never inline into the bundle", while +// membership in OPTIONAL_RUNTIME_EXTERNALS additionally means "not shipped in +// the default install — loaded on demand". A package can be both. export const COMMON_EXTERNALS: string[] = [ // Native image processing 'sharp', @@ -14,7 +19,10 @@ export const COMMON_EXTERNALS: string[] = [ '@aws-sdk/client-bedrock', '@aws-sdk/client-bedrock-runtime', '@aws-sdk/client-sts', + '@aws-sdk/credential-provider-node', '@aws-sdk/credential-providers', + '@smithy/core', + '@smithy/node-http-handler', '@azure/identity', 'google-auth-library', // @vscode/ripgrep ships a platform-specific binary alongside its @@ -42,16 +50,67 @@ export const SDK_ONLY_EXTERNALS: string[] = [ '@modelcontextprotocol/sdk', ] -// Packages kept external but NOT listed in package.json dependencies. -// These are dynamically imported at runtime — they're optional and resolved -// from transitive deps or installed by users who need that provider/protocol. +// Optional runtime packages: dynamically imported only when a provider/feature +// needs them, and NOT listed in package.json `dependencies`, so a default +// `npm install -g @gitlawb/openclaude` stays small and warning-free. +// +// Two shapes (see RUNTIME_INDIRECTION_ONLY_EXTERNALS below): +// - Most stay external in both bundles (in COMMON_EXTERNALS) so esbuild never +// inlines them — they ARE referenced where esbuild can see them. +// - The indirection-only subset (@anthropic-ai/{bedrock,foundry}-sdk) is the +// opposite: loaded purely via the runtime importer, so esbuild never sees a +// static reference and they must stay OUT of the externals lists. export const OPTIONAL_RUNTIME_EXTERNALS: string[] = [ // Cloud provider SDKs (dynamically imported per-provider) '@aws-sdk/client-bedrock', '@aws-sdk/client-bedrock-runtime', '@aws-sdk/client-sts', + '@aws-sdk/credential-provider-node', '@aws-sdk/credential-providers', + '@smithy/core', + '@smithy/node-http-handler', '@azure/identity', + // Anthropic Bedrock client — loaded via the runtime importer in + // services/api/client.ts. Not bundled (it statically imports @aws-sdk) and + // not shipped; Bedrock users install it on demand (it pulls @aws-sdk itself). + '@anthropic-ai/bedrock-sdk', + // Anthropic Foundry client — also loaded only via the runtime importer in + // services/api/client.ts (CLAUDE_CODE_USE_FOUNDRY). The Function indirection + // means esbuild never sees it, so it is not bundled; Foundry users install it + // on demand. (It is NOT in COMMON_EXTERNALS for the same reason as bedrock.) + '@anthropic-ai/foundry-sdk', + // GCP/Vertex auth — loaded via runtime import in services/api/client.ts. + // Optional: only Vertex users need it. Its transitive tree (gaxios → + // node-fetch → fetch-blob → node-domexception) is what triggered the + // deprecation warning on install, so we no longer ship it by default. + 'google-auth-library', + // Native image processing — loaded via dynamic import in the image tools. + // Optional: only image reads need it, and it carries a native install + // script. Kept opt-in so default installs run no install scripts. + 'sharp', +] + +// OPTIONAL_RUNTIME_EXTERNALS that are loaded ONLY through the runtime importer +// (the `new Function` indirection in src/utils/optionalRuntimeModule.ts), so +// esbuild never sees a static reference to them. These must NOT appear in the +// externals lists: marking @anthropic-ai/bedrock-sdk external would let esbuild +// keep (and at startup evaluate) its static `@aws-sdk/client-bedrock-runtime` +// import, which is exactly the default-install crash this design avoids. Every +// OTHER optional external IS referenced somewhere esbuild can see (e.g. sharp's +// dynamic import in imageProcessor.ts) and therefore must stay external. +export const RUNTIME_INDIRECTION_ONLY_EXTERNALS: string[] = [ + '@anthropic-ai/bedrock-sdk', + '@anthropic-ai/foundry-sdk', +] + +// OPTIONAL_RUNTIME_EXTERNALS that are NOT direct devDependencies because they +// are pulled transitively by another optional package's dependency tree, so +// source builds/tests still resolve them. Every OTHER optional external must be +// a direct devDependency (validated) so `bun install` source/dev builds keep +// working. +export const TRANSITIVE_OPTIONAL_EXTERNALS: string[] = [ + '@aws-sdk/client-bedrock-runtime', + '@aws-sdk/credential-providers', ] // Computed full lists @@ -61,10 +120,13 @@ export const SDK_EXTERNALS: string[] = [...COMMON_EXTERNALS, ...SDK_ONLY_EXTERNA // Packages intentionally bundled (not external, not flagged by validation) // These are small utilities that are fine to inline into the output bundle. export const INTENTIONALLY_BUNDLED: string[] = [ - // Test utilities (bundled, not external) - // Anthropic provider variants (bundled, not the main SDK) - '@anthropic-ai/bedrock-sdk', - '@anthropic-ai/foundry-sdk', + // Anthropic provider variants (bundled, not the main SDK). + // NOTE: @anthropic-ai/bedrock-sdk AND @anthropic-ai/foundry-sdk are + // intentionally NOT bundled — they are loaded only via the runtime importer in + // services/api/client.ts (esbuild never sees the specifier), so they live in + // OPTIONAL_RUNTIME_EXTERNALS / RUNTIME_INDIRECTION_ONLY_EXTERNALS and Bedrock / + // Foundry users install them on demand. @anthropic-ai/sandbox-runtime IS + // statically imported (utils/sandbox/sandbox-adapter.ts), so esbuild bundles it. '@anthropic-ai/sandbox-runtime', // CLI / TUI utilities '@alcalzone/ansi-tokenize', diff --git a/scripts/externalsValidation.test.ts b/scripts/externalsValidation.test.ts new file mode 100644 index 000000000..ceed97757 --- /dev/null +++ b/scripts/externalsValidation.test.ts @@ -0,0 +1,252 @@ +import { describe, expect, test } from 'bun:test' + +import { + bundledExemptionFor, + validateBundleExternals, + validateIntentionallyBundled, + validateOptionalPeers, + validateOptionalRuntimeExternals, + type PkgDeps, +} from './externalsValidation.js' + +// Mirrors the real shape: a few packages bundled in both, plus SDK-external peers. +const INTENTIONALLY_BUNDLED = ['chalk', 'zod', 'react', '@anthropic-ai/sdk'] +const SDK_ONLY_EXTERNALS = ['react', '@anthropic-ai/sdk'] +const COMMON_EXTERNALS = ['sharp', '@vscode/ripgrep'] +const SDK_EXTERNALS = [...COMMON_EXTERNALS, ...SDK_ONLY_EXTERNALS] + +describe('bundledExemptionFor', () => { + test('CLI exempts every bundled package; SDK excludes peer-provided ones', () => { + const cli = bundledExemptionFor(INTENTIONALLY_BUNDLED, new Set()) + expect(cli.has('react')).toBe(true) + + const peers = new Set(['react', '@anthropic-ai/sdk']) + const sdk = bundledExemptionFor(INTENTIONALLY_BUNDLED, peers) + expect(sdk.has('chalk')).toBe(true) // bundled in both + expect(sdk.has('react')).toBe(false) // peer => external in SDK, not exempt + expect(sdk.has('@anthropic-ai/sdk')).toBe(false) + }) +}) + +describe('validateBundleExternals', () => { + const runtimeDeps = new Set(['@vscode/ripgrep', 'react', '@anthropic-ai/sdk']) + + test('passes when every runtime dep is external or bundled-in-this-bundle', () => { + const sdkExemption = bundledExemptionFor( + INTENTIONALLY_BUNDLED, + new Set(['react', '@anthropic-ai/sdk']), + ) + const r = validateBundleExternals('SDK', runtimeDeps, SDK_EXTERNALS, sdkExemption) + expect(r.ok).toBe(true) + }) + + test('FAILS when an SDK-external peer is dropped from SDK_EXTERNALS', () => { + // The regression Jatmn flagged: drop react from externals but keep it a peer. + const brokenSdkExternals = SDK_EXTERNALS.filter(d => d !== 'react') + const sdkExemption = bundledExemptionFor( + INTENTIONALLY_BUNDLED, + new Set(['react', '@anthropic-ai/sdk']), // peers are independent of externals + ) + const r = validateBundleExternals('SDK', runtimeDeps, brokenSdkExternals, sdkExemption) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toContain('react') + }) + + test('a CLI-bundled-but-SDK-external package is still exempt in the CLI', () => { + const cliExemption = bundledExemptionFor(INTENTIONALLY_BUNDLED, new Set()) + // CLI externals do not include react (it is bundled into the CLI). + const r = validateBundleExternals('CLI', runtimeDeps, COMMON_EXTERNALS, cliExemption) + expect(r.ok).toBe(true) + }) +}) + +describe('validateIntentionallyBundled', () => { + const healthy: PkgDeps = { + dependencies: { '@vscode/ripgrep': '^1' }, + peerDependencies: { react: '*', '@anthropic-ai/sdk': '*' }, + devDependencies: { + chalk: '^5', + zod: '^3', + react: '^18', + '@anthropic-ai/sdk': '^0', + }, + } + + test('passes the real-shaped contract', () => { + const r = validateIntentionallyBundled(healthy, INTENTIONALLY_BUNDLED, SDK_ONLY_EXTERNALS) + expect(r.ok).toBe(true) + }) + + test('FAILS when a bundled package is shipped as a runtime dependency', () => { + const pkg: PkgDeps = { + ...healthy, + dependencies: { ...healthy.dependencies, chalk: '^5' }, + } + const r = validateIntentionallyBundled(pkg, INTENTIONALLY_BUNDLED, SDK_ONLY_EXTERNALS) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/must not be in dependencies.*chalk/) + }) + + test('FAILS when a bundled-only package is declared as a peerDependency', () => { + const pkg: PkgDeps = { + ...healthy, + peerDependencies: { ...healthy.peerDependencies, zod: '^3' }, // zod is not SDK-external + } + const r = validateIntentionallyBundled(pkg, INTENTIONALLY_BUNDLED, SDK_ONLY_EXTERNALS) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/peerDependencies.*zod/) + }) + + test('FAILS when a bundled package is missing from devDependencies', () => { + const pkg: PkgDeps = { + ...healthy, + devDependencies: { zod: '^3', react: '^18', '@anthropic-ai/sdk': '^0' }, // chalk missing + } + const r = validateIntentionallyBundled(pkg, INTENTIONALLY_BUNDLED, SDK_ONLY_EXTERNALS) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/devDependencies.*chalk/) + }) + + test('FAILS when an SDK external drops out of peerDependencies', () => { + const pkg: PkgDeps = { + ...healthy, + peerDependencies: { react: '*' }, // @anthropic-ai/sdk no longer a peer + } + const r = validateIntentionallyBundled(pkg, INTENTIONALLY_BUNDLED, SDK_ONLY_EXTERNALS) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/remain peerDependencies.*@anthropic-ai\/sdk/) + }) +}) + +describe('validateOptionalPeers', () => { + test('passes when every peer is marked optional', () => { + const pkg: PkgDeps = { + peerDependencies: { react: '*', '@anthropic-ai/sdk': '*' }, + peerDependenciesMeta: { + react: { optional: true }, + '@anthropic-ai/sdk': { optional: true }, + }, + } + expect(validateOptionalPeers(pkg).ok).toBe(true) + }) + + test('FAILS when a peer loses its optional flag (warning-free install regresses)', () => { + const pkg: PkgDeps = { + peerDependencies: { react: '*', '@anthropic-ai/sdk': '*' }, + peerDependenciesMeta: { react: { optional: true } }, // @anthropic-ai/sdk no longer optional + } + const r = validateOptionalPeers(pkg) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toContain('@anthropic-ai/sdk') + }) + + test('FAILS when peerDependenciesMeta is missing entirely', () => { + const pkg: PkgDeps = { peerDependencies: { react: '*' } } + expect(validateOptionalPeers(pkg).ok).toBe(false) + }) +}) + +describe('validateOptionalRuntimeExternals', () => { + const OPTIONAL = ['sharp', 'google-auth-library', '@anthropic-ai/bedrock-sdk'] + const INDIRECTION_ONLY = ['@anthropic-ai/bedrock-sdk'] + const cli = ['sharp', 'google-auth-library'] + const sdk = ['sharp', 'google-auth-library'] + // All non-transitive optionals present as devDeps, so these cases isolate the + // externals-placement behavior from the source-install (devDeps) check. + const healthyDev: PkgDeps = { + devDependencies: { + sharp: '*', + 'google-auth-library': '*', + '@anthropic-ai/bedrock-sdk': '*', + }, + } + + test('passes when esbuild-visible optionals are external and indirection-only is not', () => { + const r = validateOptionalRuntimeExternals(OPTIONAL, cli, sdk, INDIRECTION_ONLY, healthyDev) + expect(r.ok).toBe(true) + }) + + test('FAILS when an optional external is dropped from the externals lists', () => { + // The regression: sharp removed from CLI/SDK externals would get bundled. + const r = validateOptionalRuntimeExternals( + OPTIONAL, + ['google-auth-library'], // sharp dropped from CLI + sdk, + INDIRECTION_ONLY, + healthyDev, + ) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/sharp.*CLI_EXTERNALS/) + }) + + test('FAILS when the indirection-only package leaks into the externals lists', () => { + // @anthropic-ai/bedrock-sdk as external would re-expose its static @aws-sdk import. + const r = validateOptionalRuntimeExternals( + OPTIONAL, + [...cli, '@anthropic-ai/bedrock-sdk'], + sdk, + INDIRECTION_ONLY, + healthyDev, + ) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/bedrock-sdk.*must NOT/) + }) + + test('FAILS on a stray indirection-only entry not in the optional set', () => { + const r = validateOptionalRuntimeExternals(OPTIONAL, cli, sdk, ['not-optional-pkg'], healthyDev) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toContain('not-optional-pkg') + }) + + test('FAILS when an optional external is shipped in dependencies', () => { + const pkg: PkgDeps = { dependencies: { sharp: '^0.33' } } + const r = validateOptionalRuntimeExternals(OPTIONAL, cli, sdk, INDIRECTION_ONLY, pkg) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/must not be shipped.*sharp/) + }) + + test('FAILS when an optional external is shipped as a peerDependency', () => { + const pkg: PkgDeps = { peerDependencies: { 'google-auth-library': '*' } } + const r = validateOptionalRuntimeExternals(OPTIONAL, cli, sdk, INDIRECTION_ONLY, pkg) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/must not be shipped.*google-auth-library/) + }) + + test('FAILS when a non-transitive optional external drops out of devDependencies', () => { + // sharp is directly imported, so it must be a devDependency for source builds. + const pkg: PkgDeps = { + devDependencies: { 'google-auth-library': '*', '@anthropic-ai/bedrock-sdk': '*' }, // sharp missing + } + const r = validateOptionalRuntimeExternals( + OPTIONAL, + cli, + sdk, + INDIRECTION_ONLY, + pkg, + ['@example/transitive-optional'], // unrelated transitive exemption + ) + expect(r.ok).toBe(false) + expect(r.errors.join(' ')).toMatch(/missing from devDependencies.*sharp/) + }) + + test('exempts transitive optional externals from the devDependencies check', () => { + const pkg: PkgDeps = { + devDependencies: { + sharp: '*', + 'google-auth-library': '*', + '@anthropic-ai/bedrock-sdk': '*', + }, + } + // Synthetic transitive optionals are exempt when another optional package + // guarantees them in source installs. + const r = validateOptionalRuntimeExternals( + [...OPTIONAL, '@example/transitive-optional'], + [...cli, '@example/transitive-optional'], + [...sdk, '@example/transitive-optional'], + INDIRECTION_ONLY, + pkg, + ['@example/transitive-optional'], + ) + expect(r.ok).toBe(true) + }) +}) diff --git a/scripts/externalsValidation.ts b/scripts/externalsValidation.ts new file mode 100644 index 000000000..b9f966df4 --- /dev/null +++ b/scripts/externalsValidation.ts @@ -0,0 +1,221 @@ +/** + * Pure validation helpers for the externals/bundling contract, factored out of + * validate-externals.ts so the rules (bundle-specific bundled exemptions, the + * minimal-install dependency placement contract) are unit-testable with + * synthetic package.json / externals inputs. + */ + +export type ValidationResult = { ok: boolean; errors: string[] } + +export type PkgDeps = { + dependencies?: Record + peerDependencies?: Record + peerDependenciesMeta?: Record + devDependencies?: Record +} + +/** + * The set of INTENTIONALLY_BUNDLED packages that are genuinely inlined into a + * given bundle. A package declared as a peerDependency is provided by the + * consumer, so it must be EXTERNAL in the SDK bundle and is therefore NOT + * exempt there — pass `peerDepNames` (from package.json) for the SDK so the + * exemption stays independent of the externals list it is meant to guard. + */ +export function bundledExemptionFor( + intentionallyBundled: string[], + externalizedHere: ReadonlySet, +): Set { + return new Set(intentionallyBundled.filter(d => !externalizedHere.has(d))) +} + +/** + * Every runtime dependency (shipped `dependencies` + `peerDependencies`) must be + * a genuine external for a bundle, unless it is intentionally bundled INTO that + * bundle. Anything else would be missing at runtime for end users. + */ +export function validateBundleExternals( + bundleName: string, + runtimeDeps: ReadonlySet, + externals: string[], + bundledExemption: ReadonlySet, +): ValidationResult { + const externalSet = new Set(externals) + const missing = [...runtimeDeps].filter( + d => !externalSet.has(d) && !bundledExemption.has(d), + ) + if (missing.length > 0) { + return { + ok: false, + errors: [ + `${bundleName}: Dependencies missing from externals: ${missing.join(', ')}`, + ], + } + } + return { ok: true, errors: [] } +} + +/** + * The minimal-install contract for INTENTIONALLY_BUNDLED packages: + * - every entry must be a devDependency (available to build, not shipped), + * - none may be a runtime `dependency` (they are inlined; shipping them would + * install them for every user), and + * - only the SDK-externalized subset may be an (optional) peerDependency. + */ +export function validateIntentionallyBundled( + pkg: PkgDeps, + intentionallyBundled: string[], + sdkOnlyExternals: string[], +): ValidationResult { + const directDeps = pkg.dependencies ?? {} + const peerDeps = pkg.peerDependencies ?? {} + const devDeps = pkg.devDependencies ?? {} + const sdkExternalOnly = new Set(sdkOnlyExternals) + const errors: string[] = [] + + const missingFromDev = intentionallyBundled.filter(dep => !(dep in devDeps)) + if (missingFromDev.length > 0) { + errors.push( + `INTENTIONALLY_BUNDLED entries missing from devDependencies: ${missingFromDev.join(', ')}`, + ) + } + + const shippedAsRuntime = intentionallyBundled.filter(dep => dep in directDeps) + if (shippedAsRuntime.length > 0) { + errors.push( + `INTENTIONALLY_BUNDLED entries must not be in dependencies (they are inlined): ${shippedAsRuntime.join(', ')}`, + ) + } + + const unexpectedPeers = intentionallyBundled.filter( + dep => dep in peerDeps && !sdkExternalOnly.has(dep), + ) + if (unexpectedPeers.length > 0) { + errors.push( + `INTENTIONALLY_BUNDLED entries in peerDependencies that are not SDK externals: ${unexpectedPeers.join(', ')}`, + ) + } + + // Every SDK-external must STAY a peerDependency: the SDK bundle externalizes + // it, so consumers provide it. If one drops out of peerDependencies it leaves + // runtimeDeps (and other checks stop seeing it) while the SDK still expects it + // resolved at the consumer — a broken SDK publish surface. + const missingPeers = sdkOnlyExternals.filter(dep => !(dep in peerDeps)) + if (missingPeers.length > 0) { + errors.push( + `SDK externals must remain peerDependencies (the SDK bundle externalizes them): ${missingPeers.join(', ')}`, + ) + } + + return { ok: errors.length === 0, errors } +} + +/** + * The minimal-install goal depends on every peerDependency being OPTIONAL: a + * non-optional peer makes npm warn (and, on npm 7+, try to install it) for every + * end user. Assert each declared peer is marked `{ optional: true }` in + * peerDependenciesMeta so losing that flag fails the build instead of silently + * regressing the warning-free install. + */ +export function validateOptionalPeers(pkg: PkgDeps): ValidationResult { + const peers = Object.keys(pkg.peerDependencies ?? {}) + const meta = pkg.peerDependenciesMeta ?? {} + const notOptional = peers.filter(p => meta[p]?.optional !== true) + if (notOptional.length > 0) { + return { + ok: false, + errors: [ + `peerDependencies must be marked optional in peerDependenciesMeta (warning-free install): ${notOptional.join(', ')}`, + ], + } + } + return { ok: true, errors: [] } +} + +/** + * OPTIONAL_RUNTIME_EXTERNALS are never shipped and never inlined. Anything + * esbuild can see statically must therefore stay external in BOTH bundles; + * dropping one from the externals lists would let esbuild bundle it (a native + * module like sharp) or hoist its transitive imports. The indirection-only + * subset (loaded purely via the runtime importer) is the inverse: it must stay + * OUT of the externals lists, or esbuild would re-introduce its static imports. + * + * Also guards both halves of the install contract: optional packages must never + * be shipped (in dependencies/peerDependencies), and the non-transitive ones + * must be devDependencies so source/dev builds still resolve them. + */ +export function validateOptionalRuntimeExternals( + optionalRuntimeExternals: string[], + cliExternals: string[], + sdkExternals: string[], + indirectionOnly: string[], + pkg: PkgDeps = {}, + transitiveExternals: string[] = [], +): ValidationResult { + const cli = new Set(cliExternals) + const sdk = new Set(sdkExternals) + const indirection = new Set(indirectionOnly) + const transitive = new Set(transitiveExternals) + const directDeps = pkg.dependencies ?? {} + const peerDeps = pkg.peerDependencies ?? {} + const devDeps = pkg.devDependencies ?? {} + const errors: string[] = [] + + // The indirection-only set must be a subset of the optional externals (a + // stray entry would silently exempt something that is not actually optional). + const strayIndirection = indirectionOnly.filter( + p => !optionalRuntimeExternals.includes(p), + ) + if (strayIndirection.length > 0) { + errors.push( + `RUNTIME_INDIRECTION_ONLY_EXTERNALS entries not in OPTIONAL_RUNTIME_EXTERNALS: ${strayIndirection.join(', ')}`, + ) + } + + // Optional runtime externals are loaded on demand and must NEVER be shipped by + // default — listing one in dependencies or peerDependencies installs it for + // every user and breaks the minimal/warning-free install contract. + const shipped = optionalRuntimeExternals.filter( + dep => dep in directDeps || dep in peerDeps, + ) + if (shipped.length > 0) { + errors.push( + `OPTIONAL_RUNTIME_EXTERNALS must not be shipped (found in dependencies/peerDependencies): ${shipped.join(', ')}`, + ) + } + + // Source-install contract: optional packages that source code references + // directly must be devDependencies so `bun install` source/dev builds resolve + // them. The transitive set is exempt (provided by another optional package's + // dependency tree, e.g. @aws-sdk/* via @anthropic-ai/bedrock-sdk). + const missingFromDev = optionalRuntimeExternals.filter( + dep => !transitive.has(dep) && !(dep in devDeps), + ) + if (missingFromDev.length > 0) { + errors.push( + `OPTIONAL_RUNTIME_EXTERNALS missing from devDependencies (source builds need them): ${missingFromDev.join(', ')}`, + ) + } + + for (const dep of optionalRuntimeExternals) { + if (indirection.has(dep)) { + // Must NOT be external (would re-expose its static imports to esbuild). + if (cli.has(dep) || sdk.has(dep)) { + errors.push( + `${dep} is runtime-indirection-only and must NOT be in CLI/SDK externals.`, + ) + } + continue + } + // Must stay external in both bundles so it is never inlined. + const missingIn: string[] = [] + if (!cli.has(dep)) missingIn.push('CLI_EXTERNALS') + if (!sdk.has(dep)) missingIn.push('SDK_EXTERNALS') + if (missingIn.length > 0) { + errors.push( + `${dep} is an OPTIONAL_RUNTIME_EXTERNAL but missing from ${missingIn.join(' and ')} (it must never be bundled).`, + ) + } + } + + return { ok: errors.length === 0, errors } +} diff --git a/scripts/optionalRuntimeSpecifiers.test.ts b/scripts/optionalRuntimeSpecifiers.test.ts new file mode 100644 index 000000000..f0e3ad9a6 --- /dev/null +++ b/scripts/optionalRuntimeSpecifiers.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, test } from 'bun:test' +import { readdirSync, readFileSync, statSync } from 'node:fs' +import { join } from 'node:path' + +import { + OPTIONAL_RUNTIME_EXTERNALS, + INTENTIONALLY_BUNDLED, +} from './externals.js' + +// Regression coverage for the provider load paths (Bedrock/Foundry/Vertex/Azure/ +// AWS): every package routed through importOptionalRuntimeModule MUST be a +// declared optional runtime external. A static scan is the right tool here — +// exercising createClient per provider needs heavy SDK/auth mocking, while the +// thing this PR actually changed is the routing: which packages load on demand +// vs are bundled. A specifier that is NOT optional (e.g. one left in +// INTENTIONALLY_BUNDLED) means esbuild can't see it through the Function +// indirection, so it is neither bundled nor shipped and the feature would break +// for every default install with no install hint. + +const SRC = join(import.meta.dirname, '..', 'src') + +function walk(dir: string): string[] { + const out: string[] = [] + for (const entry of readdirSync(dir)) { + if (entry === 'node_modules' || entry === 'dist') continue + const full = join(dir, entry) + if (statSync(full).isDirectory()) out.push(...walk(full)) + else if (/\.tsx?$/.test(entry) && !/\.test\.tsx?$/.test(entry)) out.push(full) + } + return out +} + +// Capture the first string argument of every importOptionalRuntimeModule(...) +// call, including test-injectable wrappers. Allow an +// optional generic type argument before the parens — +// `importOptionalRuntimeModule('x', ...)` — and let it span +// lines (the in-between matchers are character classes, which match newlines). +const CALL_RE = + /(?:importOptionalRuntimeModule(?:ForClient)?|optionalRuntimeImporter)(?:<[^>]*>)?\s*\(\s*['"]([^'"]+)['"]/g + +function collectSpecifiers(): { specifier: string; file: string }[] { + const found: { specifier: string; file: string }[] = [] + for (const file of walk(SRC)) { + const text = readFileSync(file, 'utf8') + for (const m of text.matchAll(CALL_RE)) { + found.push({ specifier: m[1]!, file }) + } + } + return found +} + +// The concrete packages that must stay behind importOptionalRuntimeModule. Pin +// the exact set (not a count): a count check would still pass if a Bedrock/ +// Foundry/Vertex/Azure path regressed while some other optional import kept the +// total up. Adding/removing a provider load site is a deliberate change that +// must update this list. +const EXPECTED_SPECIFIERS = [ + '@anthropic-ai/bedrock-sdk', + '@anthropic-ai/foundry-sdk', + '@aws-sdk/client-bedrock', + '@aws-sdk/client-bedrock-runtime', + '@aws-sdk/client-sts', + '@aws-sdk/credential-provider-node', + '@aws-sdk/credential-providers', + '@smithy/core', + '@smithy/node-http-handler', + '@azure/identity', + 'google-auth-library', +].sort() + +describe('importOptionalRuntimeModule call sites', () => { + const sites = collectSpecifiers() + const optional = new Set(OPTIONAL_RUNTIME_EXTERNALS) + const bundled = new Set(INTENTIONALLY_BUNDLED) + + test('the exact set of optionally-loaded packages is the expected one', () => { + const actual = [...new Set(sites.map(s => s.specifier))].sort() + expect(actual).toEqual(EXPECTED_SPECIFIERS) + }) + + test('every optionally-loaded specifier is a declared OPTIONAL_RUNTIME_EXTERNAL', () => { + const offenders = sites.filter(s => !optional.has(s.specifier)) + expect( + offenders, + `Loaded via importOptionalRuntimeModule but not in OPTIONAL_RUNTIME_EXTERNALS ` + + `(scripts/externals.ts): ${offenders.map(o => o.specifier).join(', ')}`, + ).toEqual([]) + }) + + test('an optionally-loaded specifier is never also marked INTENTIONALLY_BUNDLED', () => { + // Mutually exclusive: a package the importer resolves from node_modules + // cannot also be inlined into the bundle. + const conflicting = sites.filter(s => bundled.has(s.specifier)) + expect( + conflicting, + `Loaded on demand AND marked bundled: ${conflicting.map(o => o.specifier).join(', ')}`, + ).toEqual([]) + }) +}) diff --git a/scripts/validate-externals.ts b/scripts/validate-externals.ts index f619accba..84208af9b 100644 --- a/scripts/validate-externals.ts +++ b/scripts/validate-externals.ts @@ -5,69 +5,79 @@ * Run as part of the build to catch missing externals early. */ import { readFileSync } from 'fs' -import { CLI_EXTERNALS, SDK_EXTERNALS, INTENTIONALLY_BUNDLED, OPTIONAL_RUNTIME_EXTERNALS } from './externals.js' +import { CLI_EXTERNALS, SDK_EXTERNALS, SDK_ONLY_EXTERNALS, INTENTIONALLY_BUNDLED, OPTIONAL_RUNTIME_EXTERNALS, RUNTIME_INDIRECTION_ONLY_EXTERNALS, TRANSITIVE_OPTIONAL_EXTERNALS } from './externals.js' +import { + bundledExemptionFor, + validateBundleExternals, + validateIntentionallyBundled, + validateOptionalPeers, + validateOptionalRuntimeExternals, +} from './externalsValidation.js' const pkg = JSON.parse(readFileSync('package.json', 'utf8')) -const allDeps = new Set([ + +// Runtime deps: shipped to users and resolved from node_modules at runtime. +// These must each be a genuine external (the bundle inlines everything else). +const runtimeDeps = new Set([ ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}), ]) +const peerDepNames = new Set(Object.keys(pkg.peerDependencies ?? {})) -function validate(bundleName: string, externals: string[]): boolean { - const externalSet = new Set(externals) - const intentionallyBundledSet = new Set(INTENTIONALLY_BUNDLED) +// The bundled allowlist is scoped PER bundle. The CLI inlines every +// INTENTIONALLY_BUNDLED package. In the SDK the optional peers (react, +// @anthropic-ai/sdk, ...) are EXTERNAL — keyed on package.json's +// peerDependencies (an independent source of truth) so dropping one of them +// from SDK_EXTERNALS fails validation instead of silently passing. +const CLI_BUNDLED_EXEMPTION = bundledExemptionFor(INTENTIONALLY_BUNDLED, new Set()) +const SDK_BUNDLED_EXEMPTION = bundledExemptionFor(INTENTIONALLY_BUNDLED, peerDepNames) - const missing = [...allDeps].filter( - d => !externalSet.has(d) && !intentionallyBundledSet.has(d), - ) +function report(result: { ok: boolean; errors: string[] }): boolean { + for (const err of result.errors) console.error(`❌ ${err}`) + return result.ok +} - if (missing.length > 0) { - console.error(`❌ ${bundleName}: Dependencies missing from externals:`) - for (const dep of missing) { - console.error(` - ${dep}`) - } - console.error( - `\n Either add them to scripts/externals.ts or to INTENTIONALLY_BUNDLED.`, - ) - return false - } +const cliOk = report( + validateBundleExternals('CLI bundle', runtimeDeps, CLI_EXTERNALS, CLI_BUNDLED_EXEMPTION), +) +const sdkOk = report( + validateBundleExternals('SDK bundle', runtimeDeps, SDK_EXTERNALS, SDK_BUNDLED_EXEMPTION), +) +const intentionallyBundledOk = report( + validateIntentionallyBundled(pkg, INTENTIONALLY_BUNDLED, SDK_ONLY_EXTERNALS), +) +const optionalPeersOk = report(validateOptionalPeers(pkg)) +const optionalExternalsOk = report( + validateOptionalRuntimeExternals( + OPTIONAL_RUNTIME_EXTERNALS, + CLI_EXTERNALS, + SDK_EXTERNALS, + RUNTIME_INDIRECTION_ONLY_EXTERNALS, + pkg, + TRANSITIVE_OPTIONAL_EXTERNALS, + ), +) +// Surface external entries not declared in package.json (informational only). +for (const [name, externals] of [ + ['CLI bundle', CLI_EXTERNALS], + ['SDK bundle', SDK_EXTERNALS], +] as const) { const optionalSet = new Set(OPTIONAL_RUNTIME_EXTERNALS) - const extra = [...externalSet].filter(d => !allDeps.has(d) && !optionalSet.has(d)) + const extra = externals.filter(d => !runtimeDeps.has(d) && !optionalSet.has(d)) if (extra.length > 0) { - console.warn(`⚠️ ${bundleName}: External entries not in package.json (may be ok):`) - for (const dep of extra) { - console.warn(` - ${dep}`) - } + console.warn(`⚠️ ${name}: External entries not in package.json (may be ok): ${extra.join(', ')}`) } - - console.log(`✓ ${bundleName}: All dependencies accounted for (${missing.length} missing, ${externalSet.size} external)`) - return true } -function validateIntentionallyBundled(): boolean { - const stale = INTENTIONALLY_BUNDLED.filter(dep => !allDeps.has(dep)) +const allOk = + cliOk && sdkOk && intentionallyBundledOk && optionalPeersOk && optionalExternalsOk - if (stale.length > 0) { - console.error(`❌ INTENTIONALLY_BUNDLED entries not in package.json:`) - for (const dep of stale) { - console.error(` - ${dep}`) - } - console.error( - `\n Remove stale entries from INTENTIONALLY_BUNDLED or add the package back to dependencies.`, - ) - return false - } - - console.log(`✓ INTENTIONALLY_BUNDLED: All entries still exist in package.json (${INTENTIONALLY_BUNDLED.length} entries)`) - return true -} - -const cliOk = validate('CLI bundle', CLI_EXTERNALS) -const sdkOk = validate('SDK bundle', SDK_EXTERNALS) -const intentionallyBundledOk = validateIntentionallyBundled() - -if (!cliOk || !sdkOk || !intentionallyBundledOk) { +if (allOk) { + console.log( + `✓ CLI/SDK externals + ${INTENTIONALLY_BUNDLED.length} bundled packages valid (devDependencies-only; SDK peers external & optional; optional externals never bundled).`, + ) +} else { console.error(`\n❌ External list validation failed. Fix scripts/externals.ts before committing.`) process.exit(1) } diff --git a/src/services/api/client.optionalRuntime.test.ts b/src/services/api/client.optionalRuntime.test.ts new file mode 100644 index 000000000..c514dc3cf --- /dev/null +++ b/src/services/api/client.optionalRuntime.test.ts @@ -0,0 +1,148 @@ +import { afterEach, beforeEach, expect, mock, test } from 'bun:test' +import { + acquireSharedMutationLock, + releaseSharedMutationLock, +} from '../../test/sharedMutationLock.js' +import * as optionalRuntimeModule from '../../utils/optionalRuntimeModule.js' + +const originalEnv = { ...process.env } + +type OptionalImport = typeof optionalRuntimeModule.importOptionalRuntimeModule + +function friendlyMissing(specifier: string, feature: string): Error { + return new Error( + `${feature} requires the "${specifier}" package, which is not installed. ` + + `Install it with \`npm install ${specifier}\` (add \`-g\` if you installed the CLI globally) to enable it.`, + ) +} + +async function importFreshClient(importOptionalRuntimeModule: OptionalImport) { + const client = await import( + `./client.js?optional-runtime=${Date.now()}-${Math.random()}` + ) + client._setOptionalRuntimeModuleImporterForTesting(importOptionalRuntimeModule) + return client +} + +beforeEach(async () => { + await acquireSharedMutationLock('services/api/client.optionalRuntime.test.ts') + process.env = { ...originalEnv } + ;(globalThis as Record).MACRO = { VERSION: 'test-version' } + delete process.env.CLAUDE_CODE_USE_OPENAI + delete process.env.CLAUDE_CODE_USE_GEMINI + delete process.env.CLAUDE_CODE_USE_GITHUB + delete process.env.CLAUDE_CODE_USE_MISTRAL + delete process.env.CLAUDE_CODE_USE_BEDROCK + delete process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH + delete process.env.CLAUDE_CODE_USE_FOUNDRY + delete process.env.CLAUDE_CODE_SKIP_FOUNDRY_AUTH + delete process.env.CLAUDE_CODE_USE_VERTEX + delete process.env.CLAUDE_CODE_SKIP_VERTEX_AUTH + delete process.env.ANTHROPIC_API_KEY + delete process.env.ANTHROPIC_AUTH_TOKEN + delete process.env.ANTHROPIC_FOUNDRY_API_KEY +}) + +afterEach(() => { + try { + process.env = { ...originalEnv } + delete (globalThis as Record).MACRO + mock.restore() + } finally { + releaseSharedMutationLock() + } +}) + +test('Bedrock reports the missing provider SDK through the optional runtime helper', async () => { + process.env.CLAUDE_CODE_USE_BEDROCK = '1' + process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH = '1' + + const importOptionalRuntimeModule = mock(async (specifier: string, feature: string) => { + throw friendlyMissing(specifier, feature) + }) as unknown as OptionalImport + + const { getAnthropicClient } = await importFreshClient(importOptionalRuntimeModule) + + await expect( + getAnthropicClient({ maxRetries: 0, model: 'claude-sonnet-4-6' }), + ).rejects.toThrow(/AWS Bedrock requires the "@anthropic-ai\/bedrock-sdk" package/) + expect(importOptionalRuntimeModule).toHaveBeenCalledWith( + '@anthropic-ai/bedrock-sdk', + 'AWS Bedrock', + ) +}) + +test('Foundry skip-auth does not load Azure identity', async () => { + process.env.CLAUDE_CODE_USE_FOUNDRY = '1' + process.env.CLAUDE_CODE_SKIP_FOUNDRY_AUTH = '1' + + const importOptionalRuntimeModule = mock(async (specifier: string, feature: string) => { + if (specifier === '@azure/identity') { + throw friendlyMissing(specifier, feature) + } + if (specifier === '@anthropic-ai/foundry-sdk') { + return { + AnthropicFoundry: class AnthropicFoundry { + constructor(readonly args: unknown) {} + }, + } + } + throw new Error(`unexpected optional import: ${specifier}`) + }) as unknown as OptionalImport + + const { getAnthropicClient } = await importFreshClient(importOptionalRuntimeModule) + + await getAnthropicClient({ maxRetries: 0, model: 'claude-sonnet-4-6' }) + + expect(importOptionalRuntimeModule).toHaveBeenCalledWith( + '@anthropic-ai/foundry-sdk', + 'Azure Foundry', + ) + expect(importOptionalRuntimeModule).not.toHaveBeenCalledWith( + '@azure/identity', + 'Azure Foundry authentication', + ) +}) + +test('Foundry real-auth branch reports missing Azure identity through the optional runtime helper', async () => { + process.env.CLAUDE_CODE_USE_FOUNDRY = '1' + + const importOptionalRuntimeModule = mock(async (specifier: string, feature: string) => { + if (specifier === '@anthropic-ai/foundry-sdk') { + return { + AnthropicFoundry: class AnthropicFoundry { + constructor(readonly args: unknown) {} + }, + } + } + throw friendlyMissing(specifier, feature) + }) as unknown as OptionalImport + + const { getAnthropicClient } = await importFreshClient(importOptionalRuntimeModule) + + await expect( + getAnthropicClient({ maxRetries: 0, model: 'claude-sonnet-4-6' }), + ).rejects.toThrow(/Azure Foundry authentication requires the "@azure\/identity" package/) + expect(importOptionalRuntimeModule).toHaveBeenCalledWith( + '@azure/identity', + 'Azure Foundry authentication', + ) +}) + +test('Vertex skip-auth branch does not load google-auth-library', async () => { + process.env.CLAUDE_CODE_USE_VERTEX = '1' + process.env.CLAUDE_CODE_SKIP_VERTEX_AUTH = '1' + + const importOptionalRuntimeModule = mock(async (specifier: string, feature: string) => { + throw friendlyMissing(specifier, feature) + }) as unknown as OptionalImport + + const { getAnthropicClient } = await importFreshClient(importOptionalRuntimeModule) + + await getAnthropicClient({ maxRetries: 0, model: 'claude-sonnet-4-6' }) + + expect(importOptionalRuntimeModule).not.toHaveBeenCalledWith( + 'google-auth-library', + 'Vertex AI (GCP) authentication', + ) +}) diff --git a/src/services/api/client.ts b/src/services/api/client.ts index 9b8734b81..5bd6b55f6 100644 --- a/src/services/api/client.ts +++ b/src/services/api/client.ts @@ -53,11 +53,18 @@ import { type ProviderOverride, } from './authRouting.js' import { AnthropicVertex } from './vertexClient.js' +import { importOptionalRuntimeModule } from '../../utils/optionalRuntimeModule.js' -const importRuntimeModule = new Function( - 'specifier', - 'return import(specifier)', -) as (specifier: string) => Promise +type OptionalRuntimeImporter = typeof importOptionalRuntimeModule + +let importOptionalRuntimeModuleForClient: OptionalRuntimeImporter = + importOptionalRuntimeModule + +export function _setOptionalRuntimeModuleImporterForTesting( + importer?: OptionalRuntimeImporter, +): void { + importOptionalRuntimeModuleForClient = importer ?? importOptionalRuntimeModule +} /** * Environment variables for different client types: @@ -550,7 +557,15 @@ export async function getAnthropicClient({ }) as unknown as Anthropic } if (isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK)) { - const { AnthropicBedrock } = await import('@anthropic-ai/bedrock-sdk') + // Loaded via the runtime importer (not a static `import()`), so esbuild + // stays blind to it and does NOT inline @anthropic-ai/bedrock-sdk — which + // statically imports @aws-sdk/client-bedrock-runtime. Inlining would hoist + // that AWS import into the CLI bundle and require it at startup for every + // user. Keeping it lazy means only Bedrock users install the SDK (which + // pulls @aws-sdk transitively). + const { AnthropicBedrock } = await importOptionalRuntimeModuleForClient< + typeof import('@anthropic-ai/bedrock-sdk') + >('@anthropic-ai/bedrock-sdk', 'AWS Bedrock') // Use region override for small fast model if specified const awsRegion = model === getSmallFastModel() && @@ -594,9 +609,9 @@ export async function getAnthropicClient({ ) as unknown as Anthropic } if (isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY)) { - const { AnthropicFoundry } = await importRuntimeModule( - '@anthropic-ai/foundry-sdk', - ) + const { AnthropicFoundry } = await importOptionalRuntimeModuleForClient< + typeof import('@anthropic-ai/foundry-sdk') + >('@anthropic-ai/foundry-sdk', 'Azure Foundry') // Determine Azure AD token provider based on configuration // SDK reads ANTHROPIC_FOUNDRY_API_KEY by default let azureADTokenProvider: (() => Promise) | undefined @@ -609,7 +624,10 @@ export async function getAnthropicClient({ const { DefaultAzureCredential: AzureCredential, getBearerTokenProvider, - } = await importRuntimeModule('@azure/identity') + } = + await importOptionalRuntimeModuleForClient< + typeof import('@azure/identity') + >('@azure/identity', 'Azure Foundry authentication') azureADTokenProvider = getBearerTokenProvider( new AzureCredential(), 'https://cognitiveservices.azure.com/.default', @@ -632,7 +650,6 @@ export async function getAnthropicClient({ await refreshGcpCredentialsIfNeeded() } - const { GoogleAuth } = await importRuntimeModule('google-auth-library') // TODO: Cache either GoogleAuth instance or AuthClient to improve performance // Currently we create a new GoogleAuth instance for every getAnthropicClient() call // This could cause repeated authentication flows and metadata server checks @@ -667,33 +684,42 @@ export async function getAnthropicClient({ process.env['GOOGLE_APPLICATION_CREDENTIALS'] || process.env['google_application_credentials'] - const googleAuth = isEnvTruthy(process.env.CLAUDE_CODE_SKIP_VERTEX_AUTH) - ? ({ - // Mock GoogleAuth for testing/proxy scenarios - getClient: () => ({ - getRequestHeaders: () => ({}), - }), - } as { - getClient: () => { - getRequestHeaders: () => Record - } - }) - : new GoogleAuth({ - scopes: ['https://www.googleapis.com/auth/cloud-platform'], - // Only use ANTHROPIC_VERTEX_PROJECT_ID as last resort fallback - // This prevents the 12-second metadata server timeout when: - // - No project env vars are set AND - // - No credential keyfile is specified AND - // - ADC file exists but lacks project_id field - // - // Risk: If auth project != API target project, this could cause billing/audit issues - // Mitigation: Users can set GOOGLE_CLOUD_PROJECT to override - ...(hasProjectEnvVar || hasKeyFile - ? {} - : { - projectId: process.env.ANTHROPIC_VERTEX_PROJECT_ID, - }), - }) + let googleAuth: { + getClient: () => { getRequestHeaders: () => Record } + } + if (isEnvTruthy(process.env.CLAUDE_CODE_SKIP_VERTEX_AUTH)) { + // Mock GoogleAuth for testing/proxy scenarios. This path intentionally + // does NOT load google-auth-library — proxy/test runs must work even when + // the optional package is absent (it is only needed for real auth below). + googleAuth = { + getClient: () => ({ + getRequestHeaders: () => ({}), + }), + } + } else { + const { GoogleAuth } = await importOptionalRuntimeModuleForClient< + typeof import('google-auth-library') + >('google-auth-library', 'Vertex AI (GCP) authentication') + // The real GoogleAuth (async getClient) is wider than the minimal shape + // declared above and shared with the mock; AnthropicVertex accepts it at + // runtime, so narrow it back to the shared shape here. + googleAuth = new GoogleAuth({ + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + // Only use ANTHROPIC_VERTEX_PROJECT_ID as last resort fallback + // This prevents the 12-second metadata server timeout when: + // - No project env vars are set AND + // - No credential keyfile is specified AND + // - ADC file exists but lacks project_id field + // + // Risk: If auth project != API target project, this could cause billing/audit issues + // Mitigation: Users can set GOOGLE_CLOUD_PROJECT to override + ...(hasProjectEnvVar || hasKeyFile + ? {} + : { + projectId: process.env.ANTHROPIC_VERTEX_PROJECT_ID, + }), + }) as unknown as typeof googleAuth + } const vertexArgs = { ...ARGS, diff --git a/src/services/tokenEstimation.ts b/src/services/tokenEstimation.ts index e5da94640..2aeac095d 100644 --- a/src/services/tokenEstimation.ts +++ b/src/services/tokenEstimation.ts @@ -1,4 +1,5 @@ import type { Anthropic } from '@anthropic-ai/sdk' +import { importOptionalRuntimeModule } from '../utils/optionalRuntimeModule.js' import type { BetaMessageParam as MessageParam } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' // @aws-sdk/client-bedrock-runtime is imported dynamically in countTokensWithBedrock() // to defer ~279KB of AWS SDK code until a Bedrock call is actually made @@ -694,9 +695,9 @@ async function countTokensWithBedrock({ }), } - const { CountTokensCommand } = await import( - '@aws-sdk/client-bedrock-runtime' - ) + const { CountTokensCommand } = await importOptionalRuntimeModule< + typeof import('@aws-sdk/client-bedrock-runtime') + >('@aws-sdk/client-bedrock-runtime', 'AWS Bedrock') const input: CountTokensCommandInput = { modelId, input: { diff --git a/src/tools/FileReadTool/FileReadTool.ts b/src/tools/FileReadTool/FileReadTool.ts index 2c199eaf8..d365bbb36 100644 --- a/src/tools/FileReadTool/FileReadTool.ts +++ b/src/tools/FileReadTool/FileReadTool.ts @@ -48,6 +48,10 @@ import { ImageResizeError, maybeResizeAndDownsampleImageBuffer, } from '../../utils/imageResizer.js' +import { + getImageProcessor, + ImageProcessorUnavailableError, +} from './imageProcessor.js' import { lazySchema } from '../../utils/lazySchema.js' import { logError } from '../../utils/log.js' import { isAutoMemFile } from '../../utils/memoryFileDetection.js' @@ -1261,15 +1265,13 @@ export async function readImageWithTokenBudget( } } catch (e) { logError(e) - // Fallback: heavily compressed version from the SAME buffer + // Fallback: heavily compressed version from the SAME buffer, loaded via + // the shared optional image processor (NOT a raw import('sharp')). This + // keeps the missing-image-processor contract: when no processor is + // installed, surface the actionable install hint instead of swallowing it + // and returning an image that already exceeded the token budget. try { - const sharpModule = await import('sharp') - // CJS/ESM interop: prefer .default; some loaders expose the callable - // as the module itself, hence the cast on the fallback. - const sharp = - sharpModule.default || - (sharpModule as unknown as typeof sharpModule.default) - + const sharp = await getImageProcessor() const fallbackBuffer = await sharp(imageBuffer) .resize(400, 400, { fit: 'inside', @@ -1280,6 +1282,10 @@ export async function readImageWithTokenBudget( return createImageResponse(fallbackBuffer, 'jpeg', originalSize) } catch (error) { + // No image processor available → surface the install hint rather than + // returning an over-budget image. Other failures (e.g. a corrupt + // buffer) still degrade gracefully to the original. + if (error instanceof ImageProcessorUnavailableError) throw error logError(error) return createImageResponse(imageBuffer, detectedFormat, originalSize) } diff --git a/src/tools/FileReadTool/imageProcessor.ts b/src/tools/FileReadTool/imageProcessor.ts index e2f622555..497839133 100644 --- a/src/tools/FileReadTool/imageProcessor.ts +++ b/src/tools/FileReadTool/imageProcessor.ts @@ -40,7 +40,9 @@ let imageCreatorModule: { default: SharpCreator } | null = null */ export class ImageProcessorUnavailableError extends Error { constructor() { - super('No image processor available (sharp is not installed)') + super( + 'Image support is not installed. Install `sharp` (add `-g` if you installed the CLI globally) to enable reading and processing images.', + ) this.name = 'ImageProcessorUnavailableError' } } diff --git a/src/utils/attachments.editedImage.test.ts b/src/utils/attachments.editedImage.test.ts new file mode 100644 index 000000000..c4db60978 --- /dev/null +++ b/src/utils/attachments.editedImage.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, test } from 'bun:test' + +import { tryReadEditedImageAttachment } from './attachments.js' +import { ImageProcessorUnavailableError } from '../tools/FileReadTool/imageProcessor.js' +import type { readImageWithTokenBudget } from '../tools/FileReadTool/FileReadTool.js' + +// Pins the chosen contract for background edited-image attachments: they DEGRADE +// (return null) on any read/compress failure instead of throwing, so a missing +// optional image processor — or any other error — never aborts the turn. The +// explicit FileReadTool path is the opposite (it surfaces ImageProcessorUnavailableError +// so the user sees the install hint); see FileReadTool.readImageWithTokenBudget. +const SECRET_PATH = '/Users/jane.doe/secret-project/edited-image.png' + +describe('tryReadEditedImageAttachment', () => { + test('degrades to null when the image processor is unavailable', async () => { + const result = await tryReadEditedImageAttachment(SECRET_PATH, { + read: async () => { + throw new ImageProcessorUnavailableError() + }, + }) + expect(result).toBeNull() + }) + + test('degrades to null on a path-bearing read error (without rethrowing)', async () => { + const result = await tryReadEditedImageAttachment(SECRET_PATH, { + read: async () => { + throw new Error(`Image file is empty: ${SECRET_PATH}`) + }, + }) + expect(result).toBeNull() + }) + + test('degrades to null when the file cannot be read (real ENOENT)', async () => { + const result = await tryReadEditedImageAttachment( + '/nonexistent/path/definitely-not-a-real-image-xyz.png', + ) + expect(result).toBeNull() + }) + + test('telemetry is sanitized: no file path reaches logError or analytics', async () => { + const logged: Error[] = [] + const tracked: Array<[string, Record | undefined]> = [] + await tryReadEditedImageAttachment(SECRET_PATH, { + // Error message intentionally carries the path — it must NOT be forwarded. + read: async () => { + throw new Error(`Image file is empty: ${SECRET_PATH}`) + }, + log: err => { + logged.push(err as Error) + }, + track: (name, meta) => { + tracked.push([name, meta as Record | undefined]) + }, + }) + + // logError received a generic, path-free error (only the error TYPE name). + expect(logged).toHaveLength(1) + expect(logged[0]!.message).not.toContain(SECRET_PATH) + expect(logged[0]!.message).not.toContain('jane.doe') + + // Analytics payload carries only `ext`, never the path or the raw error. + expect(tracked).toHaveLength(1) + const [eventName, meta] = tracked[0]! + expect(eventName).toBe('tengu_watched_file_compression_failed') + expect(meta).toEqual({ ext: 'png' }) + expect(JSON.stringify(meta)).not.toContain('jane.doe') + }) + + test('returns the attachment when the read succeeds', async () => { + const fake = { + file: { base64: 'AAAA', type: 'image/png' }, + } as unknown as Awaited> + const result = await tryReadEditedImageAttachment(SECRET_PATH, { + read: async () => fake, + }) + expect(result).toEqual({ + type: 'edited_image_file', + filename: SECRET_PATH, + content: fake, + }) + }) +}) diff --git a/src/utils/attachments.ts b/src/utils/attachments.ts index 72fbca6a9..103e06a7a 100644 --- a/src/utils/attachments.ts +++ b/src/utils/attachments.ts @@ -3,6 +3,7 @@ import { logEvent, type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, } from 'src/services/analytics/index.js' +import { getFileExtensionForAnalytics } from 'src/services/analytics/metadata.js' import { toolMatchesName, type Tools, @@ -2089,6 +2090,54 @@ async function processMcpResourceAttachments( ) as Attachment[] } +// Read a changed/watched image file for a background diff attachment. +// +// Contract: this path DEGRADES on any failure — returning null skips the +// attachment rather than interrupting the turn. That deliberately includes +// ImageProcessorUnavailableError (no image processor installed): a background +// attachment must never abort the conversation over a missing optional package. +// The explicit FileReadTool path is the opposite — it lets that error surface so +// the user sees the install hint when they directly read an image. +export async function tryReadEditedImageAttachment( + normalizedPath: string, + // Injectable for tests so the degrade path (and its sanitized telemetry) can + // be exercised for a specific error type without a real file. + deps: { + read?: typeof readImageWithTokenBudget + log?: typeof logError + track?: typeof logEvent + } = {}, +): Promise<{ + type: 'edited_image_file' + filename: string + content: Awaited> +} | null> { + const read = deps.read ?? readImageWithTokenBudget + const log = deps.log ?? logError + const track = deps.track ?? logEvent + try { + const data = await read(normalizedPath) + return { + type: 'edited_image_file' as const, + filename: normalizedPath, + content: data, + } + } catch (compressionError) { + // Log only the error TYPE, never the raw error: readImageWithTokenBudget can + // throw path-bearing messages/stacks (e.g. "Image file is empty: "), + // and logError persists message/stack, which would leak local file paths. + const errorName = + compressionError instanceof Error ? compressionError.name : 'UnknownError' + log(new Error(`watched-file image attachment skipped (${errorName})`)) + // Likewise only the file extension goes to analytics — never the path. + const analyticsExt = getFileExtensionForAnalytics(normalizedPath) + track('tengu_watched_file_compression_failed', { + ...(analyticsExt !== undefined && { ext: analyticsExt }), + }) + return null + } +} + export async function getChangedFiles( toolUseContext: ToolUseContext, ): Promise { @@ -2150,22 +2199,10 @@ export async function getChangedFiles( } } - // For non-text files (images), apply the same token limit logic as FileReadTool + // For non-text files (images), apply the same token limit logic as + // FileReadTool. Degrades to null on failure (see the helper's contract). if (result.data.type === 'image') { - try { - const data = await readImageWithTokenBudget(normalizedPath) - return { - type: 'edited_image_file' as const, - filename: normalizedPath, - content: data, - } - } catch (compressionError) { - logError(compressionError) - logEvent('tengu_watched_file_compression_failed', { - file: normalizedPath, - } as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS) - return null - } + return tryReadEditedImageAttachment(normalizedPath) } // notebook / pdf / parts — no diff representation; explicitly diff --git a/src/utils/auth.ts b/src/utils/auth.ts index eb5b17fb1..a9dec1470 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -40,6 +40,7 @@ import { isValidAwsStsOutput, } from './aws.js' import { AwsAuthStatusManager } from './awsAuthStatusManager.js' +import { importOptionalRuntimeModule } from './optionalRuntimeModule.js' import { clearBetasCaches } from './betas.js' import { type AccountInfo, @@ -867,8 +868,11 @@ const GCP_CREDENTIALS_CHECK_TIMEOUT_MS = 5_000 */ export async function checkGcpCredentialsValid(): Promise { try { - // Dynamically import to avoid loading google-auth-library unnecessarily - const { GoogleAuth } = await import('google-auth-library') + // Dynamically import to avoid loading google-auth-library unnecessarily. + // It is an optional, on-demand dependency (not shipped by default). + const { GoogleAuth } = await importOptionalRuntimeModule< + typeof import('google-auth-library') + >('google-auth-library', 'Vertex AI (GCP) authentication') const auth = new GoogleAuth({ scopes: ['https://www.googleapis.com/auth/cloud-platform'], }) diff --git a/src/utils/aws.ts b/src/utils/aws.ts index 611d34cbd..fe91ba651 100644 --- a/src/utils/aws.ts +++ b/src/utils/aws.ts @@ -1,4 +1,5 @@ import { logForDebugging } from './debug.js' +import { importOptionalRuntimeModule } from './optionalRuntimeModule.js' /** AWS short-term credentials format. */ export type AwsCredentials = { @@ -48,9 +49,11 @@ export function isValidAwsStsOutput(obj: unknown): obj is AwsStsOutput { /** Throws if STS caller identity cannot be retrieved. */ export async function checkStsCallerIdentity(): Promise { - const { STSClient, GetCallerIdentityCommand } = await import( - '@aws-sdk/client-sts' - ) + const { STSClient, GetCallerIdentityCommand } = + await importOptionalRuntimeModule( + '@aws-sdk/client-sts', + 'AWS credentials', + ) await new STSClient().send(new GetCallerIdentityCommand({})) } @@ -61,7 +64,9 @@ export async function checkStsCallerIdentity(): Promise { export async function clearAwsIniCache(): Promise { try { logForDebugging('Clearing AWS credential provider cache') - const { fromIni } = await import('@aws-sdk/credential-providers') + const { fromIni } = await importOptionalRuntimeModule< + typeof import('@aws-sdk/credential-providers') + >('@aws-sdk/credential-providers', 'AWS credentials') const iniProvider = fromIni({ ignoreCache: true }) await iniProvider() // This updates the global file cache logForDebugging('AWS credential provider cache refreshed') diff --git a/src/utils/geminiAuth.optionalRuntime.test.ts b/src/utils/geminiAuth.optionalRuntime.test.ts new file mode 100644 index 000000000..a2dcc272c --- /dev/null +++ b/src/utils/geminiAuth.optionalRuntime.test.ts @@ -0,0 +1,61 @@ +import { afterEach, beforeEach, expect, mock, test } from 'bun:test' +import { + acquireSharedMutationLock, + releaseSharedMutationLock, +} from '../test/sharedMutationLock.js' +import { OptionalRuntimeModuleUnavailableError } from './optionalRuntimeModule.js' + +const originalEnv = { ...process.env } + +beforeEach(async () => { + await acquireSharedMutationLock('utils/geminiAuth.optionalRuntime.test.ts') + process.env = { ...originalEnv } + delete process.env.GEMINI_API_KEY + delete process.env.GOOGLE_API_KEY + delete process.env.GEMINI_ACCESS_TOKEN + process.env.GEMINI_AUTH_MODE = 'adc' + process.env.GOOGLE_APPLICATION_CREDENTIALS = import.meta.path +}) + +afterEach(() => { + try { + process.env = { ...originalEnv } + mock.restore() + } finally { + releaseSharedMutationLock() + } +}) + +test('Gemini ADC reports missing google-auth-library through the optional runtime helper', async () => { + const importOptionalRuntimeModule = mock(async (specifier: string, feature: string) => { + throw new OptionalRuntimeModuleUnavailableError(feature, specifier) + }) + + const { resolveGeminiCredential } = await import( + `./geminiAuth.ts?optional-runtime=${Date.now()}-${Math.random()}` + ) + + await expect( + resolveGeminiCredential(process.env, { importOptionalRuntimeModule }), + ).rejects.toThrow(/Gemini Application Default Credentials requires the "google-auth-library" package/) + expect(importOptionalRuntimeModule).toHaveBeenCalledWith( + 'google-auth-library', + 'Gemini Application Default Credentials', + ) +}) + +test('Gemini ADC still degrades to none for credential lookup failures', async () => { + const { resolveGeminiCredential } = await import( + `./geminiAuth.ts?credential-failure=${Date.now()}-${Math.random()}` + ) + + await expect( + resolveGeminiCredential(process.env, { + createGoogleAuth: async () => ({ + getClient: async () => { + throw new Error('ADC token unavailable') + }, + }), + }), + ).resolves.toEqual({ kind: 'none' }) +}) diff --git a/src/utils/geminiAuth.ts b/src/utils/geminiAuth.ts index 46af97e1b..d0886aebe 100644 --- a/src/utils/geminiAuth.ts +++ b/src/utils/geminiAuth.ts @@ -3,6 +3,10 @@ import { homedir } from 'node:os' import { join } from 'node:path' import { memoizeWithTTLAsync } from './memoize.js' +import { + importOptionalRuntimeModule, + isOptionalRuntimeModuleUnavailableError, +} from './optionalRuntimeModule.js' const GEMINI_ADC_SCOPE = 'https://www.googleapis.com/auth/cloud-platform' const GEMINI_ADC_CACHE_TTL_MS = 5 * 60 * 1000 @@ -42,6 +46,7 @@ export type GeminiResolvedCredential = type ResolveGeminiCredentialDeps = { createGoogleAuth?: () => Promise + importOptionalRuntimeModule?: typeof importOptionalRuntimeModule } function sanitizeCredential(value: string | undefined | null): string | undefined { @@ -132,8 +137,13 @@ function normalizeAccessToken( return sanitizeCredential(value?.token) } -async function createDefaultGoogleAuth(): Promise { - const { GoogleAuth } = await import('google-auth-library') +async function createDefaultGoogleAuth( + optionalRuntimeImporter: typeof importOptionalRuntimeModule = + importOptionalRuntimeModule, +): Promise { + const { GoogleAuth } = await optionalRuntimeImporter< + typeof import('google-auth-library') + >('google-auth-library', 'Gemini Application Default Credentials') return new GoogleAuth({ scopes: [GEMINI_ADC_SCOPE], }) as GoogleAuthLike @@ -148,7 +158,10 @@ async function resolveGeminiAdcCredentialUncached( } try { - const auth = await (deps.createGoogleAuth ?? createDefaultGoogleAuth)() + const createGoogleAuth = + deps.createGoogleAuth ?? + (() => createDefaultGoogleAuth(deps.importOptionalRuntimeModule)) + const auth = await createGoogleAuth() const client = await auth.getClient() const accessToken = normalizeAccessToken(await client.getAccessToken()) if (!accessToken) { @@ -167,7 +180,10 @@ async function resolveGeminiAdcCredentialUncached( credential: accessToken, ...(resolvedProjectId ? { projectId: resolvedProjectId } : {}), } - } catch { + } catch (error) { + if (isOptionalRuntimeModuleUnavailableError(error)) { + throw error + } return { kind: 'none' } } } @@ -227,7 +243,7 @@ export async function resolveGeminiCredential( return { kind: 'none' } } - if (deps.createGoogleAuth) { + if (deps.createGoogleAuth || deps.importOptionalRuntimeModule) { return resolveGeminiAdcCredentialUncached(env, deps) } diff --git a/src/utils/model/bedrock.ts b/src/utils/model/bedrock.ts index cf6a1e003..defecff80 100644 --- a/src/utils/model/bedrock.ts +++ b/src/utils/model/bedrock.ts @@ -1,4 +1,5 @@ import memoize from 'lodash-es/memoize.js' +import { importOptionalRuntimeModule } from '../optionalRuntimeModule.js' import { refreshAndGetAwsCredentials } from '../auth.js' import { getAWSRegion, isEnvTruthy } from '../envUtils.js' import { logError } from '../log.js' @@ -9,7 +10,10 @@ export const getBedrockInferenceProfiles = memoize(async function (): Promise< > { const [client, { ListInferenceProfilesCommand }] = await Promise.all([ createBedrockClient(), - import('@aws-sdk/client-bedrock'), + importOptionalRuntimeModule( + '@aws-sdk/client-bedrock', + 'AWS Bedrock', + ), ]) const allProfiles: Array<{ inferenceProfileId?: string }> = [] let nextToken: string | undefined @@ -47,8 +51,35 @@ export function findFirstMatch( return profiles.find(p => p.includes(substring)) ?? null } +async function getBedrockNoAuthConfig() { + const [{ NodeHttpHandler }, { NoAuthSigner }] = await Promise.all([ + importOptionalRuntimeModule( + '@smithy/node-http-handler', + 'AWS Bedrock no-auth support', + ), + importOptionalRuntimeModule( + '@smithy/core', + 'AWS Bedrock no-auth support', + ), + ]) + + return { + requestHandler: new NodeHttpHandler(), + httpAuthSchemes: [ + { + schemeId: 'smithy.api#noAuth', + identityProvider: () => async () => ({}), + signer: new NoAuthSigner(), + }, + ], + httpAuthSchemeProvider: () => [{ schemeId: 'smithy.api#noAuth' }], + } +} + async function createBedrockClient() { - const { BedrockClient } = await import('@aws-sdk/client-bedrock') + const { BedrockClient } = await importOptionalRuntimeModule< + typeof import('@aws-sdk/client-bedrock') + >('@aws-sdk/client-bedrock', 'AWS Bedrock') // Match the Anthropic Bedrock SDK's region behavior exactly: // - Reads AWS_REGION or AWS_DEFAULT_REGION env vars (not AWS config files) // - Falls back to 'us-east-1' if neither is set @@ -63,19 +94,7 @@ async function createBedrockClient() { endpoint: process.env.ANTHROPIC_BEDROCK_BASE_URL, }), ...(await getAWSClientProxyConfig()), - ...(skipAuth && { - requestHandler: new ( - await import('@smithy/node-http-handler') - ).NodeHttpHandler(), - httpAuthSchemes: [ - { - schemeId: 'smithy.api#noAuth', - identityProvider: () => async () => ({}), - signer: new (await import('@smithy/core')).NoAuthSigner(), - }, - ], - httpAuthSchemeProvider: () => [{ schemeId: 'smithy.api#noAuth' }], - }), + ...(skipAuth ? await getBedrockNoAuthConfig() : {}), } if (!skipAuth && !process.env.AWS_BEARER_TOKEN_BEDROCK) { @@ -94,9 +113,9 @@ async function createBedrockClient() { } export async function createBedrockRuntimeClient() { - const { BedrockRuntimeClient } = await import( - '@aws-sdk/client-bedrock-runtime' - ) + const { BedrockRuntimeClient } = await importOptionalRuntimeModule< + typeof import('@aws-sdk/client-bedrock-runtime') + >('@aws-sdk/client-bedrock-runtime', 'AWS Bedrock') const region = getAWSRegion() const skipAuth = isEnvTruthy(process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH) @@ -106,21 +125,7 @@ export async function createBedrockRuntimeClient() { endpoint: process.env.ANTHROPIC_BEDROCK_BASE_URL, }), ...(await getAWSClientProxyConfig()), - ...(skipAuth && { - // BedrockRuntimeClient defaults to HTTP/2 without fallback - // proxy servers may not support this, so we explicitly force HTTP/1.1 - requestHandler: new ( - await import('@smithy/node-http-handler') - ).NodeHttpHandler(), - httpAuthSchemes: [ - { - schemeId: 'smithy.api#noAuth', - identityProvider: () => async () => ({}), - signer: new (await import('@smithy/core')).NoAuthSigner(), - }, - ], - httpAuthSchemeProvider: () => [{ schemeId: 'smithy.api#noAuth' }], - }), + ...(skipAuth ? await getBedrockNoAuthConfig() : {}), } if (!skipAuth && !process.env.AWS_BEARER_TOKEN_BEDROCK) { @@ -144,7 +149,10 @@ export const getInferenceProfileBackingModel = memoize(async function ( try { const [client, { GetInferenceProfileCommand }] = await Promise.all([ createBedrockClient(), - import('@aws-sdk/client-bedrock'), + importOptionalRuntimeModule( + '@aws-sdk/client-bedrock', + 'AWS Bedrock', + ), ]) const command = new GetInferenceProfileCommand({ inferenceProfileIdentifier: profileId, diff --git a/src/utils/optionalRuntimeModule.test.ts b/src/utils/optionalRuntimeModule.test.ts new file mode 100644 index 000000000..3b1799119 --- /dev/null +++ b/src/utils/optionalRuntimeModule.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, test } from 'bun:test' +import { + importOptionalRuntimeModule, + importRuntimeModule, + isMissingSpecifierError, +} from './optionalRuntimeModule.js' + +function moduleNotFound(message: string): Error { + const e = new Error(message) + ;(e as { code?: string }).code = 'ERR_MODULE_NOT_FOUND' + return e +} + +describe('importOptionalRuntimeModule', () => { + test('throws an actionable error when the optional package is missing', async () => { + const promise = importOptionalRuntimeModule( + '@openclaude/does-not-exist-xyz', + 'Test Feature', + ) + await expect(promise).rejects.toThrow( + /Test Feature requires the "@openclaude\/does-not-exist-xyz" package, which is not installed\. Install it with `npm install @openclaude\/does-not-exist-xyz`/, + ) + }) + + test('resolves the module when it is present', async () => { + // node: builtins are always resolvable — exercises the success path. The + // type arg mirrors how production call sites declare the module contract. + const mod = await importOptionalRuntimeModule( + 'node:path', + 'Test Feature', + ) + const join = mod.join ?? (mod as { default?: typeof import('node:path') }).default?.join + expect(typeof join).toBe('function') + }) + + test('does not mask a missing package as the wrong feature specifier', async () => { + // The friendly error must name the specifier we asked for. + try { + await importOptionalRuntimeModule('totally-absent-pkg-123', 'Vertex AI') + throw new Error('expected rejection') + } catch (e) { + expect((e as Error).message).toContain('totally-absent-pkg-123') + expect((e as Error).message).toContain('Vertex AI') + } + }) +}) + +describe('isMissingSpecifierError', () => { + test('matches the exact missing package, quoted', () => { + expect( + isMissingSpecifierError( + moduleNotFound("Cannot find package 'sharp' imported from /x/y.js"), + 'sharp', + ), + ).toBe(true) + expect( + isMissingSpecifierError( + moduleNotFound('Cannot find module "sharp" imported from /x/y.js'), + 'sharp', + ), + ).toBe(true) + }) + + test('does not misattribute a lookalike transitive package', () => { + // A missing transitive dep whose name CONTAINS the specifier must not be + // reported as the specifier itself. + expect( + isMissingSpecifierError( + moduleNotFound("Cannot find package 'sharp-libvips-dev' imported from /x"), + 'sharp', + ), + ).toBe(false) + expect( + isMissingSpecifierError( + moduleNotFound( + "Cannot find package '@aws-sdk/client-bedrock-runtime' imported from /x", + ), + '@aws-sdk/client-bedrock', + ), + ).toBe(false) + }) + + test('ignores errors that are not ERR_MODULE_NOT_FOUND', () => { + const other = new Error("Cannot find package 'sharp'") + ;(other as { code?: string }).code = 'ERR_SOMETHING_ELSE' + expect(isMissingSpecifierError(other, 'sharp')).toBe(false) + expect(isMissingSpecifierError(undefined, 'sharp')).toBe(false) + }) +}) + +describe('importRuntimeModule', () => { + test('rejects with the raw error for a missing module (no friendly wrapping)', async () => { + const promise = importRuntimeModule('@openclaude/does-not-exist-xyz') + // Raw error wording differs by runtime (Node: "Cannot find package", + // Bun: "Cannot find module") — the point is it is NOT the friendly message. + await expect(promise).rejects.toThrow(/Cannot find (module|package)/) + await expect(promise).rejects.not.toThrow(/npm install/) + }) +}) diff --git a/src/utils/optionalRuntimeModule.ts b/src/utils/optionalRuntimeModule.ts new file mode 100644 index 000000000..5b771ef48 --- /dev/null +++ b/src/utils/optionalRuntimeModule.ts @@ -0,0 +1,91 @@ +/** + * Runtime loading for optional, on-demand dependencies. + * + * Some provider SDKs and native helpers are NOT shipped in the default install + * (see OPTIONAL_RUNTIME_EXTERNALS in scripts/externals.ts) — they are loaded + * only when a provider/feature that needs them is actually used. We import them + * through a `new Function` indirection so esbuild cannot see the specifier: + * that keeps the package out of the CLI bundle AND prevents esbuild from + * hoisting the package's own static imports (e.g. @anthropic-ai/bedrock-sdk's + * static `@aws-sdk/client-bedrock-runtime` import) into the bundle, which would + * otherwise make those packages required at startup for every user. + */ + +// Hidden from esbuild's static analysis: resolved from node_modules at runtime. +const runtimeImport = new Function( + 'specifier', + 'return import(specifier)', +) as (specifier: string) => Promise + +export class OptionalRuntimeModuleUnavailableError extends Error { + constructor( + readonly feature: string, + readonly specifier: string, + ) { + super( + `${feature} requires the "${specifier}" package, which is not installed. ` + + `Install it with \`npm install ${specifier}\` (add \`-g\` if you installed the CLI globally) to enable it.`, + ) + this.name = 'OptionalRuntimeModuleUnavailableError' + } +} + +export function isOptionalRuntimeModuleUnavailableError( + error: unknown, +): error is OptionalRuntimeModuleUnavailableError { + return error instanceof OptionalRuntimeModuleUnavailableError +} + +/** Raw runtime import — rejects with the underlying error if the module is missing. */ +export function importRuntimeModule(specifier: string): Promise { + return runtimeImport(specifier) +} + +/** + * True only when `error` is the resolver reporting that THIS specifier (not a + * lookalike transitive package) could not be found. Node reports the unresolved + * name quoted — `Cannot find package 'sharp' imported from ...` — so we match + * the quoted token rather than a raw substring. A bare includes() would + * misattribute a missing transitive package whose name merely contains ours + * (`sharp` ⊂ `sharp-libvips`, `@aws-sdk/client-bedrock` ⊂ + * `@aws-sdk/client-bedrock-runtime`) and print an install hint for the wrong + * package instead of surfacing the real failure. + */ +export function isMissingSpecifierError( + error: unknown, + specifier: string, +): boolean { + const code = (error as { code?: string } | undefined)?.code + if (code !== 'ERR_MODULE_NOT_FOUND') return false + const message = error instanceof Error ? error.message : String(error) + return ( + message.includes(`'${specifier}'`) || message.includes(`"${specifier}"`) + ) +} + +/** + * Import an optional runtime dependency. If the package itself is not installed, + * throw a clear, actionable error naming the feature and the install command + * instead of a cryptic ERR_MODULE_NOT_FOUND. + * + * The guard checks the error `code` AND that the failing specifier is the + * package we asked for — so a genuine missing-package error is reported with + * the install hint, while a broken transitive dependency inside an installed + * package surfaces its real error rather than a misleading "not installed". + */ +export async function importOptionalRuntimeModule( + specifier: string, + feature: string, +): Promise { + try { + return (await runtimeImport(specifier)) as T + } catch (e) { + if (isMissingSpecifierError(e, specifier)) { + // Context-neutral install hint: this helper backs both the globally + // installed CLI and the project-local ./sdk consumers, so don't prescribe + // `-g` (which is wrong for a local SDK install). + throw new OptionalRuntimeModuleUnavailableError(feature, specifier) + } + throw e + } +} diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index d650c7253..bed385a0f 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -17,6 +17,7 @@ import { getTLSFetchOptions, type TLSConfig, } from './mtls.js' +import { importOptionalRuntimeModule } from './optionalRuntimeModule.js' // Disable fetch keep-alive after a stale-pool ECONNRESET so retries open a // fresh TCP connection instead of reusing the dead pooled socket. Sticky for @@ -401,8 +402,14 @@ export async function getAWSClientProxyConfig(): Promise { } const [{ NodeHttpHandler }, { defaultProvider }] = await Promise.all([ - import('@smithy/node-http-handler'), - import('@aws-sdk/credential-provider-node'), + importOptionalRuntimeModule( + '@smithy/node-http-handler', + 'AWS proxy support', + ), + importOptionalRuntimeModule( + '@aws-sdk/credential-provider-node', + 'AWS proxy support', + ), ]) const agent = createHttpsProxyAgent(proxyUrl)