Compare commits

..

2 Commits

Author SHA1 Message Date
Shoubhit Dash 909bcee42b docs: simplify local reasoning notes 2026-08-17 20:08:46 +05:30
Shoubhit Dash 1272cc2f05 feat(ai): configure local reasoning controls 2026-08-17 20:05:10 +05:30
49 changed files with 2134 additions and 431 deletions
+138 -46
View File
@@ -342,6 +342,9 @@
"version": "1.18.4",
"dependencies": {
"@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/amazon-bedrock": "4.0.112",
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/azure": "3.0.88",
"@ai-sdk/cerebras": "2.0.41",
"@ai-sdk/cohere": "3.0.27",
"@ai-sdk/deepinfra": "2.0.41",
@@ -349,11 +352,13 @@
"@ai-sdk/google-vertex": "4.0.128",
"@ai-sdk/groq": "3.0.31",
"@ai-sdk/mistral": "3.0.51",
"@ai-sdk/openai-compatible": "2.0.41",
"@ai-sdk/perplexity": "3.0.26",
"@ai-sdk/provider": "3.0.8",
"@ai-sdk/provider-utils": "4.0.23",
"@ai-sdk/togetherai": "2.0.41",
"@ai-sdk/vercel": "2.0.39",
"@aws-sdk/credential-providers": "3.1057.0",
"@ff-labs/fff-bun": "0.10.1",
"@ff-labs/fff-node": "0.10.1",
"@lydell/node-pty": "catalog:",
@@ -373,6 +378,7 @@
"effect": "catalog:",
"fuzzysort": "3.1.0",
"gitlab-ai-provider": "6.12.1",
"google-auth-library": "10.5.0",
"gray-matter": "4.0.3",
"htmlparser2": "8.0.2",
"ignore": "7.0.5",
@@ -1157,7 +1163,7 @@
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.112", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/openai": "3.0.67", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-PsSh7a6qW+3kQXPs1kD4wDwuZby0t1PIaB6j/1aMKmPFJ5LxcIcULLMF/bjITLt5o/8lc0t6TXIwG0zlhH7uZw=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.77", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ML8C2M1YvPA1ulEx4TiyF0k1xvC2ikEiPBIC1PPQ0a5xELUGrO2lAaEzsTEoJ+eCeDd8PSBuFJjs+r+9yIwQXA=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.82", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WKKou2wbhGGYV8PSALAPyV2YY4nfCqCPkyBzYtJtDA9yCcIFwsbtkTNgg7bqtLCVzeEsY7wwxRoCWy+EMfrw/A=="],
"@ai-sdk/azure": ["@ai-sdk/azure@3.0.88", "", { "dependencies": { "@ai-sdk/deepseek": "2.0.47", "@ai-sdk/openai": "3.0.84", "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RRjZkB1lYplh8dpBarnvkl1j7sYLHsyXua7erL3oNcMK7fHcl4bPO5C7iQhD1O/DqD/zCceDifnege1s+8yEvw=="],
@@ -1185,7 +1191,7 @@
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.51", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-83eXY6p0lUFhSuMvNDmTKDuMciK5XDAWDlNh5c0L80tKjmtCFRItA1MZHp4IKe1r7eK8Rb5nN7qtxqMLUFRIRw=="],
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="],
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-oAiGC9eWG7IgtdsdS74bOCnAAHarAfTJhWN9x5INwnWPekL802AvF+0I5DvLzIF1MIRmNw4N8mPSL/GUVbX9Mw=="],
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
@@ -1307,6 +1313,8 @@
"@aws-sdk/client-athena": ["@aws-sdk/client-athena@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.933.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9eMUCu1Ay3C9ojo+dJcynSdpbxuwDVtZUt/Xhce+c2+mgDsmvRzjww+wfLpZwRNWxBWmeauQQAZk52tCwQgXsQ=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1057.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/fetch-http-handler": "^5.4.5", "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-5MliYkp2u0+2arTp5fZIaxl+xmm90LEKv/VeSxhfNQW4t0fvWJrNO429/jchWQenNoDRrOGE59VfbuZUfwFujg=="],
"@aws-sdk/client-firehose": ["@aws-sdk/client-firehose@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.933.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-tDrtgczN2lQsflLDPYu/wdOoyCZLVYtgzmWnYzSEOBWd/cp2AbuQ7D+FemSwUTzyoMTuhhIevyEJKzqsF+QYxA=="],
"@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.1106.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/credential-provider-node": "^3.972.78", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-vxZtyCy6k9GaZ6j39zg1cNHDtAgwan/a2cQRtnaiQ4iUtX4MGxHdErKyhYAj59tkSqiKAMQpOFhIH7mb0u/9UQ=="],
@@ -1319,21 +1327,25 @@
"@aws-sdk/core": ["@aws-sdk/core@3.932.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.2", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-AS8gypYQCbNojwgjvZGkJocC2CoEICDx9ZJ15ILsv+MlcCVLtUJSRSx3VzJOUY2EEIaGLRrPNlIqyn/9/fySvA=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ozge/c7NdHUDyHqro6+P5oHt8wfKSUBN+olttiVfBe9Mw3wBMpPa3gQ0pZnG+gwBkKskBuip2bMR16tqYvUSEA=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.66", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-i4HTkP21eHaXA+XKoc6dYxlKPvYUqbusGGIsFRcSGTF8ln/q6RrWoRdVJ6UVODTCW59Ot1mVcbJXRAEf6kxa3w=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-b6N9Nnlg8JInQwzBkUq5spNaXssM3h3zLxGzpPrnw0nHSIWPJPTbZzA5Ca285fcDUFuKP+qf3qkuqlAjGOdWhg=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.67", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-env": "3.932.0", "@aws-sdk/credential-provider-http": "3.932.0", "@aws-sdk/credential-provider-process": "3.932.0", "@aws-sdk/credential-provider-sso": "3.933.0", "@aws-sdk/credential-provider-web-identity": "3.933.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HygGyKuMG5AaGXsmM0d81miWDon55xwalRHB3UmDg3QBhtunbNIoIaWUbNTKuBZXcIN6emeeEZw/YgSMqLc0YA=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.69", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.12", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/credential-provider-env": "^3.972.67", "@aws-sdk/credential-provider-http": "^3.972.69", "@aws-sdk/credential-provider-login": "^3.972.74", "@aws-sdk/credential-provider-process": "^3.972.67", "@aws-sdk/credential-provider-sso": "^3.973.11", "@aws-sdk/credential-provider-web-identity": "^3.972.73", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.74", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-0AQfDcf99TNmqVKv0owHrw/TQs6i4ZE5t9qmz6NvO53bE/sA/tpXhXL9AAcEP1qHc6Zzjd1UMb69+/9zdhvY3g=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.933.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.932.0", "@aws-sdk/credential-provider-http": "3.932.0", "@aws-sdk/credential-provider-ini": "3.933.0", "@aws-sdk/credential-provider-process": "3.932.0", "@aws-sdk/credential-provider-sso": "3.933.0", "@aws-sdk/credential-provider-web-identity": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-L2dE0Y7iMLammQewPKNeEh1z/fdJyYEU+/QsLBD9VEh+SXcN/FIyTi21Isw8wPZN6lMB9PDVtISzBnF8HuSFrw=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-BodZYKvT4p/Dkm28Ql/FhDdS1+p51bcZeMMu2TRtU8PoMDHnVDhHz27zASEKSZwmhvquxHrZHB0IGuVqjZUtSQ=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.67", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.933.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.933.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/token-providers": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/R1DBR7xNcuZIhS2RirU+P2o8E8/fOk+iLAhbqeSTq+g09fP/F6W7ouFpS5eVE2NIfWG7YBFoVddOhvuqpn51g=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.11", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/token-providers": "3.1103.0", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-c7Eccw2lhFx2/+qJn3g+uIDWRuWi2A6Sz3PVvckFUEzPsP0dPUo19hlvtarwP5GzrsXn0yEPRVhpewsIaSCGaQ=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.73", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1057.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1057.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-cognito-identity": "^3.972.38", "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.46", "@aws-sdk/credential-provider-login": "^3.972.45", "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-rbrEHtz11g0kxsSkYr3fx2HABNNblp4AhB2MgPvJHgYOWfJ2eBviU7Mvoaef0PW8QH6lbZDfJcnM7eKvtvz3sw=="],
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-cnCLWeKPYgvV4yRYPFH6pWMdUByvu2cy2BAlfsPpvnm4RaVioztyvxmQj5PmVN5fvWs5w/2d6U7le8X9iye2sA=="],
@@ -1355,13 +1367,13 @@
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@smithy/core": "^3.18.2", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-9BGTbJyA/4PTdwQWE9hAFIJGpsYkyEW20WON3i15aDqo5oRZwZmqaVageOD57YYqG8JDJjvcwKyDdR4cc38dvg=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.41", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ=="],
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-KL2JZqH6aYeQssu1g1KuWsReupdfOoxD6f1as2VC+rdwYFUu4LfzMsFfXnBvvQWWqQ7rZHWOw1T+o5gJmg7Dzw=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.932.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-NCIRJvoRc9246RZHIusY1+n/neeG2yGhBGdKhghmrNdM+mLLN6Ii7CKFZjx3DhxtpHMpl1HWLTMhdVrGwP2upw=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Qzq7zj9yXUgAAJEbbmqRhm0jmUndl8nHG0AbxFEfCfQRVZWL96Qzx0mf8lYwT9hIMrXncLwy31HOthmbXwFRwQ=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1103.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ=="],
"@aws-sdk/types": ["@aws-sdk/types@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-we/vaAgwlEFW7IeftmCLlLMw+6hFs3DzZPJw7lVHbj/5HJ0bz9gndxEsS2lQoeJ1zhiiLqAqvXxmM43s0MBg0A=="],
@@ -5965,8 +5977,6 @@
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.81", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA=="],
"@ai-sdk/amazon-bedrock/@ai-sdk/openai": ["@ai-sdk/openai@3.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-oAiGC9eWG7IgtdsdS74bOCnAAHarAfTJhWN9x5INwnWPekL802AvF+0I5DvLzIF1MIRmNw4N8mPSL/GUVbX9Mw=="],
"@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
@@ -5975,6 +5985,8 @@
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="],
"@ai-sdk/azure/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
"@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
@@ -6011,6 +6023,8 @@
"@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.77", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ML8C2M1YvPA1ulEx4TiyF0k1xvC2ikEiPBIC1PPQ0a5xELUGrO2lAaEzsTEoJ+eCeDd8PSBuFJjs+r+9yIwQXA=="],
"@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Enm5UlL0zUCrW3792opk5h7hRWxZOZzDe6eQYVFqX9LUOGGCe1h8MZWAGim765nwzgnjlpeYOsuzZmLtRsTPlg=="],
"@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
@@ -6023,9 +6037,9 @@
"@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="],
"@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
"@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
@@ -6111,6 +6125,12 @@
"@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-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.78", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.67", "@aws-sdk/credential-provider-http": "^3.972.69", "@aws-sdk/credential-provider-ini": "^3.973.12", "@aws-sdk/credential-provider-process": "^3.972.67", "@aws-sdk/credential-provider-sso": "^3.973.11", "@aws-sdk/credential-provider-web-identity": "^3.972.73", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/client-lambda/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.78", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.67", "@aws-sdk/credential-provider-http": "^3.972.69", "@aws-sdk/credential-provider-ini": "^3.973.12", "@aws-sdk/credential-provider-process": "^3.972.67", "@aws-sdk/credential-provider-sso": "^3.973.11", "@aws-sdk/credential-provider-web-identity": "^3.972.73", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng=="],
@@ -6139,12 +6159,64 @@
"@aws-sdk/client-sts/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.782.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/credential-provider-env/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/credential-provider-env/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/credential-provider-http/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/credential-provider-http/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/credential-provider-login/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/credential-provider-login/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.41", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ=="],
"@aws-sdk/credential-provider-login/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ozge/c7NdHUDyHqro6+P5oHt8wfKSUBN+olttiVfBe9Mw3wBMpPa3gQ0pZnG+gwBkKskBuip2bMR16tqYvUSEA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-b6N9Nnlg8JInQwzBkUq5spNaXssM3h3zLxGzpPrnw0nHSIWPJPTbZzA5Ca285fcDUFuKP+qf3qkuqlAjGOdWhg=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-env": "3.932.0", "@aws-sdk/credential-provider-http": "3.932.0", "@aws-sdk/credential-provider-process": "3.932.0", "@aws-sdk/credential-provider-sso": "3.933.0", "@aws-sdk/credential-provider-web-identity": "3.933.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HygGyKuMG5AaGXsmM0d81miWDon55xwalRHB3UmDg3QBhtunbNIoIaWUbNTKuBZXcIN6emeeEZw/YgSMqLc0YA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-BodZYKvT4p/Dkm28Ql/FhDdS1+p51bcZeMMu2TRtU8PoMDHnVDhHz27zASEKSZwmhvquxHrZHB0IGuVqjZUtSQ=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.933.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.933.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/token-providers": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/R1DBR7xNcuZIhS2RirU+P2o8E8/fOk+iLAhbqeSTq+g09fP/F6W7ouFpS5eVE2NIfWG7YBFoVddOhvuqpn51g=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-c7Eccw2lhFx2/+qJn3g+uIDWRuWi2A6Sz3PVvckFUEzPsP0dPUo19hlvtarwP5GzrsXn0yEPRVhpewsIaSCGaQ=="],
"@aws-sdk/credential-provider-process/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/credential-provider-process/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/credential-providers/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.78", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.67", "@aws-sdk/credential-provider-http": "^3.972.69", "@aws-sdk/credential-provider-ini": "^3.973.12", "@aws-sdk/credential-provider-process": "^3.972.67", "@aws-sdk/credential-provider-sso": "^3.973.11", "@aws-sdk/credential-provider-web-identity": "^3.972.73", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng=="],
"@aws-sdk/credential-providers/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/nested-clients/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="],
"@aws-sdk/nested-clients/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/token-providers/@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
"@aws-sdk/token-providers/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
"@azure/core-http/@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="],
@@ -6483,7 +6555,7 @@
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hlxLxqbdV+f7gBpL2SbLL+UhjV+DfUpJ28J4vSHekVV9fiTRrhqcd/ewJwaVbR/ahtLCGJPAVYzj+yJzaddc6g=="],
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.82", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WKKou2wbhGGYV8PSALAPyV2YY4nfCqCPkyBzYtJtDA9yCcIFwsbtkTNgg7bqtLCVzeEsY7wwxRoCWy+EMfrw/A=="],
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="],
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -7103,22 +7175,14 @@
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/client-lambda/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/client-lambda/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.67", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.69", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.12", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/credential-provider-env": "^3.972.67", "@aws-sdk/credential-provider-http": "^3.972.69", "@aws-sdk/credential-provider-login": "^3.972.74", "@aws-sdk/credential-provider-process": "^3.972.67", "@aws-sdk/credential-provider-sso": "^3.973.11", "@aws-sdk/credential-provider-web-identity": "^3.972.73", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.67", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.11", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/token-providers": "3.1103.0", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.73", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A=="],
"@aws-sdk/client-sts/@aws-sdk/core/fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw=="],
@@ -7133,11 +7197,51 @@
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.782.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/nested-clients": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xCna0opVPaueEbJoclj5C6OpDNi0Gynj+4d7tnuXGgQhTHPyAz8ZyClkVqpi5qvHTgxROdUEDxWqEO5jqRHZHQ=="],
"@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/credential-provider-env/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/credential-provider-http/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Qzq7zj9yXUgAAJEbbmqRhm0jmUndl8nHG0AbxFEfCfQRVZWL96Qzx0mf8lYwT9hIMrXncLwy31HOthmbXwFRwQ=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/credential-providers/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/nested-clients/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
"@aws-sdk/token-providers/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
@@ -7485,9 +7589,9 @@
"@vercel/routing-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
"ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
@@ -8201,14 +8305,6 @@
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.41", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.41", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1103.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.41", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ=="],
"@aws-sdk/client-sts/@aws-sdk/core/fast-xml-parser/strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
@@ -8219,6 +8315,8 @@
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
"@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
@@ -8881,12 +8979,6 @@
"@astrojs/vercel/astro/vite/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="],
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
+24 -41
View File
@@ -92,7 +92,7 @@ const GeminiFunctionCallPart = Schema.Struct({
functionCall: Schema.Struct({
id: Schema.optional(Schema.String),
name: Schema.String,
args: Schema.optional(Schema.Unknown),
args: Schema.Unknown,
}),
thoughtSignature: Schema.optional(Schema.String),
})
@@ -190,19 +190,8 @@ const GeminiCandidate = Schema.Struct({
finishReason: Schema.optional(Schema.String),
})
const GeminiPromptFeedback = Schema.StructWithRest(
Schema.Struct({
blockReason: Schema.optional(Schema.String),
blockReasonMessage: Schema.optional(Schema.String),
safetyRatings: Schema.optional(Schema.Unknown),
}),
[Schema.Record(Schema.String, Schema.Unknown)],
)
type GeminiPromptFeedback = Schema.Schema.Type<typeof GeminiPromptFeedback>
const GeminiEvent = Schema.Struct({
candidates: optionalArray(GeminiCandidate),
promptFeedback: Schema.optional(GeminiPromptFeedback),
usageMetadata: Schema.optional(GeminiUsage),
})
type GeminiEvent = Schema.Schema.Type<typeof GeminiEvent>
@@ -211,7 +200,6 @@ interface ParserState {
readonly finishReason?: string
readonly hasToolCalls: boolean
readonly nextToolCallId: number
readonly promptFeedback?: GeminiPromptFeedback
readonly usage?: Usage
readonly lifecycle: Lifecycle.State
readonly reasoningSignature?: string
@@ -515,37 +503,32 @@ const mapFinishReason = (finishReason: string | undefined, hasToolCalls: boolean
return "unknown"
}
const finish = (state: ParserState): ReadonlyArray<LLMEvent> => {
const promptBlockReason = state.finishReason === undefined ? state.promptFeedback?.blockReason : undefined
const finishReason = state.finishReason ?? promptBlockReason
if (finishReason === undefined && state.usage === undefined) return []
const events: LLMEvent[] = []
const lifecycle = state.reasoningSignature
? Lifecycle.reasoningEnd(
state.lifecycle,
events,
"reasoning-0",
googleMetadata({ thoughtSignature: state.reasoningSignature }),
)
: state.lifecycle
Lifecycle.finish(lifecycle, events, {
reason: {
normalized:
promptBlockReason === undefined ? mapFinishReason(finishReason, state.hasToolCalls) : "content-filter",
raw: finishReason,
},
usage: state.usage,
providerMetadata:
state.promptFeedback === undefined ? undefined : googleMetadata({ promptFeedback: state.promptFeedback }),
})
return events
}
const finish = (state: ParserState): ReadonlyArray<LLMEvent> =>
state.finishReason || state.usage
? (() => {
const events: LLMEvent[] = []
const lifecycle = state.reasoningSignature
? Lifecycle.reasoningEnd(
state.lifecycle,
events,
"reasoning-0",
googleMetadata({ thoughtSignature: state.reasoningSignature }),
)
: state.lifecycle
Lifecycle.finish(lifecycle, events, {
reason: {
normalized: mapFinishReason(state.finishReason, state.hasToolCalls),
raw: state.finishReason,
},
usage: state.usage,
})
return events
})()
: []
const step = (state: ParserState, event: GeminiEvent) => {
const nextState = {
...state,
promptFeedback: event.promptFeedback ?? state.promptFeedback,
usage: event.usageMetadata ? (mapUsage(event.usageMetadata) ?? state.usage) : state.usage,
}
const candidate = event.candidates?.[0]
@@ -586,7 +569,7 @@ const step = (state: ParserState, event: GeminiEvent) => {
}
if ("functionCall" in part) {
const input = part.functionCall.args === undefined ? {} : part.functionCall.args
const input = part.functionCall.args
const id = `tool_${nextToolCallId++}`
const metadata = {
...(part.functionCall.id === undefined ? {} : { functionCallId: part.functionCall.id }),
@@ -1,4 +1,4 @@
import { ProviderID, type ModelID } from "../schema/index.js"
import { ProviderID, type ModelID, type ReasoningEffort } from "../schema/index.js"
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat.js"
import type { RouteDefaultsInput } from "../route/client.js"
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
@@ -19,6 +19,7 @@ export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL: string
readonly provider?: string
readonly reasoningEffort?: ReasoningEffort
}
export type FamilyModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
@@ -75,6 +76,8 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
provider: settings.provider,
providerOptions:
settings.reasoningEffort === undefined ? undefined : { openai: { reasoningEffort: settings.reasoningEffort } },
}).model(modelID)
export const baseten = define(profiles.baseten)
+12
View File
@@ -106,6 +106,18 @@ describe("provider package entrypoints", () => {
})
})
test("maps OpenAI-compatible Chat reasoning effort onto the executable model", async () => {
const OpenAICompatible = await import("@opencode-ai/ai/providers/openai-compatible")
const selected = OpenAICompatible.model("custom-model", {
baseURL: "https://chat.example.test/v1",
provider: "example",
reasoningEffort: "high",
})
expect(String(selected.provider)).toBe("example")
expect(selected.route.defaults.providerOptions).toEqual({ openai: { reasoningEffort: "high" } })
})
test("maps Anthropic-compatible settings onto the executable model", async () => {
const AnthropicCompatible = await import("@opencode-ai/ai/providers/anthropic-compatible")
const selected = AnthropicCompatible.model("compatible-model", {
-70
View File
@@ -767,31 +767,6 @@ describe("Gemini route", () => {
}),
)
it.effect("defaults omitted function call args to an empty object", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(
LLMRequest.update(request, {
tools: [ToolDefinition.make({ name: "ping", description: "Ping", inputSchema: { type: "object" } })],
}),
).pipe(
Effect.provide(
fixedResponse(
sseEvents({
candidates: [
{
content: { role: "model", parts: [{ functionCall: { name: "ping" } }] },
finishReason: "STOP",
},
],
}),
),
),
)
expect(response.toolCalls).toEqual([{ type: "tool-call", id: "tool_0", name: "ping", input: {} }])
}),
)
it.effect("maps tool calls without a finish reason", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(
@@ -887,51 +862,6 @@ describe("Gemini route", () => {
}),
)
it.effect("preserves candidate-less prompt safety blocks as content-filter outcomes", () =>
Effect.gen(function* () {
const blocked = yield* LLMClient.generate(request).pipe(
Effect.provide(
fixedResponse(
sseEvents({
promptFeedback: {
blockReason: "FUTURE_SAFETY_REASON",
blockReasonMessage: "Prompt blocked",
safetyRatings: [{ category: "HARM_CATEGORY_HARASSMENT", blocked: true }],
},
}),
),
),
)
const blockedWithUsage = yield* LLMClient.generate(request).pipe(
Effect.provide(
fixedResponse(
sseEvents(
{ promptFeedback: { blockReason: "SAFETY" } },
{ usageMetadata: { promptTokenCount: 7, totalTokenCount: 7 } },
),
),
),
)
expect(blocked.events.map((event) => event.type)).toEqual(["step-start", "step-finish", "finish"])
expect(blocked.events.at(-1)).toMatchObject({
type: "finish",
reason: { normalized: "content-filter", raw: "FUTURE_SAFETY_REASON" },
providerMetadata: {
google: {
promptFeedback: {
blockReason: "FUTURE_SAFETY_REASON",
blockReasonMessage: "Prompt blocked",
safetyRatings: [{ category: "HARM_CATEGORY_HARASSMENT", blocked: true }],
},
},
},
})
expect(blockedWithUsage.finishReason).toEqual({ normalized: "content-filter", raw: "SAFETY" })
expect(blockedWithUsage.usage).toMatchObject({ inputTokens: 7, totalTokens: 7 })
}),
)
it.effect("maps current blocking and invalid-output finish reasons", () =>
Effect.gen(function* () {
const reasons = [
+12 -6
View File
@@ -94,6 +94,9 @@
},
"dependencies": {
"@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/amazon-bedrock": "4.0.112",
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/azure": "3.0.88",
"@ai-sdk/cerebras": "2.0.41",
"@ai-sdk/cohere": "3.0.27",
"@ai-sdk/deepinfra": "2.0.41",
@@ -101,23 +104,25 @@
"@ai-sdk/google-vertex": "4.0.128",
"@ai-sdk/groq": "3.0.31",
"@ai-sdk/mistral": "3.0.51",
"@ai-sdk/openai-compatible": "2.0.41",
"@ai-sdk/perplexity": "3.0.26",
"@ai-sdk/provider": "3.0.8",
"@ai-sdk/provider-utils": "4.0.23",
"@ai-sdk/togetherai": "2.0.41",
"@ai-sdk/vercel": "2.0.39",
"@ff-labs/fff-bun": "0.10.1",
"@ff-labs/fff-node": "0.10.1",
"@aws-sdk/credential-providers": "3.1057.0",
"@lydell/node-pty": "catalog:",
"@modelcontextprotocol/sdk": "1.29.0",
"@opencode-ai/ai": "workspace:*",
"@ff-labs/fff-bun": "0.10.1",
"@ff-labs/fff-node": "0.10.1",
"@opencode-ai/codemode": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/ai": "workspace:*",
"@opencode-ai/schema": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@standard-schema/spec": "catalog:",
"@parcel/watcher": "2.5.1",
"@silvia-odwyer/photon-node": "0.3.4",
"@standard-schema/spec": "catalog:",
"ai-gateway-provider": "3.1.2",
"bun-pty": "0.4.8",
"diff": "catalog:",
@@ -125,10 +130,11 @@
"effect": "catalog:",
"fuzzysort": "3.1.0",
"gitlab-ai-provider": "6.12.1",
"google-auth-library": "10.5.0",
"gray-matter": "4.0.3",
"htmlparser2": "8.0.2",
"ignore": "7.0.5",
"immer": "11.1.4",
"ignore": "7.0.5",
"jsonc-parser": "3.3.1",
"mime-types": "3.0.2",
"tree-sitter-bash": "0.25.0",
@@ -1,43 +1,10 @@
import { Effect } from "effect"
import { sql } from "drizzle-orm"
import type { DatabaseMigration } from "../migration.js"
const previousV2Marker = "20260730195856_optional_session_title"
const migration: DatabaseMigration.Migration = {
id: "20260804233008_loose_psylocke",
up(tx) {
return Effect.gen(function* () {
// This marker identifies the completed pre-split V2 lineage. Its V2 tables
// are canonical, so rename them in place instead of replaying the V1 squash.
if (yield* tx.get(sql`SELECT id FROM migration WHERE id = ${previousV2Marker}`)) {
const v1Only = yield* tx.get(sql`
SELECT 1
FROM message
WHERE NOT EXISTS (
SELECT 1 FROM session_message WHERE session_message.session_id = message.session_id
)
LIMIT 1
`)
if (v1Only) return yield* Effect.die(new Error("Previous V2 database contains V1-only session history"))
yield* tx.run(`DROP INDEX IF EXISTS \`session_project_idx\`;`)
yield* tx.run(`DROP INDEX IF EXISTS \`session_workspace_idx\`;`)
yield* tx.run(`DROP INDEX IF EXISTS \`session_parent_idx\`;`)
yield* tx.run(`DROP INDEX IF EXISTS \`session_time_suspended_idx\`;`)
yield* tx.run(`ALTER TABLE \`session\` RENAME TO \`session_v2\`;`)
yield* tx.run(`CREATE INDEX \`session_v2_project_idx\` ON \`session_v2\` (\`project_id\`);`)
yield* tx.run(`CREATE INDEX \`session_v2_workspace_idx\` ON \`session_v2\` (\`workspace_id\`);`)
yield* tx.run(`CREATE INDEX \`session_v2_parent_idx\` ON \`session_v2\` (\`parent_id\`);`)
yield* tx.run(
`CREATE INDEX \`session_v2_time_suspended_idx\` ON \`session_v2\` (\`time_suspended\`) WHERE "session_v2"."time_suspended" is not null;`,
)
yield* tx.run(`DROP TABLE IF EXISTS \`data_migration\`;`)
yield* tx.run(`DROP TABLE IF EXISTS \`session_context_epoch\`;`)
yield* tx.run(`DROP TABLE IF EXISTS \`session_input\`;`)
return
}
yield* tx.run(`
CREATE TABLE IF NOT EXISTS \`kv\` (
\`key\` text PRIMARY KEY,
+1 -1
View File
@@ -33,7 +33,7 @@ const layer = Layer.effect(
` Workspace root folder: ${location.project.directory}`,
` Is directory a git repo: ${location.vcs?.type === "git" ? "yes" : "no"}`,
` Platform: ${process.platform}`,
` Prefer ${global.tmp} over generic system temporary directories such as /tmp; it is pre-created and approved for external access.`,
` Use ${global.tmp} for temporary work outside the workspace; it already exists and is pre-approved for external directory access.`,
"</env>",
].join("\n"),
),
+2 -4
View File
@@ -188,10 +188,8 @@ const resolveCatalogModel = Effect.fn("ModelResolver.resolveCatalogModel")(funct
if (
Provider.isAISDK(resolved.package) &&
packageName === "@ai-sdk/openai-compatible" &&
typeof resolved.settings?.baseURL !== "string"
)
return yield* unsupported(resolved)
if (Provider.isAISDK(resolved.package) && packageName === "@ai-sdk/openai-compatible") {
typeof resolved.settings?.baseURL === "string"
) {
const runtime = yield* prepareProviderModel(resolved)
return withDefaults(runtime, OpenAICompatibleChat.route)
.with({ auth: key === undefined ? Auth.none : Auth.bearer(key) })
+4
View File
@@ -20,6 +20,8 @@ import { MistralPlugin } from "./provider/mistral.js"
import { NvidiaPlugin } from "./provider/nvidia.js"
import { OllamaPlugin } from "./provider/ollama.js"
import { OpenAIPlugin } from "./provider/openai.js"
import { SnowflakeCortexPlugin } from "./provider/snowflake-cortex.js"
import { OpenAICompatiblePlugin } from "./provider/openai-compatible.js"
import { OpencodePlugin } from "./provider/opencode.js"
import { OpenRouterPlugin } from "./provider/openrouter.js"
import { PerplexityPlugin } from "./provider/perplexity.js"
@@ -54,6 +56,8 @@ export const ProviderPlugins: PluginInternal.InternalPlugin[] = [
NvidiaPlugin,
OllamaPlugin,
OpencodePlugin,
SnowflakeCortexPlugin,
OpenAICompatiblePlugin,
OpenAIPlugin,
OpenRouterPlugin,
PerplexityPlugin,
@@ -1,7 +1,64 @@
import { Effect } from "effect"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { define } from "@opencode-ai/plugin/effect/plugin"
import { Provider } from "../../provider.js"
type MantleSDK = {
languageModel: (modelID: string) => LanguageModelV3
chat: (modelID: string) => LanguageModelV3
responses: (modelID: string) => LanguageModelV3
}
// Bedrock cross-region inference profiles require regional prefixes only for
// specific model/region combinations. Keep the mapping narrow and avoid
// double-prefixing model IDs that models.dev already marks as global/us/eu/etc.
function resolveModelID(modelID: string, region: string | undefined) {
const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."]
if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) return modelID
const resolvedRegion = region ?? "us-east-1"
const regionPrefix = resolvedRegion.split("-")[0]
if (regionPrefix === "us") {
const requiresPrefix = ["nova-micro", "nova-lite", "nova-pro", "nova-premier", "nova-2", "claude", "deepseek"].some(
(item) => modelID.includes(item),
)
if (requiresPrefix && !resolvedRegion.startsWith("us-gov")) return `${regionPrefix}.${modelID}`
return modelID
}
if (regionPrefix === "eu") {
const regionRequiresPrefix = [
"eu-west-1",
"eu-west-2",
"eu-west-3",
"eu-north-1",
"eu-central-1",
"eu-south-1",
"eu-south-2",
].some((item) => resolvedRegion.includes(item))
const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((item) =>
modelID.includes(item),
)
return regionRequiresPrefix && modelRequiresPrefix ? `${regionPrefix}.${modelID}` : modelID
}
if (regionPrefix !== "ap") return modelID
const australia = ["ap-southeast-2", "ap-southeast-4"].includes(resolvedRegion)
if (australia && ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((item) => modelID.includes(item))) {
return `au.${modelID}`
}
const prefix = resolvedRegion === "ap-northeast-1" ? "jp" : "apac"
return ["claude", "nova-lite", "nova-micro", "nova-pro"].some((item) => modelID.includes(item))
? `${prefix}.${modelID}`
: modelID
}
function selectMantleModel(sdk: MantleSDK, modelID: string) {
if (modelID === "openai.gpt-oss-safeguard-20b" || modelID === "openai.gpt-oss-safeguard-120b")
return sdk.chat(modelID)
return sdk.responses(modelID)
}
export const AmazonBedrockPlugin = define({
id: "opencode.provider.amazon-bedrock",
effect: Effect.fn(function* (ctx) {
@@ -18,5 +75,54 @@ export const AmazonBedrockPlugin = define({
})
}
})
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (!["@ai-sdk/amazon-bedrock", "@ai-sdk/amazon-bedrock/mantle"].includes(evt.package)) return
const options = { ...evt.options }
const profile = typeof options.profile === "string" ? options.profile : process.env.AWS_PROFILE
const region = typeof options.region === "string" ? options.region : (process.env.AWS_REGION ?? "us-east-1")
const bearerToken =
process.env.AWS_BEARER_TOKEN_BEDROCK ??
(typeof options.bearerToken === "string" ? options.bearerToken : undefined)
if (bearerToken && !process.env.AWS_BEARER_TOKEN_BEDROCK) process.env.AWS_BEARER_TOKEN_BEDROCK = bearerToken
const containerCreds = Boolean(
process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI,
)
options.region = region
if (typeof options.endpoint === "string") options.baseURL = options.endpoint
if (!bearerToken && options.credentialProvider === undefined) {
// Do not gate SDK creation on explicit AWS env vars. The default chain
// also handles ~/.aws/credentials, SSO, process creds, and instance roles.
const { fromNodeProviderChain } = yield* Effect.promise(() => import("@aws-sdk/credential-providers"))
options.credentialProvider = fromNodeProviderChain(profile ? { profile } : {})
}
if (evt.package === "@ai-sdk/amazon-bedrock/mantle") {
const mod = yield* Effect.promise(() => import("@ai-sdk/amazon-bedrock/mantle"))
evt.sdk = mod.createBedrockMantle(options)
return
}
const mod = yield* Effect.promise(() => import("@ai-sdk/amazon-bedrock"))
evt.sdk = mod.createAmazonBedrock(options)
}),
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== Provider.ID.amazonBedrock) return
if (
Provider.isAISDK(evt.model.package) &&
Provider.packageName(evt.model.package) === "@ai-sdk/amazon-bedrock/mantle"
) {
evt.language = selectMantleModel(evt.sdk, evt.model.modelID ?? evt.model.id)
return
}
const region = typeof evt.options.region === "string" ? evt.options.region : process.env.AWS_REGION
evt.language = evt.sdk.languageModel(resolveModelID(evt.model.modelID ?? evt.model.id, region))
}),
)
}),
})
@@ -17,5 +17,13 @@ export const AnthropicPlugin = define({
})
}
})
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/anthropic") return
const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic"))
evt.sdk = mod.createAnthropic(evt.options)
}),
)
}),
})
@@ -5,6 +5,14 @@ import { Provider } from "../../provider.js"
import { iife } from "../../util/iife.js"
import { configuredSettings } from "./configured.js"
function selectLanguage(sdk: any, modelID: string, useChat: boolean) {
if (useChat && sdk.chat) return sdk.chat(modelID)
if (sdk.responses) return sdk.responses(modelID)
if (sdk.messages) return sdk.messages(modelID)
if (sdk.chat) return sdk.chat(modelID)
return sdk.languageModel(modelID)
}
export const AzurePlugin = define({
id: "opencode.provider.azure",
effect: Effect.fn(function* (ctx) {
@@ -57,6 +65,34 @@ export const AzurePlugin = define({
}
}
})
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/azure") return
if (evt.model.providerID === Provider.ID.azure) {
if (
!evt.options.resourceName &&
!evt.options.baseURL &&
(!Provider.isAISDK(evt.model.package) || typeof evt.model.settings?.baseURL !== "string")
) {
throw new Error("Azure resource name is missing; set AZURE_RESOURCE_NAME or configure resourceName/baseURL")
}
}
const mod = yield* Effect.promise(() => import("@ai-sdk/azure"))
evt.sdk = mod.createAzure(evt.options)
}),
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== Provider.ID.azure) return
evt.language = selectLanguage(
evt.sdk,
evt.model.modelID ?? evt.model.id,
Boolean(evt.options.useCompletionUrls),
)
}),
)
}),
})
@@ -1,3 +1,5 @@
import os from "os"
import { App } from "../../app.js"
import { Effect } from "effect"
import { define } from "@opencode-ai/plugin/effect/plugin"
import { Form } from "@opencode-ai/schema/form"
@@ -43,6 +45,33 @@ export const CloudflareWorkersAIPlugin = define({
if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) }
})
})
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.model.providerID !== providerID) return
if (evt.package !== "@ai-sdk/openai-compatible") return
const accountId = resolveAccountId(evt.options)
if (!hasWorkersEndpoint(evt.model) && !accountId) return
const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
evt.sdk = mod.createOpenAICompatible(
sdkOptions(
{
...evt.options,
baseURL: evt.options.baseURL ?? (accountId ? workersEndpoint(accountId) : undefined),
},
ctx.app,
) as any,
)
}),
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== providerID) return
evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id)
}),
)
}),
})
@@ -54,6 +83,31 @@ function workersEndpoint(accountId: string) {
return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`
}
function hasWorkersEndpoint(model: {
readonly package?: string
readonly settings?: Readonly<Record<string, unknown>>
}) {
return Provider.isAISDK(model.package) && typeof model.settings?.baseURL === "string"
}
function sdkOptions(options: Record<string, any>, app: App.Info) {
return {
...options,
baseURL: expandAccountId(options.baseURL),
apiKey: process.env.CLOUDFLARE_API_KEY ?? options.apiKey,
headers: {
"User-Agent": `${App.useragent(app)} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
...options.headers,
},
name: providerID,
}
}
function expandAccountId(baseURL: unknown) {
if (typeof baseURL !== "string") return baseURL
return baseURL.replaceAll("${CLOUDFLARE_ACCOUNT_ID}", process.env.CLOUDFLARE_ACCOUNT_ID ?? "${CLOUDFLARE_ACCOUNT_ID}")
}
function stringOption(options: Record<string, unknown>, key: string) {
return typeof options[key] === "string" ? options[key] : undefined
}
@@ -38,6 +38,22 @@ function replaceVertexVars(value: string, project: string | undefined, location:
.replaceAll("${GOOGLE_VERTEX_ENDPOINT}", vertexEndpoint(location))
}
function authFetch(fetchWithRuntimeOptions?: unknown) {
// Native Vertex SDKs handle ADC internally. OpenAI-compatible Vertex endpoints
// do not, so inject a Google access token into their fetch path.
return async (input: Parameters<typeof fetch>[0], init?: RequestInit) => {
const { GoogleAuth } = await import("google-auth-library")
const auth = new GoogleAuth({ scopes: ["https://www.googleapis.com/auth/cloud-platform"] })
const client = await auth.getClient()
const token = await client.getAccessToken()
const headers = new Headers(init?.headers)
headers.set("Authorization", `Bearer ${token.token}`)
return typeof fetchWithRuntimeOptions === "function"
? fetchWithRuntimeOptions(input, { ...init, headers })
: fetch(input, { ...init, headers })
}
}
export const GoogleVertexPlugin = define({
id: "opencode.provider.google-vertex",
effect: Effect.fn(function* (ctx) {
@@ -62,6 +78,9 @@ export const GoogleVertexPlugin = define({
...(typeof provider.settings?.baseURL === "string"
? { baseURL: replaceVertexVars(provider.settings.baseURL, project, location) }
: {}),
...(Provider.packageName(provider.package)?.includes("@ai-sdk/openai-compatible")
? { fetch: authFetch(provider.settings?.fetch) }
: {}),
}
})
}
@@ -69,6 +88,26 @@ export const GoogleVertexPlugin = define({
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.model.providerID === Provider.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) {
evt.options.fetch = authFetch(evt.options.fetch)
return
}
if (evt.package === "@ai-sdk/google-vertex/anthropic") {
const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic"))
const project = resolveProject(evt.options)
const location = String(resolveLocation(evt.options))
const regionalBaseURL =
(location === "eu" || location === "us") && project && !evt.options.baseURL
? `https://aiplatform.${location}.rep.googleapis.com/v1/projects/${project}/locations/${location}/publishers/anthropic/models`
: undefined
evt.sdk = mod.createVertexAnthropic({
...evt.options,
project,
location,
...(regionalBaseURL ? { baseURL: regionalBaseURL } : {}),
})
return
}
if (evt.package !== "@ai-sdk/google-vertex") return
const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex"))
const project = resolveProject(evt.options)
@@ -6,6 +6,7 @@ import { Config } from "../../config.js"
import { Model } from "../../model.js"
import { Provider } from "../../provider.js"
import type { PluginInternal } from "../internal.js"
import { LocalReasoning } from "./local-reasoning.js"
const providerID = "lmstudio"
@@ -23,6 +24,10 @@ const RemoteModel = Schema.Struct({
capabilities: Schema.Struct({
vision: Schema.Boolean,
trained_for_tool_use: Schema.Boolean,
reasoning: Schema.Struct({
allowed_options: Schema.Array(Schema.Literals(["off", "on", "low", "medium", "high"])),
default: Schema.Literals(["off", "on", "low", "medium", "high"]),
}).pipe(Schema.optional),
}).pipe(Schema.optional),
})
@@ -70,6 +75,7 @@ export function make(origin = "http://127.0.0.1:1234", interval: Duration.Input
input: ["text", ...(item.capabilities?.vision ? ["image"] : [])],
output: ["text"],
}
model.variants = LocalReasoning.fromOptions(item.capabilities?.reasoning?.allowed_options ?? [])
model.limit = {
context:
item.loaded_instances.length === 0
@@ -0,0 +1,47 @@
export * as LocalReasoning from "./local-reasoning.js"
import { Model } from "../../model.js"
type Option = "off" | "on" | "low" | "medium" | "high"
export function fromOptions(options: readonly Option[]) {
return variants(
options.map((option) => {
if (option === "off") return ["none", "none"] as const
if (option === "on") return ["thinking", "medium"] as const
return [option, option] as const
}),
)
}
export function infer(engine: "ollama" | "vllm", model: string) {
const id = model.toLowerCase().replaceAll("_", "-")
if (id.includes("gpt-oss") || id.includes("gptoss"))
return variants([
["low", "low"],
["medium", "medium"],
["high", "high"],
])
if (id.includes("deepseek-v4") || id.includes("deepseekv4"))
return variants([
["none", "none"],
["high", "high"],
["max", "max"],
])
if (id.includes("qwen3") || id.includes("gemma-4") || id.includes("gemma4")) return toggle()
return engine === "ollama" ? toggle() : []
}
function toggle() {
return variants([
["none", "none"],
["thinking", "medium"],
])
}
function variants(items: ReadonlyArray<readonly [id: string, effort: string]>) {
return items.map(([id, effort]) => ({
id: Model.VariantID.make(id),
settings: { reasoningEffort: effort },
}))
}
@@ -6,6 +6,7 @@ import { Config } from "../../config.js"
import { Model } from "../../model.js"
import { Provider } from "../../provider.js"
import type { PluginInternal } from "../internal.js"
import { LocalReasoning } from "./local-reasoning.js"
const providerID = "ollama"
@@ -96,6 +97,9 @@ export function make(origin = "http://127.0.0.1:11434", interval: Duration.Input
input: ["text", ...(item.show.capabilities?.includes("vision") ? ["image"] : [])],
output: ["text"],
}
model.variants = item.show.capabilities?.includes("thinking")
? LocalReasoning.infer("ollama", `${item.model} ${model.family ?? ""}`)
: []
model.limit = {
context:
Object.entries(item.show.model_info ?? {}).flatMap(([key, value]) =>
@@ -0,0 +1,18 @@
import { Effect } from "effect"
import { define } from "@opencode-ai/plugin/effect/plugin"
export const OpenAICompatiblePlugin = define({
id: "opencode.provider.openai-compatible",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.sdk) return
if (!evt.package.includes("@ai-sdk/openai-compatible")) return
if (evt.options.includeUsage !== false) evt.options.includeUsage = true
const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
evt.sdk = mod.createOpenAICompatible(evt.options as any)
}),
)
}),
})
@@ -0,0 +1,90 @@
import { Effect } from "effect"
import { define } from "@opencode-ai/plugin/effect/plugin"
import { Provider } from "../../provider.js"
type FetchLike = (url: string | URL | Request, init?: RequestInit) => Promise<Response>
// Exported for testing: intercepts Cortex-specific request/response quirks.
export function cortexFetch(upstream: FetchLike = fetch) {
return async (url: string | URL | Request, init?: RequestInit): Promise<Response> => {
if (init?.body && typeof init.body === "string") {
try {
const body = JSON.parse(init.body)
if ("max_tokens" in body) {
body.max_completion_tokens = body.max_tokens
delete body.max_tokens
init = { ...init, body: JSON.stringify(body) }
}
} catch {}
}
const response = await upstream(url, init)
// Cortex returns 400 "conversation complete" as a normal stop condition
if (!response.ok && response.status === 400) {
try {
const errorData = (await response.clone().json()) as Record<string, unknown>
if (
String(errorData.message || errorData.error || "")
.toLowerCase()
.includes("conversation complete")
) {
return new Response(
JSON.stringify({ choices: [{ finish_reason: "stop", message: { content: "", role: "assistant" } }] }),
{ status: 200, headers: new Headers({ "content-type": "application/json" }) },
)
}
} catch {}
}
// Cortex returns role:"" in streaming deltas; the AI SDK schema requires "assistant"
if (response.body && response.headers.get("content-type")?.includes("text/event-stream")) {
const reader = response.body.getReader()
const encoder = new TextEncoder()
const decoder = new TextDecoder()
const stream = new ReadableStream({
async pull(ctrl) {
const { done, value } = await reader.read()
if (done) {
ctrl.close()
return
}
ctrl.enqueue(
encoder.encode(decoder.decode(value, { stream: true }).replace(/"role"\s*:\s*""/g, '"role":"assistant"')),
)
},
cancel() {
reader.cancel()
},
})
return new Response(stream, { headers: response.headers, status: response.status })
}
return response
}
}
export const SnowflakeCortexPlugin = define({
id: "opencode.provider.snowflake-cortex",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.model.providerID !== Provider.ID.make("snowflake-cortex")) return
const token =
process.env.SNOWFLAKE_CORTEX_TOKEN ??
process.env.SNOWFLAKE_CORTEX_PAT ??
(typeof evt.options.token === "string" ? evt.options.token : undefined) ??
(typeof evt.options.apiKey === "string" ? evt.options.apiKey : undefined)
const upstream = typeof evt.options.fetch === "function" ? (evt.options.fetch as FetchLike) : undefined
if (evt.options.includeUsage !== false) evt.options.includeUsage = true
const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
evt.sdk = mod.createOpenAICompatible({
...evt.options,
...(token ? { apiKey: token } : {}),
fetch: cortexFetch(upstream) as typeof fetch,
} as any)
}),
)
}),
})
@@ -6,6 +6,7 @@ import { Config } from "../../config.js"
import { Model } from "../../model.js"
import { Provider } from "../../provider.js"
import type { PluginInternal } from "../internal.js"
import { LocalReasoning } from "./local-reasoning.js"
const providerID = "vllm"
@@ -55,6 +56,7 @@ export function make(origin = "http://127.0.0.1:8000", interval: Duration.Input
model.name = item.id
// Tool calling depends on vLLM server flags and parsers that model discovery does not report.
model.capabilities = { tools: false, input: ["text"], output: ["text"] }
model.variants = LocalReasoning.infer("vllm", item.id)
model.limit = { context: item.max_model_len ?? 0, output: 0 }
})
}
+1 -1
View File
@@ -324,7 +324,7 @@ export const layer = (options?: ShellSelect.Options) =>
runFork(
handle.exitCode.pipe(
Effect.flatMap((code) => finish("exited", code)),
Effect.catch(() => finish("exited")),
Effect.catch(() => Effect.void),
),
)
@@ -13,10 +13,6 @@ import { tmpdir } from "./fixture/tmpdir"
import type { SqlClient } from "effect/unstable/sql/SqlClient"
import legacyCredentialsMigration from "@opencode-ai/core/database/migration/20260805200742_import_legacy_credentials"
import worktreeMigration from "@opencode-ai/core/database/migration/20260812213948_worktree"
import previousV2Migration from "@opencode-ai/core/database/migration/20260804233008_loose_psylocke"
import workspaceMigration from "@opencode-ai/core/database/migration/20260808023530_workspace_domain"
import executionClaimsMigration from "@opencode-ai/core/database/migration/20260811161259_execution_claim_attempts"
import sessionInboxMigration from "@opencode-ai/core/database/migration/20260812181746_session_inbox"
import { Global } from "@opencode-ai/util/global"
const run = <A, E>(
@@ -132,142 +128,6 @@ describe("DatabaseMigration", () => {
)
})
test("preserves previous V2 state through the current migration lineage", async () => {
await run(
Effect.gen(function* () {
const db = yield* makeDb
yield* db.run(sql`PRAGMA foreign_keys = ON`)
yield* db.run(sql`CREATE TABLE migration (id text PRIMARY KEY, time_completed integer NOT NULL)`)
yield* db.run(sql`
INSERT INTO migration (id, time_completed)
VALUES ('20260730195856_optional_session_title', 1)
`)
yield* db.run(sql`CREATE TABLE project (id text PRIMARY KEY)`)
yield* db.run(sql`
CREATE TABLE project_directory (
project_id text NOT NULL,
directory text NOT NULL,
type text,
strategy text,
time_created integer NOT NULL,
PRIMARY KEY (project_id, directory)
)
`)
yield* db.run(sql`
CREATE TABLE workspace (
id text PRIMARY KEY,
type text NOT NULL,
name text NOT NULL,
project_id text NOT NULL,
time_used integer NOT NULL
)
`)
yield* db.run(sql`
CREATE TABLE session (
id text PRIMARY KEY,
project_id text NOT NULL REFERENCES project(id) ON DELETE CASCADE,
workspace_id text,
parent_id text,
time_suspended integer
)
`)
yield* db.run(sql`CREATE INDEX session_project_idx ON session (project_id)`)
yield* db.run(sql`CREATE INDEX session_workspace_idx ON session (workspace_id)`)
yield* db.run(sql`CREATE INDEX session_parent_idx ON session (parent_id)`)
yield* db.run(
sql`CREATE INDEX session_time_suspended_idx ON session (time_suspended) WHERE "session"."time_suspended" IS NOT NULL`,
)
yield* db.run(sql`
CREATE TABLE session_message (
id text PRIMARY KEY,
session_id text NOT NULL REFERENCES session(id) ON DELETE CASCADE,
data text NOT NULL
)
`)
yield* db.run(sql`CREATE TABLE message (id text PRIMARY KEY, session_id text NOT NULL)`)
yield* db.run(sql`
CREATE TABLE session_pending (
id text PRIMARY KEY,
session_id text NOT NULL REFERENCES session(id) ON DELETE CASCADE
)
`)
yield* db.run(sql`CREATE TABLE event_sequence (aggregate_id text PRIMARY KEY, seq integer NOT NULL)`)
yield* db.run(sql`
CREATE TABLE event (
id text PRIMARY KEY,
aggregate_id text NOT NULL REFERENCES event_sequence(aggregate_id) ON DELETE CASCADE,
seq integer NOT NULL,
created integer NOT NULL,
type text NOT NULL,
data text NOT NULL
)
`)
yield* db.run(sql`CREATE TABLE data_migration (name text PRIMARY KEY)`)
yield* db.run(sql`INSERT INTO project VALUES ('project')`)
yield* db.run(sql`INSERT INTO project_directory VALUES ('project', '/repo', 'main', NULL, 1)`)
yield* db.run(sql`INSERT INTO session VALUES ('session', 'project', NULL, NULL, NULL)`)
yield* db.run(sql`INSERT INTO session_message VALUES ('message', 'session', '{"text":"preserved"}')`)
yield* db.run(sql`INSERT INTO session_pending VALUES ('pending', 'session')`)
yield* db.run(sql`INSERT INTO event_sequence VALUES ('session', 41)`)
yield* db.run(sql`INSERT INTO event VALUES ('event', 'session', 41, 1, 'session.text.ended.1', '{}')`)
yield* DatabaseMigration.applyOnly(db, [
previousV2Migration,
workspaceMigration,
executionClaimsMigration,
sessionInboxMigration,
worktreeMigration,
])
expect(yield* db.get(sql`SELECT id, resume_attempts FROM session_v2`)).toEqual({
id: "session",
resume_attempts: 0,
})
expect(yield* db.get(sql`SELECT id, data FROM session_message`)).toEqual({
id: "message",
data: '{"text":"preserved"}',
})
expect(yield* db.get(sql`SELECT id FROM session_pending`)).toEqual({ id: "pending" })
expect(yield* db.get(sql`SELECT seq FROM event_sequence`)).toEqual({ seq: 41 })
expect(yield* db.get(sql`SELECT id, seq FROM event`)).toEqual({ id: "event", seq: 41 })
expect(
yield* db.get(sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'session'`),
).toBeUndefined()
expect(yield* db.get(sql`SELECT directory FROM worktree`)).toEqual({ directory: "/repo" })
expect(yield* db.all<{ table: string }>(sql`PRAGMA foreign_key_list(session_message)`)).toContainEqual(
expect.objectContaining({ table: "session_v2" }),
)
expect(yield* db.all<{ table: string }>(sql`PRAGMA foreign_key_list(session_pending)`)).toContainEqual(
expect.objectContaining({ table: "session_v2" }),
)
}),
)
})
test("rejects previous V2 databases with V1-only session history", async () => {
await run(
Effect.gen(function* () {
const db = yield* makeDb
yield* db.run(sql`CREATE TABLE migration (id text PRIMARY KEY, time_completed integer NOT NULL)`)
yield* db.run(sql`
INSERT INTO migration (id, time_completed)
VALUES ('20260730195856_optional_session_title', 1)
`)
yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY)`)
yield* db.run(sql`CREATE TABLE session_message (id text PRIMARY KEY, session_id text NOT NULL)`)
yield* db.run(sql`CREATE TABLE message (id text PRIMARY KEY, session_id text NOT NULL)`)
yield* db.run(sql`INSERT INTO session VALUES ('session')`)
yield* db.run(sql`INSERT INTO message VALUES ('message', 'session')`)
expect((yield* Effect.exit(DatabaseMigration.applyOnly(db, [previousV2Migration])))._tag).toBe("Failure")
expect(yield* db.get(sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'session'`)).toEqual({
name: "session",
})
expect(yield* db.get(sql`SELECT id FROM migration WHERE id = ${previousV2Migration.id}`)).toBeUndefined()
}),
)
})
test("copies project directories into worktrees without removing the old table", async () => {
await run(
Effect.gen(function* () {
@@ -51,7 +51,7 @@ describe("InstructionBuiltIns", () => {
` Workspace root folder: ${projectDirectory}`,
" Is directory a git repo: yes",
` Platform: ${process.platform}`,
` Prefer ${temporary} over generic system temporary directories such as /tmp; it is pre-created and approved for external access.`,
` Use ${temporary} for temporary work outside the workspace; it already exists and is pre-approved for external directory access.`,
"</env>",
"",
`Today's date: ${localDate(timestamp)}`,
-15
View File
@@ -415,21 +415,6 @@ describe("ModelResolver", () => {
}),
)
it.effect("rejects OpenAI-compatible packages without an endpoint instead of loading the AI SDK", () =>
Effect.gen(function* () {
const failure = yield* ModelResolver.fromCatalogModel(
model(Provider.aisdk("@ai-sdk/openai-compatible")),
undefined,
{ loadAISDK: () => Effect.die("AI SDK loader should not be called") },
).pipe(Effect.flip)
expect(failure).toMatchObject({
_tag: "SessionRunnerModel.UnsupportedPackageError",
package: "aisdk:@ai-sdk/openai-compatible",
})
}),
)
it.effect("resolves provider URLs from environment without mutating the catalog model", () =>
withEnv({ ACME_HOST: "api.acme.test" }, () =>
Effect.gen(function* () {
@@ -1,6 +1,9 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { AmazonBedrockPlugin } from "@opencode-ai/core/plugin/provider/amazon-bedrock"
@@ -12,6 +15,7 @@ const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* AmazonBedrockPlugin.effect(host)
})
@@ -21,6 +25,59 @@ function required<T>(value: T | undefined): T {
return value
}
function withEnv<A, E, R>(vars: Record<string, string | undefined>, fx: () => Effect.Effect<A, E, R>) {
return Effect.acquireUseRelease(
Effect.sync(() => {
const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]]))
Object.entries(vars).forEach(([key, value]) => {
if (value === undefined) delete process.env[key]
else process.env[key] = value
})
return previous
}),
fx,
(previous) =>
Effect.sync(() => {
Object.entries(previous).forEach(([key, value]) => {
if (value === undefined) delete process.env[key]
else process.env[key] = value
})
}),
)
}
function fakeSelectorSdk(calls: string[]) {
const make = (method: string) => (id: string) => {
calls.push(`${method}:${id}`)
return { modelId: id, provider: method, specificationVersion: "v3" } as unknown as LanguageModelV3
}
return {
responses: make("responses"),
messages: make("messages"),
chat: make("chat"),
languageModel: make("languageModel"),
}
}
function bedrockBaseURL(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") {
const language = (sdk as { languageModel: (id: string) => unknown }).languageModel(modelID)
return (language as { config: { baseUrl: () => string } }).config.baseUrl()
}
function bedrockFetch(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") {
const language = (sdk as { languageModel: (id: string) => unknown }).languageModel(modelID)
return (
language as { config: { fetch: (input: Parameters<typeof fetch>[0], init?: RequestInit) => Promise<Response> } }
).config.fetch
}
function openAIUrl(language: unknown, path: string, modelId: string) {
return (language as { config: { url: (input: { path: string; modelId: string }) => string } }).config.url({
path,
modelId,
})
}
describe("AmazonBedrockPlugin", () => {
it.effect("moves endpoint setting to baseURL", () =>
Effect.gen(function* () {
@@ -42,4 +99,510 @@ describe("AmazonBedrockPlugin", () => {
expect(result.settings).toEqual({ baseURL: "https://bedrock.example" })
}),
)
it.effect("prefers endpoint over baseURL for SDK base URL", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
name: "amazon-bedrock",
bearerToken: "token",
baseURL: "https://base.example",
endpoint: "https://endpoint.example",
region: "us-east-1",
},
})
expect(bedrockBaseURL(result.sdk)).toBe("https://endpoint.example")
}),
),
)
it.effect("uses baseURL as SDK base URL", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
name: "amazon-bedrock",
bearerToken: "token",
baseURL: "https://base.example",
region: "us-east-1",
},
})
expect(bedrockBaseURL(result.sdk)).toBe("https://base.example")
}),
),
)
it.effect("creates SDK without explicit credential env so the default AWS chain can resolve credentials", () =>
withEnv(
{
AWS_ACCESS_KEY_ID: undefined,
AWS_BEARER_TOKEN_BEDROCK: undefined,
AWS_CONTAINER_CREDENTIALS_FULL_URI: undefined,
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: undefined,
AWS_PROFILE: undefined,
AWS_REGION: undefined,
AWS_WEB_IDENTITY_TOKEN_FILE: undefined,
},
() =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: { name: "amazon-bedrock" },
})
expect(result.sdk).toBeDefined()
expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.us-east-1.amazonaws.com")
}),
),
)
it.effect("uses config region over AWS_REGION for SDK base URL", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: "us-east-1" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: { name: "amazon-bedrock", region: "eu-west-1" },
})
expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.eu-west-1.amazonaws.com")
}),
),
)
it.effect("uses AWS_REGION for SDK base URL when config region is absent", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: "eu-west-1" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: { name: "amazon-bedrock" },
})
expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.eu-west-1.amazonaws.com")
}),
),
)
it.effect("defaults SDK region to us-east-1", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: { name: "amazon-bedrock" },
})
expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.us-east-1.amazonaws.com")
}),
),
)
it.effect("loads bearer token option into env and uses bearer auth", () =>
withEnv({ AWS_ACCESS_KEY_ID: undefined, AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
name: "amazon-bedrock",
bearerToken: "option-token",
fetch: async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
headers.push(new Headers(init?.headers).get("Authorization"))
return new Response("{}")
},
},
})
yield* Effect.promise(() => bedrockFetch(result.sdk)("https://bedrock.example", { method: "POST" }))
expect(process.env.AWS_BEARER_TOKEN_BEDROCK).toBe("option-token")
expect(headers).toEqual(["Bearer option-token"])
}),
),
)
it.effect("prefers bearer token env over bearer token option", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: "env-token" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
name: "amazon-bedrock",
bearerToken: "option-token",
fetch: async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
headers.push(new Headers(init?.headers).get("Authorization"))
return new Response("{}")
},
},
})
yield* Effect.promise(() => bedrockFetch(result.sdk)("https://bedrock.example", { method: "POST" }))
expect(process.env.AWS_BEARER_TOKEN_BEDROCK).toBe("env-token")
expect(headers).toEqual(["Bearer env-token"])
}),
),
)
it.effect("creates Mantle SDK with GPT-5 OpenAI base path", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("openai.gpt-5.5")),
modelID: Model.ID.make("openai.gpt-5.5"),
package: Provider.aisdk("@ai-sdk/amazon-bedrock/mantle"),
}),
package: "@ai-sdk/amazon-bedrock/mantle",
options: {
name: "amazon-bedrock",
bearerToken: "token",
baseURL: "https://bedrock-mantle.us-east-2.api.aws/openai/v1",
region: "us-east-2",
},
})
const language = result.sdk.responses("openai.gpt-5.5")
expect(openAIUrl(language, "/responses", "openai.gpt-5.5")).toBe(
"https://bedrock-mantle.us-east-2.api.aws/openai/v1/responses",
)
}),
),
)
it.effect("selects Mantle APIs without Bedrock cross-region prefixes", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("openai.gpt-5.5")),
modelID: Model.ID.make("openai.gpt-5.5"),
package: Provider.aisdk("@ai-sdk/amazon-bedrock/mantle"),
}),
sdk: fakeSelectorSdk(calls),
options: { baseURL: "https://bedrock-mantle.us-east-2.api.aws/openai/v1", region: "us-east-2" },
})
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("openai.gpt-oss-safeguard-120b")),
modelID: Model.ID.make("openai.gpt-oss-safeguard-120b"),
package: Provider.aisdk("@ai-sdk/amazon-bedrock/mantle"),
}),
sdk: fakeSelectorSdk(calls),
options: { region: "us-east-1" },
})
expect(calls).toEqual(["responses:openai.gpt-5.5", "chat:openai.gpt-oss-safeguard-120b"])
}),
)
it.effect("ignores other Bedrock provider subpaths", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("@ai-sdk/amazon-bedrock/anthropic"),
}),
package: "@ai-sdk/amazon-bedrock/anthropic",
options: { name: "amazon-bedrock" },
})
expect(result.sdk).toBeUndefined()
}),
)
it.effect("uses SigV4 credential env when bearer token is absent", () =>
withEnv(
{
AWS_ACCESS_KEY_ID: "test-access-key",
AWS_BEARER_TOKEN_BEDROCK: undefined,
AWS_REGION: "us-east-1",
AWS_SECRET_ACCESS_KEY: "test-secret-key",
AWS_SESSION_TOKEN: "test-session-token",
},
() =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
name: "amazon-bedrock",
fetch: async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
headers.push(new Headers(init?.headers).get("Authorization"))
return new Response("{}")
},
},
})
yield* Effect.promise(() =>
bedrockFetch(result.sdk)("https://bedrock-runtime.us-east-1.amazonaws.com/model/test/invoke", {
body: "{}",
method: "POST",
}),
)
expect(headers[0]?.startsWith("AWS4-HMAC-SHA256 ")).toBe(true)
}),
),
)
it.effect("applies legacy cross-region inference prefixes", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: {},
})
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "eu-west-1" },
})
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("global.anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("global.anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "eu-west-1" },
})
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "ap-northeast-1" },
})
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "ap-southeast-2" },
})
expect(calls).toEqual([
"languageModel:us.anthropic.claude-sonnet-4-5",
"languageModel:eu.anthropic.claude-sonnet-4-5",
"languageModel:global.anthropic.claude-sonnet-4-5",
"languageModel:jp.anthropic.claude-sonnet-4-5",
"languageModel:au.anthropic.claude-sonnet-4-5",
])
}),
)
it.effect("uses AWS_REGION for language prefixes when region option is absent", () =>
withEnv({ AWS_REGION: "eu-west-1" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: {},
})
expect(calls).toEqual(["languageModel:eu.anthropic.claude-sonnet-4-5"])
}),
),
)
it.effect("applies the full legacy cross-region prefix matrix", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
const cases = [
{ region: "us-east-1", modelID: "amazon.nova-micro-v1:0", expected: "us.amazon.nova-micro-v1:0" },
{ region: "us-east-1", modelID: "amazon.nova-lite-v1:0", expected: "us.amazon.nova-lite-v1:0" },
{ region: "us-east-1", modelID: "amazon.nova-pro-v1:0", expected: "us.amazon.nova-pro-v1:0" },
{ region: "us-east-1", modelID: "amazon.nova-premier-v1:0", expected: "us.amazon.nova-premier-v1:0" },
{ region: "us-east-1", modelID: "amazon.nova-2-lite-v1:0", expected: "us.amazon.nova-2-lite-v1:0" },
{ region: "us-east-1", modelID: "anthropic.claude-sonnet-4-5", expected: "us.anthropic.claude-sonnet-4-5" },
{ region: "us-east-1", modelID: "deepseek.r1-v1:0", expected: "us.deepseek.r1-v1:0" },
{ region: "us-gov-west-1", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" },
{ region: "us-east-1", modelID: "cohere.command-r-plus-v1:0", expected: "cohere.command-r-plus-v1:0" },
{ region: "eu-west-1", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" },
{ region: "eu-west-2", modelID: "amazon.nova-lite-v1:0", expected: "eu.amazon.nova-lite-v1:0" },
{ region: "eu-west-3", modelID: "amazon.nova-micro-v1:0", expected: "eu.amazon.nova-micro-v1:0" },
{
region: "eu-north-1",
modelID: "meta.llama3-70b-instruct-v1:0",
expected: "eu.meta.llama3-70b-instruct-v1:0",
},
{ region: "eu-central-1", modelID: "mistral.pixtral-large-v1:0", expected: "eu.mistral.pixtral-large-v1:0" },
{ region: "eu-south-1", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" },
{ region: "eu-south-2", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" },
{ region: "eu-central-2", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" },
{ region: "eu-west-1", modelID: "cohere.command-r-plus-v1:0", expected: "cohere.command-r-plus-v1:0" },
{
region: "ap-southeast-2",
modelID: "anthropic.claude-sonnet-4-5",
expected: "au.anthropic.claude-sonnet-4-5",
},
{
region: "ap-southeast-4",
modelID: "anthropic.claude-haiku-v1:0",
expected: "au.anthropic.claude-haiku-v1:0",
},
{ region: "ap-southeast-2", modelID: "anthropic.claude-opus-4", expected: "apac.anthropic.claude-opus-4" },
{
region: "ap-northeast-1",
modelID: "anthropic.claude-sonnet-4-5",
expected: "jp.anthropic.claude-sonnet-4-5",
},
{ region: "ap-northeast-1", modelID: "amazon.nova-pro-v1:0", expected: "jp.amazon.nova-pro-v1:0" },
{ region: "ap-south-1", modelID: "anthropic.claude-sonnet-4-5", expected: "apac.anthropic.claude-sonnet-4-5" },
{ region: "ap-south-1", modelID: "amazon.nova-lite-v1:0", expected: "apac.amazon.nova-lite-v1:0" },
{ region: "ca-central-1", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" },
{
region: "us-east-1",
modelID: "global.anthropic.claude-sonnet-4-5",
expected: "global.anthropic.claude-sonnet-4-5",
},
{ region: "us-east-1", modelID: "us.anthropic.claude-sonnet-4-5", expected: "us.anthropic.claude-sonnet-4-5" },
{ region: "eu-west-1", modelID: "eu.anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" },
{
region: "ap-northeast-1",
modelID: "jp.anthropic.claude-sonnet-4-5",
expected: "jp.anthropic.claude-sonnet-4-5",
},
{
region: "ap-south-1",
modelID: "apac.anthropic.claude-sonnet-4-5",
expected: "apac.anthropic.claude-sonnet-4-5",
},
{
region: "ap-southeast-2",
modelID: "au.anthropic.claude-sonnet-4-5",
expected: "au.anthropic.claude-sonnet-4-5",
},
]
yield* addPlugin()
for (const item of cases) {
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make(item.modelID)),
modelID: Model.ID.make(item.modelID),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: item.region },
})
}
expect(calls).toEqual(cases.map((item) => `languageModel:${item.expected}`))
}),
)
it.effect("ignores non-Bedrock providers for language selection", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.openai, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "eu-west-1" },
})
expect(calls).toEqual([])
expect(result.language).toBeUndefined()
}),
)
})
@@ -1,6 +1,8 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { AnthropicPlugin } from "@opencode-ai/core/plugin/provider/anthropic"
@@ -12,6 +14,7 @@ const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* AnthropicPlugin.effect(host)
})
@@ -52,4 +55,40 @@ describe("AnthropicPlugin", () => {
expect(required(yield* catalog.provider.get(Provider.ID.openai)).headers?.["anthropic-beta"]).toBeUndefined()
}),
)
it.effect("creates Anthropic SDKs with the model provider ID as the SDK name", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-anthropic"), Model.ID.make("claude-sonnet-4-5")),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: Provider.aisdk("@ai-sdk/anthropic"),
}),
package: "@ai-sdk/anthropic",
options: { name: "custom-anthropic", apiKey: "test" },
})
expect(result.sdk.languageModel("claude-sonnet-4-5").provider).toBe("custom-anthropic")
}),
)
it.effect("uses the Anthropic provider ID as the SDK name for the bundled Anthropic provider", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.anthropic, Model.ID.make("claude-sonnet-4-5")),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: Provider.aisdk("@ai-sdk/anthropic"),
}),
package: "@ai-sdk/anthropic",
options: { name: "anthropic", apiKey: "test" },
})
expect(result.sdk.languageModel("claude-sonnet-4-5").provider).toBe("anthropic")
}),
)
})
@@ -1,4 +1,6 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Model } from "@opencode-ai/core/model"
@@ -14,6 +16,7 @@ const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* AzurePlugin.effect(host)
})
@@ -44,6 +47,19 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, fx: () => Ef
)
}
function fakeSelectorSdk(calls: string[]) {
const make = (method: string) => (id: string) => {
calls.push(`${method}:${id}`)
return { modelId: id, provider: method, specificationVersion: "v3" } as unknown as LanguageModelV3
}
return {
responses: make("responses"),
messages: make("messages"),
chat: make("chat"),
languageModel: make("languageModel"),
}
}
describe("AzurePlugin", () => {
it.effect("registers a resource name form when the environment does not provide one", () =>
withEnv({ AZURE_RESOURCE_NAME: undefined, AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: undefined }, () =>
@@ -199,6 +215,8 @@ describe("AzurePlugin", () => {
it.effect("allows configured baseURL without resourceName", () =>
withEnv({ AZURE_RESOURCE_NAME: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(Provider.ID.azure, (provider) => {
@@ -210,7 +228,157 @@ describe("AzurePlugin", () => {
type: "key",
label: "API key",
})
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/azure",
options: { name: "azure", baseURL: "https://proxy.example.com/openai" },
})
expect(result.sdk).toBeDefined()
}),
),
)
it.effect("rejects missing resourceName when baseURL is not configured", () =>
withEnv({ AZURE_RESOURCE_NAME: undefined }, () =>
Effect.gen(function* () {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const exit = yield* aisdk
.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/azure",
options: { name: "azure" },
})
.pipe(Effect.exit)
expect(exit._tag).toBe("Failure")
}),
),
)
it.effect("selects chat only for completion URLs", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: { useCompletionUrls: true },
})
expect(calls).toEqual(["chat:deployment"])
}),
)
it.effect("selects chat from per-call useCompletionUrls", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: { useCompletionUrls: true },
})
expect(calls).toEqual(["chat:deployment"])
}),
)
it.effect("ignores model useCompletionUrls when per-call option is unset", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
body: { useCompletionUrls: true },
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
expect(calls).toEqual(["responses:deployment"])
}),
)
it.effect("uses the legacy Azure selector order and provider guard", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
const ignored = yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.openai, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
expect(calls).toEqual(["responses:deployment"])
expect(ignored.language).toBeUndefined()
}),
)
it.effect("falls back through the legacy Azure selector order", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
const make = (method: string) => (id: string) => {
calls.push(`${method}:${id}`)
return { modelId: id, provider: method, specificationVersion: "v3" }
}
yield* addPlugin()
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("messages-deployment")),
modelID: Model.ID.make("messages-deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: { messages: make("messages"), chat: make("chat"), languageModel: make("languageModel") },
options: {},
})
yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("language-deployment")),
modelID: Model.ID.make("language-deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: make("languageModel") },
options: {},
})
expect(calls).toEqual(["messages:messages-deployment", "languageModel:language-deployment"])
}),
)
})
@@ -1,11 +1,14 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { CloudflareWorkersAIPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-workers-ai"
import { Provider } from "@opencode-ai/core/provider"
import { Integration } from "@opencode-ai/core/integration"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
@@ -13,6 +16,7 @@ const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* CloudflareWorkersAIPlugin.effect(host)
})
@@ -43,6 +47,38 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
)
}
function fakeSelectorSdk(calls: string[]) {
const make = (method: string) => (id: string) => {
calls.push(`${method}:${id}`)
return { modelId: id, provider: method, specificationVersion: "v3" } as unknown as LanguageModelV3
}
return {
responses: make("responses"),
messages: make("messages"),
chat: make("chat"),
languageModel: make("languageModel"),
}
}
function cloudflareLanguage(sdk: unknown, modelID = "@cf/model") {
return (sdk as { languageModel: (id: string) => { config: CloudflareConfig; provider: string } }).languageModel(
modelID,
)
}
type CloudflareConfig = {
url: (input: { path: string; modelId: string }) => string
headers: () => Record<string, string> | Promise<Record<string, string>>
}
function cloudflareURL(sdk: unknown, modelID = "@cf/model") {
return cloudflareLanguage(sdk, modelID).config.url({ path: "/chat/completions", modelId: modelID })
}
function cloudflareHeaders(sdk: unknown, modelID = "@cf/model") {
return cloudflareLanguage(sdk, modelID).config.headers()
}
describe("CloudflareWorkersAIPlugin", () => {
it.effect("registers an account form when the environment does not provide one", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: undefined }, () =>
@@ -67,9 +103,11 @@ describe("CloudflareWorkersAIPlugin", () => {
),
)
it.effect("maps account ID to endpoint URL", () =>
it.effect("maps account ID to endpoint URL and creates an OpenAI-compatible SDK", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(Provider.ID.make("cloudflare-workers-ai"), (provider) => {
@@ -81,10 +119,21 @@ describe("CloudflareWorkersAIPlugin", () => {
(yield* (yield* Integration.Service).get(Integration.ID.make("cloudflare-workers-ai")))?.methods,
).toContainEqual({ type: "key", label: "API key" })
const provider = required(yield* catalog.provider.get(Provider.ID.make("cloudflare-workers-ai")))
const sdk = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: provider.package,
settings: provider.settings,
}),
package: "@ai-sdk/openai-compatible",
options: { name: "cloudflare-workers-ai", headers: { custom: "header" } },
})
expect(provider).toMatchObject({
package: "aisdk:test-provider",
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1" },
})
expect(sdk.sdk).toBeDefined()
}),
),
)
@@ -107,4 +156,149 @@ describe("CloudflareWorkersAIPlugin", () => {
}),
),
)
it.effect("allows a configured baseURL without account ID", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: undefined, CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(Provider.ID.make("cloudflare-workers-ai"), (provider) => {
provider.settings = { ...provider.settings, baseURL: "https://proxy.example/v1" }
}),
)
yield* addPlugin()
expect(
(yield* (yield* Integration.Service).get(Integration.ID.make("cloudflare-workers-ai")))?.methods,
).toContainEqual({ type: "key", label: "API key" })
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: "aisdk:@ai-sdk/openai-compatible",
settings: { baseURL: "https://proxy.example/v1" },
}),
package: "@ai-sdk/openai-compatible",
options: { name: "cloudflare-workers-ai", baseURL: "https://proxy.example/v1" },
})
expect(cloudflareURL(result.sdk)).toBe("https://proxy.example/v1/chat/completions")
}),
),
)
it.effect("uses env account ID over configured account ID", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "env-acct" }, () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(Provider.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = Provider.aisdk("test-provider")
provider.settings = { ...provider.settings, accountId: "configured-acct" }
}),
)
yield* addPlugin()
expect(required(yield* catalog.provider.get(Provider.ID.make("cloudflare-workers-ai")))).toMatchObject({
package: "aisdk:test-provider",
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1" },
})
}),
),
)
it.effect("uses env API key over auth or configured API key and keeps the Cloudflare User-Agent", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "env-key" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: "aisdk:@ai-sdk/openai-compatible",
settings: { baseURL: "https://proxy.example/v1" },
}),
package: "@ai-sdk/openai-compatible",
options: {
name: "cloudflare-workers-ai",
apiKey: "auth-key",
baseURL: "https://proxy.example/v1",
headers: { custom: "header" },
},
})
const headers = yield* Effect.promise(() => Promise.resolve(cloudflareHeaders(result.sdk)))
expect(headers.authorization).toBe("Bearer env-key")
expect(headers.custom).toBe("header")
expect(headers["user-agent"]).toMatch(/^opencode\/.* cloudflare-workers-ai \(.+\) ai-sdk\/openai-compatible\//)
}),
),
)
it.effect("expands account ID vars in endpoint URLs", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: "aisdk:@ai-sdk/openai-compatible",
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1" },
}),
package: "@ai-sdk/openai-compatible",
options: {
name: "cloudflare-workers-ai",
baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
},
})
expect(cloudflareURL(result.sdk)).toBe(
"https://api.cloudflare.com/client/v4/accounts/acct/ai/v1/chat/completions",
)
}),
),
)
it.effect("selects languageModel with the API model ID", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("alias")),
modelID: Model.ID.make("@cf/api-model"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
expect(result.language).toBeDefined()
expect(calls).toEqual(["languageModel:@cf/api-model"])
}),
)
it.effect("does not create an SDK for non OpenAI-compatible packages", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: "aisdk:@ai-sdk/anthropic",
settings: { baseURL: "https://proxy.example/v1" },
}),
package: "@ai-sdk/anthropic",
options: { name: "cloudflare-workers-ai" },
})
expect(result.sdk).toBeUndefined()
}),
),
)
})
@@ -12,10 +12,12 @@ import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const vertexOptions: Record<string, any>[] = []
const googleAuthOptions: Record<string, any>[] = []
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* GoogleVertexPlugin.effect(host)
})
@@ -68,6 +70,22 @@ void mock.module("@ai-sdk/google-vertex", () => ({
},
}))
void mock.module("google-auth-library", () => ({
GoogleAuth: class {
constructor(options: Record<string, any>) {
googleAuthOptions.push(options)
}
async getClient() {
return {
async getAccessToken() {
return { token: "vertex-token" }
},
}
}
},
}))
describe("GoogleVertexPlugin", () => {
it.effect("ignores OpenAI-compatible providers that are not Google Vertex", () =>
Effect.gen(function* () {
@@ -123,7 +141,7 @@ describe("GoogleVertexPlugin", () => {
),
)
it.effect("resolves the advertised GOOGLE_VERTEX_PROJECT env", () =>
it.effect("resolves the advertised GOOGLE_VERTEX_PROJECT env for provider updates and SDKs", () =>
withEnv(
{
GOOGLE_VERTEX_PROJECT: "vertex-project",
@@ -137,6 +155,8 @@ describe("GoogleVertexPlugin", () => {
() =>
Effect.gen(function* () {
vertexOptions.length = 0
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(Provider.ID.make("google-vertex"), (provider) => {
@@ -150,6 +170,16 @@ describe("GoogleVertexPlugin", () => {
)
yield* addPlugin()
const provider = required(yield* catalog.provider.get(Provider.ID.make("google-vertex")))
yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("gemini")),
modelID: Model.ID.make("gemini"),
package: "aisdk:@ai-sdk/google-vertex",
}),
package: "@ai-sdk/google-vertex",
options: { name: "google-vertex" },
})
expect(provider.settings?.project).toBe("vertex-project")
expect(provider).toMatchObject({
package: "aisdk:@ai-sdk/openai-compatible",
@@ -158,6 +188,8 @@ describe("GoogleVertexPlugin", () => {
"https://europe-west4-aiplatform.googleapis.com/v1/projects/vertex-project/locations/europe-west4",
},
})
expect(vertexOptions[0].project).toBe("vertex-project")
expect(vertexOptions[0].location).toBe("europe-west4")
}),
),
)
@@ -257,6 +289,7 @@ describe("GoogleVertexPlugin", () => {
() =>
Effect.gen(function* () {
vertexOptions.length = 0
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
@@ -276,8 +309,80 @@ describe("GoogleVertexPlugin", () => {
),
)
it.effect("creates Anthropic SDKs for canonical Google Vertex models", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.googleVertex, Model.ID.make("claude-sonnet-4-6@default")),
modelID: Model.ID.make("claude-sonnet-4-6@default"),
package: "aisdk:@ai-sdk/google-vertex/anthropic",
}),
package: "@ai-sdk/google-vertex/anthropic",
options: { name: "google-vertex", project: "project", location: "eu" },
})
expect(result.sdk.languageModel("claude-sonnet-4-6@default").config.baseURL).toBe(
"https://aiplatform.eu.rep.googleapis.com/v1/projects/project/locations/eu/publishers/anthropic/models",
)
}),
)
it.effect("keeps Google auth fetch for OpenAI-compatible Vertex endpoints", () =>
Effect.gen(function* () {
googleAuthOptions.length = 0
const fetchCalls: { input: Parameters<typeof fetch>[0]; init?: RequestInit }[] = []
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.hook.sdk((evt) =>
Effect.promise(async () => {
if (evt.model.providerID !== "google-vertex") return
if (evt.package !== "@ai-sdk/openai-compatible") return
expect(typeof evt.options.fetch).toBe("function")
await evt.options.fetch("https://vertex.example", {
headers: { "x-test": "1" },
})
}),
)
const originalFetch = fetch
;(globalThis as typeof globalThis & { fetch: typeof fetch }).fetch = (async (
input: Parameters<typeof fetch>[0],
init?: RequestInit,
) => {
fetchCalls.push({ input, init })
return new Response("ok")
}) as typeof fetch
yield* Effect.acquireUseRelease(
Effect.void,
() =>
aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("gemini")),
modelID: Model.ID.make("gemini"),
package: "aisdk:@ai-sdk/openai-compatible",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "google-vertex" },
}),
() =>
Effect.sync(() => {
;(globalThis as typeof globalThis & { fetch: typeof fetch }).fetch = originalFetch
}),
)
const vertexCalls = fetchCalls.filter((call) => call.input === "https://vertex.example")
expect(vertexCalls).toHaveLength(1)
expect(googleAuthOptions).toEqual([{ scopes: ["https://www.googleapis.com/auth/cloud-platform"] }])
expect(new Headers(vertexCalls[0].init?.headers).get("authorization")).toBe("Bearer vertex-token")
expect(new Headers(vertexCalls[0].init?.headers).get("x-test")).toBe("1")
}),
)
it.effect("trims model IDs before selecting language models", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
@@ -60,7 +60,11 @@ describe("LMStudioPlugin", () => {
architecture: "gemma4",
loaded_instances: [{ config: { context_length: 32_768 } }, { config: { context_length: 16_384 } }],
max_context_length: 262_144,
capabilities: { vision: true, trained_for_tool_use: true },
capabilities: {
vision: true,
trained_for_tool_use: true,
reasoning: { allowed_options: ["off", "on"], default: "on" },
},
},
{
type: "llm",
@@ -105,6 +109,10 @@ describe("LMStudioPlugin", () => {
name: "Gemma 4 26B A4B",
capabilities: { tools: true, input: ["text", "image"], output: ["text"] },
limit: { context: 16_384, output: 0 },
variants: [
{ id: "none", settings: { reasoningEffort: "none" } },
{ id: "thinking", settings: { reasoningEffort: "medium" } },
],
})
expect(yield* catalog.model.get(providerID, Model.ID.make("deepseek-r1"))).toMatchObject({
capabilities: { tools: false, input: ["text"], output: ["text"] },
@@ -54,6 +54,7 @@ describe("OllamaPlugin", () => {
return Response.json({
models: [
summary("gemma3:4b", "gemma-digest", "gemma3"),
summary("gpt-oss:20b", "gpt-oss-digest", "gptoss"),
summary("nomic-embed", "embed-digest"),
summary("removed-model", "removed-digest"),
],
@@ -65,10 +66,12 @@ describe("OllamaPlugin", () => {
return Response.json(
body.model === "gemma3:4b"
? {
capabilities: ["completion", "tools", "vision"],
capabilities: ["completion", "tools", "vision", "thinking"],
model_info: { "gemma3.context_length": 131_072 },
}
: show({ family: "nomic-bert", capabilities: ["embedding"], context: 8192 }),
: body.model === "gpt-oss:20b"
? show({ family: "gptoss", capabilities: ["completion", "thinking"], context: 131_072 })
: show({ family: "nomic-bert", capabilities: ["embedding"], context: 8192 }),
)
},
}),
@@ -99,6 +102,17 @@ describe("OllamaPlugin", () => {
family: "gemma3",
capabilities: { tools: true, input: ["text", "image"], output: ["text"] },
limit: { context: 131_072, output: 0 },
variants: [
{ id: "none", settings: { reasoningEffort: "none" } },
{ id: "thinking", settings: { reasoningEffort: "medium" } },
],
})
expect(yield* catalog.model.get(providerID, Model.ID.make("gpt-oss:20b"))).toMatchObject({
variants: [
{ id: "low", settings: { reasoningEffort: "low" } },
{ id: "medium", settings: { reasoningEffort: "medium" } },
{ id: "high", settings: { reasoningEffort: "high" } },
],
})
expect(yield* catalog.model.get(providerID, Model.ID.make("nomic-embed"))).toBeUndefined()
expect(requests).toContainEqual({ method: "GET", path: "/api/tags" })
@@ -0,0 +1,112 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { OpenAICompatiblePlugin } from "@opencode-ai/core/plugin/provider/openai-compatible"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* OpenAICompatiblePlugin.effect(host)
})
describe("OpenAICompatiblePlugin", () => {
it.effect("preserves explicit includeUsage false and defaults it to true", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const defaulted = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "custom" },
})
const disabled = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "custom", includeUsage: false },
})
expect(defaulted.options.includeUsage).toBe(true)
expect(disabled.options.includeUsage).toBe(false)
}),
)
it.effect("defaults includeUsage for OpenAI-compatible package matches", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "file:///tmp/@ai-sdk/openai-compatible-provider.js",
options: { name: "custom" },
})
expect(result.options.includeUsage).toBe(true)
}),
)
it.effect("uses the provider ID as the OpenAI-compatible provider name", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const observed: string[] = []
yield* addPlugin()
yield* aisdk.hook.sdk((event) =>
Effect.sync(() => {
observed.push(event.sdk.languageModel("model").provider)
}),
)
yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-provider"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "custom-provider", baseURL: "https://example.com/v1" },
})
expect(observed).toEqual(["custom-provider.chat"])
}),
)
it.effect("does not overwrite an SDK created by an earlier provider-specific plugin", () =>
Effect.gen(function* () {
const aisdk = yield* AISDK.Service
const sentinel = { languageModel: (modelID: string) => ({ modelID }) }
yield* aisdk.hook.sdk((event) => {
event.sdk = sentinel
})
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "cloudflare-workers-ai" },
})
expect(result.sdk).toBe(sentinel)
}),
)
})
@@ -0,0 +1,269 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect, it as bun_it } from "bun:test"
import { Effect } from "effect"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { SnowflakeCortexPlugin, cortexFetch } from "@opencode-ai/core/plugin/provider/snowflake-cortex"
import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* SnowflakeCortexPlugin.effect(host)
})
function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () => Effect.Effect<A, E, R>) {
return Effect.acquireUseRelease(
Effect.sync(() => {
const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]]))
Object.entries(vars).forEach(([key, value]) => {
if (value === undefined) delete process.env[key]
else process.env[key] = value
})
return previous
}),
effect,
(previous) =>
Effect.sync(() => {
Object.entries(previous).forEach(([key, value]) => {
if (value === undefined) delete process.env[key]
else process.env[key] = value
})
}),
)
}
describe("SnowflakeCortexPlugin", () => {
it.effect("is registered in ProviderPlugins before OpenAICompatiblePlugin", () =>
Effect.sync(() => {
expect(ProviderPlugins.map((item) => item.id)).toContain("opencode.provider.snowflake-cortex")
const ids = ProviderPlugins.map((p) => p.id)
expect(ids.indexOf("opencode.provider.snowflake-cortex")).toBeLessThan(
ids.indexOf("opencode.provider.openai-compatible"),
)
}),
)
it.effect("ignores non-snowflake-cortex providers", () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-4")),
modelID: Model.ID.make("gpt-4"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai",
options: { name: "openai" },
})
expect(result.sdk).toBeUndefined()
}),
)
it.effect("creates SDK for snowflake-cortex using SNOWFLAKE_CORTEX_PAT env var", () =>
withEnv({ SNOWFLAKE_CORTEX_PAT: "test-pat" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" },
})
expect(result.sdk).toBeDefined()
}),
),
)
it.effect("falls back to options.apiKey when SNOWFLAKE_CORTEX_PAT env var is absent", () =>
withEnv({ SNOWFLAKE_CORTEX_PAT: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: {
name: "snowflake-cortex",
baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1",
apiKey: "options-pat",
},
})
expect(result.sdk).toBeDefined()
}),
),
)
it.effect("uses SNOWFLAKE_CORTEX_TOKEN env var", () =>
withEnv({ SNOWFLAKE_CORTEX_TOKEN: "oauth-token", SNOWFLAKE_CORTEX_PAT: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" },
})
expect(result.sdk).toBeDefined()
}),
),
)
it.effect("falls back to options.token when no Snowflake env token is set", () =>
withEnv({ SNOWFLAKE_CORTEX_TOKEN: undefined, SNOWFLAKE_CORTEX_PAT: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: {
name: "snowflake-cortex",
baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1",
token: "options-token",
},
})
expect(result.sdk).toBeDefined()
}),
),
)
it.effect("sets includeUsage on the SDK options", () =>
withEnv({ SNOWFLAKE_CORTEX_PAT: "test-pat" }, () =>
Effect.gen(function* () {
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" },
})
expect(result.options.includeUsage).toBe(true)
}),
),
)
})
type FetchLike = (url: string | URL | Request, init?: RequestInit) => Promise<Response>
describe("cortexFetch", () => {
bun_it("rewrites max_tokens to max_completion_tokens", async () => {
const captured: RequestInit[] = []
const upstream: FetchLike = async (_url, init) => {
captured.push(init ?? {})
return new Response("{}", { status: 200 })
}
await cortexFetch(upstream)("https://test", {
method: "POST",
body: JSON.stringify({ model: "claude-sonnet-4-6", max_tokens: 1024 }),
})
const body = JSON.parse(captured[0].body as string)
expect(body.max_completion_tokens).toBe(1024)
expect(body.max_tokens).toBeUndefined()
})
bun_it("preserves body when max_tokens is absent", async () => {
const captured: RequestInit[] = []
const upstream: FetchLike = async (_url, init) => {
captured.push(init ?? {})
return new Response("{}", { status: 200 })
}
const original = JSON.stringify({ model: "claude-sonnet-4-6", temperature: 0.7 })
await cortexFetch(upstream)("https://test", { method: "POST", body: original })
expect(captured[0].body).toBe(original)
})
bun_it("treats 400 'conversation complete' as a stop response", async () => {
const upstream: FetchLike = async () =>
new Response(JSON.stringify({ message: "Conversation complete" }), {
status: 400,
headers: { "content-type": "application/json" },
})
const response = await cortexFetch(upstream)("https://test", {})
expect(response.status).toBe(200)
const data = (await response.json()) as { choices: { finish_reason: string }[] }
expect(data.choices[0].finish_reason).toBe("stop")
})
bun_it("passes through other 400 errors unchanged", async () => {
const upstream: FetchLike = async () =>
new Response(JSON.stringify({ message: "Invalid model" }), {
status: 400,
headers: { "content-type": "application/json" },
})
const response = await cortexFetch(upstream)("https://test", {})
expect(response.status).toBe(400)
})
bun_it("passes through non-400 errors unchanged", async () => {
const upstream: FetchLike = async () => new Response("Unauthorized", { status: 401 })
const response = await cortexFetch(upstream)("https://test", {})
expect(response.status).toBe(401)
})
bun_it("handles invalid JSON body gracefully without throwing", async () => {
const captured: RequestInit[] = []
const upstream: FetchLike = async (_url, init) => {
captured.push(init ?? {})
return new Response("{}", { status: 200 })
}
const invalidBody = "{ not json }"
await cortexFetch(upstream)("https://test", { method: "POST", body: invalidBody })
expect(captured[0].body).toBe(invalidBody)
})
bun_it("rewrites role:'' to role:'assistant' in streaming SSE chunks", async () => {
const chunk = `data: {"choices":[{"delta":{"role":"","content":"Hi"},"index":0}]}\n\n`
const upstream: FetchLike = async () =>
new Response(
new ReadableStream({
start: (ctrl) => {
ctrl.enqueue(new TextEncoder().encode(chunk))
ctrl.close()
},
}),
{
status: 200,
headers: { "content-type": "text/event-stream" },
},
)
const response = await cortexFetch(upstream)("https://test", {})
const text = await response.text()
expect(text).toContain('"role":"assistant"')
expect(text).not.toContain('"role":""')
})
})
@@ -63,7 +63,10 @@ describe("VLLMPlugin", () => {
state.models++
return Response.json({
object: "list",
data: [remoteModel("Qwen/Qwen3-Coder", 65_536), remoteModel("foreign-model", 4096, "other")],
data: [
remoteModel("deepseek-ai/DeepSeek-V4-Flash", 65_536),
remoteModel("foreign-model", 4096, "other"),
],
})
},
}),
@@ -82,7 +85,7 @@ describe("VLLMPlugin", () => {
state.healthy = true
const model = yield* eventually(
catalog.model.get(providerID, Model.ID.make("Qwen/Qwen3-Coder")),
catalog.model.get(providerID, Model.ID.make("deepseek-ai/DeepSeek-V4-Flash")),
(item) => item !== undefined,
)
expect(yield* catalog.provider.get(providerID)).toEqual({
@@ -94,10 +97,15 @@ describe("VLLMPlugin", () => {
})
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toContain(providerID)
expect(model).toMatchObject({
modelID: "Qwen/Qwen3-Coder",
name: "Qwen/Qwen3-Coder",
modelID: "deepseek-ai/DeepSeek-V4-Flash",
name: "deepseek-ai/DeepSeek-V4-Flash",
capabilities: { tools: false, input: ["text"], output: ["text"] },
limit: { context: 65_536, output: 0 },
variants: [
{ id: "none", settings: { reasoningEffort: "none" } },
{ id: "high", settings: { reasoningEffort: "high" } },
{ id: "max", settings: { reasoningEffort: "max" } },
],
})
expect(yield* catalog.model.get(providerID, Model.ID.make("foreign-model"))).toBeUndefined()
}),
-34
View File
@@ -782,40 +782,6 @@ describe("ShellTool", () => {
),
)
if (!isWindows) {
it.live("settles a shell terminated by an external signal", () =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
(tmp) => {
reset()
return withSession(tmp.path, (registry) =>
Effect.gen(function* () {
const shell = yield* Shell.Service
const settled = yield* executeTool(
registry,
call({ command: idleCommand, background: true }, "call-external-signal"),
)
const shellID = settled.metadata?.shellID
expect(typeof shellID).toBe("string")
if (typeof shellID !== "string") return
const id = ShellSchema.ID.make(shellID)
const info = yield* shell.get(id)
expect(typeof info.pid).toBe("number")
if (info.pid === undefined) return
process.kill(-info.pid, "SIGTERM")
const result = yield* shell.wait(id).pipe(Effect.timeoutOption(Duration.seconds(1)))
expect(result._tag).toBe("Some")
if (result._tag === "Some") expect(result.value.status).toBe("exited")
expect((yield* shell.list()).map((item) => item.id)).not.toContain(id)
}),
)
},
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
),
)
}
it.live("backgrounds a foreground command when the session is signaled", () =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
@@ -452,6 +452,7 @@ export function Prompt(props: PromptProps) {
title: "Queue prompt",
name: "prompt.queue",
category: "Prompt",
palette: undefined,
run: async (_input: string | undefined, event?: KeyEvent) => {
event?.preventDefault()
event?.stopPropagation()
+12 -8
View File
@@ -23,7 +23,7 @@ import {
NEW_SESSION_TAB_TITLE,
sessionTabComplete,
sessionTabDetail,
sessionTabNumberLabel,
sessionTabShortcutLabel,
seedSessionTabMotion,
sessionTabOverflowWidth,
type SessionTab,
@@ -426,7 +426,7 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
const value = session()
return value ? data.project.get(value.projectID) : undefined
})
const numberWidth = () => Math.max(2, String(items().length).length)
const numberWidth = () => 2
const restingTitleWidth = () => Math.max(1, width() - numberWidth() - 2)
const hoveredTitleWidth = () => Math.max(1, restingTitleWidth() - 1)
const titleWidth = () => (hovered() === tab.sessionID ? hoveredTitleWidth() : restingTitleWidth())
@@ -657,14 +657,14 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
backgroundColor={pulseBackground()}
onLevel={setSweepLevel}
/>
<box zIndex={1} width="100%" flexDirection="row" paddingRight={1}>
<box zIndex={1} width="100%" flexDirection="row" paddingLeft={1} paddingRight={1}>
<text
width={numberWidth() + 1}
width={numberWidth()}
fg={numberColor()}
selectable={false}
attributes={selected() ? TextAttributes.BOLD : undefined}
>
{sessionTabNumberLabel(index()).padStart(numberWidth())}
{sessionTabShortcutLabel(index())}
</text>
<text
width={titleWidth()}
@@ -1040,7 +1040,8 @@ function HorizontalSessionTabs(props: { controller?: SessionTabsController; anim
const glows = () => !selected() && (status().attention || (!status().busy && status().unread !== undefined))
const title = () => tab.title ?? "Untitled session"
const tabNumber = createMemo(() => items().findIndex((item) => item.sessionID === tab.sessionID) + 1)
const numberWidth = () => Math.max(2, String(items().length).length)
// Shortcut labels stay one cell wide: 1-9, 0 for ten, then a neutral dot.
const numberWidth = () => 2
// Hovering reveals the close mark, so the title's right bound shifts left of it.
const restingTitleWidth = () => Math.max(1, width() - 1 - numberWidth())
const hoveredTitleWidth = () => Math.max(1, restingTitleWidth() - 2)
@@ -1140,8 +1141,11 @@ function HorizontalSessionTabs(props: { controller?: SessionTabsController; anim
onLevel={setSweepLevel}
/>
<box zIndex={1} width="100%" flexDirection="row">
<text width={numberWidth() + 1} fg={numberColor()} selectable={false} attributes={bold()}>
{(tab === NEW_SESSION_TAB ? "+" : sessionTabNumberLabel(tabNumber() - 1)).padStart(numberWidth())}
<text width={1} selectable={false}>
{" "}
</text>
<text width={numberWidth()} fg={numberColor()} selectable={false} attributes={bold()}>
{tab === NEW_SESSION_TAB ? "+" : sessionTabShortcutLabel(tabNumber() - 1)}
</text>
<text
width={availableTitleWidth()}
+1 -1
View File
@@ -178,7 +178,7 @@ export const Definitions = {
"session.toggle.thinking": keybind("none", "Toggle thinking blocks visibility"),
"prompt.submit": keybind("none", "Submit prompt"),
"prompt.queue": keybind("<leader>return", "Queue prompt"),
"prompt.queue": keybind("alt+return", "Queue prompt"),
"prompt.editor_context.clear": keybind("none", "Clear editor context"),
"prompt.images.view": keybind("<leader>i", "View image attachments"),
"prompt.skills": keybind("none", "Open skill selector"),
+1 -1
View File
@@ -163,7 +163,7 @@ export const Definitions = {
display_thinking: keybind("none", "Toggle thinking blocks visibility"),
prompt_submit: keybind("none", "Submit prompt"),
prompt_queue: keybind("<leader>return", "Queue prompt"),
prompt_queue: keybind("alt+return", "Queue prompt"),
prompt_editor_context_clear: keybind("none", "Clear editor context"),
prompt_images_view: keybind("<leader>i", "View image attachments"),
prompt_skills: keybind("none", "Open skill selector"),
@@ -7,8 +7,10 @@ export type SessionTabUnread = "activity" | "error"
export const NEW_SESSION_TAB_TITLE = "New session"
export function sessionTabNumberLabel(index: number) {
return String(index + 1)
export function sessionTabShortcutLabel(index: number) {
if (index >= 0 && index < 9) return String(index + 1)
if (index === 9) return "0"
return "·"
}
export function sessionTabDetail(
-1
View File
@@ -1050,7 +1050,6 @@ export function createPromptState(input: PromptInput): PromptState {
id: "prompt.queue",
title: "Queue prompt",
group: "Prompt",
palette: true,
run() {
syncDraft()
submitPrompt(promptCopy(draft), "queue")
+1 -1
View File
@@ -595,7 +595,7 @@ export function RunFooterView(props: RunFooterViewProps) {
{
id: "session.queued_prompts",
title: "View queued prompts",
group: "Prompt",
group: "Session",
run: openQueuedMenu,
},
],
+1 -1
View File
@@ -1062,7 +1062,7 @@ export function Session(props: { verticalTabsWidth: number }) {
{
title: "View queued prompts",
id: "session.queued_prompts",
group: "Prompt",
group: "Session",
enabled: queuedPrompts().length > 0,
run: openQueuedPrompts,
},
-1
View File
@@ -107,7 +107,6 @@ test("preserves migrated v1 keybind defaults", () => {
const pairs = [
["app.exit", "app_exit"],
["prompt.paste", "input_paste"],
["prompt.queue", "prompt_queue"],
["session.delete", "session_delete"],
["session.list", "session_list"],
["agent.list", "agent_list"],
@@ -13,7 +13,7 @@ import {
sessionTabComplete,
sessionTabDetail,
sessionTabOverflowWidth,
sessionTabNumberLabel,
sessionTabShortcutLabel,
} from "../../src/context/session-tabs-model"
describe("session tabs", () => {
@@ -25,8 +25,8 @@ describe("session tabs", () => {
expect(sessionTabDetail("opencode", undefined, "main", true)).toBe("opencode")
})
test("labels tabs by ordinal", () => {
expect(Array.from({ length: 12 }, (_, index) => sessionTabNumberLabel(index))).toEqual([
test("labels direct shortcut tabs and marks unbound tabs with a dot", () => {
expect(Array.from({ length: 12 }, (_, index) => sessionTabShortcutLabel(index))).toEqual([
"1",
"2",
"3",
@@ -36,9 +36,9 @@ describe("session tabs", () => {
"7",
"8",
"9",
"10",
"11",
"12",
"0",
"·",
"·",
])
})
+2 -4
View File
@@ -981,8 +981,7 @@ test("direct footer steers the oldest queued prompt from an empty composer", asy
try {
await app.renderOnce()
app.mockInput.pressKey("x", { ctrl: true })
app.mockInput.pressEnter()
app.mockInput.pressEnter({ meta: true })
await Bun.sleep(0)
expect(steered).toEqual([])
app.mockInput.pressEnter()
@@ -1035,8 +1034,7 @@ test("direct footer rejects local commands submitted with the queue shortcut", a
try {
await app.renderOnce()
await app.mockInput.typeText("/settings ")
app.mockInput.pressKey("x", { ctrl: true })
app.mockInput.pressEnter()
app.mockInput.pressEnter({ meta: true })
await Bun.sleep(0)
expect(submitted).toEqual([])
expect(statuses).toContain("this prompt cannot be queued")
+1 -1
View File
@@ -22,7 +22,7 @@ describe("run runtime boot", () => {
expect(result.keybinds.get("prompt.clear")?.[0]?.key).toBe("ctrl+c")
expect(result.keybinds.get("input.submit")?.[0]?.key).toBe("return")
expect(result.keybinds.get("input.newline")?.[0]?.key).toBe("shift+return,ctrl+return,ctrl+j")
expect(result.keybinds.get("prompt.queue")?.[0]?.key).toBe("<leader>return")
expect(result.keybinds.get("prompt.queue")?.[0]?.key).toBe("alt+return")
})
test("preserves shared config while resolving independent Mini defaults", async () => {
@@ -165,6 +165,7 @@ OpenCode automatically discovers language models from an Ollama server listening
```
OpenCode refreshes the inventory in the background and reads context, vision, and tool-use capabilities from Ollama.
Thinking-capable models also expose reasoning variants.
Embedding-only models are excluded because they cannot drive a session. Disable discovery with
`"plugins": ["-opencode.provider.ollama"]`.
@@ -199,8 +200,9 @@ address, `http://127.0.0.1:1234`. Discovered models use the `lmstudio` provider
}
```
OpenCode refreshes the inventory in the background and reads context, vision, and tool-use capabilities from LM
Studio. Embedding models are excluded because they cannot drive a session. Disable discovery with
OpenCode refreshes the inventory in the background and reads context, vision, tool-use, and reasoning capabilities from
LM Studio. Available reasoning controls become model variants. Embedding models are excluded because they cannot drive
a session. Disable discovery with
`"plugins": ["-opencode.provider.lmstudio"]`.
For a different host or port, configure the OpenAI-compatible base URL. Models are still discovered automatically:
@@ -239,6 +241,8 @@ text input and output, but not vision or tools. Tool calling is conservative bec
flags such as `--enable-auto-tool-choice` and `--tool-call-parser`, which model discovery does not report. Disable
discovery with `"plugins": ["-opencode.provider.vllm"]`.
Recognized reasoning models expose reasoning variants.
For a different endpoint or an authenticated server, configure its OpenAI-compatible base URL:
```jsonc title="opencode.jsonc"