diff --git a/bun.lock b/bun.lock index 2703bd029d01..2f7c3ac6bd4b 100644 --- a/bun.lock +++ b/bun.lock @@ -138,9 +138,9 @@ "name": "@opencode-ai/console-function", "version": "1.2.15", "dependencies": { - "@ai-sdk/anthropic": "2.0.0", - "@ai-sdk/openai": "2.0.2", - "@ai-sdk/openai-compatible": "1.0.1", + "@ai-sdk/anthropic": "3.0.53", + "@ai-sdk/openai": "3.0.39", + "@ai-sdk/openai-compatible": "2.0.30", "@hono/zod-validator": "catalog:", "@openauthjs/openauth": "0.0.0-20250322224806", "@opencode-ai/console-core": "workspace:*", @@ -270,28 +270,27 @@ "@actions/core": "1.11.1", "@actions/github": "6.0.1", "@agentclientprotocol/sdk": "0.14.1", - "@ai-sdk/amazon-bedrock": "3.0.82", - "@ai-sdk/anthropic": "2.0.65", - "@ai-sdk/azure": "2.0.91", - "@ai-sdk/cerebras": "1.0.36", - "@ai-sdk/cohere": "2.0.22", - "@ai-sdk/deepinfra": "1.0.36", - "@ai-sdk/gateway": "2.0.30", - "@ai-sdk/google": "2.0.54", - "@ai-sdk/google-vertex": "3.0.106", - "@ai-sdk/groq": "2.0.34", - "@ai-sdk/mistral": "2.0.27", - "@ai-sdk/openai": "2.0.89", - "@ai-sdk/openai-compatible": "1.0.32", - "@ai-sdk/perplexity": "2.0.23", - "@ai-sdk/provider": "2.0.1", - "@ai-sdk/provider-utils": "3.0.21", - "@ai-sdk/togetherai": "1.0.34", - "@ai-sdk/vercel": "1.0.33", - "@ai-sdk/xai": "2.0.51", + "@ai-sdk/amazon-bedrock": "4.0.73", + "@ai-sdk/anthropic": "3.0.54", + "@ai-sdk/azure": "3.0.40", + "@ai-sdk/cerebras": "2.0.37", + "@ai-sdk/cohere": "3.0.23", + "@ai-sdk/deepinfra": "2.0.37", + "@ai-sdk/gateway": "3.0.63", + "@ai-sdk/google": "3.0.37", + "@ai-sdk/google-vertex": "4.0.73", + "@ai-sdk/groq": "3.0.27", + "@ai-sdk/mistral": "3.0.22", + "@ai-sdk/openai": "3.0.39", + "@ai-sdk/openai-compatible": "2.0.33", + "@ai-sdk/perplexity": "3.0.21", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.17", + "@ai-sdk/togetherai": "2.0.37", + "@ai-sdk/vercel": "2.0.35", + "@ai-sdk/xai": "3.0.64", "@aws-sdk/credential-providers": "3.993.0", "@clack/prompts": "1.0.0-alpha.1", - "@gitlab/gitlab-ai-provider": "3.6.0", "@gitlab/opencode-gitlab-auth": "1.3.3", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", @@ -303,7 +302,7 @@ "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", - "@openrouter/ai-sdk-provider": "1.5.4", + "@openrouter/ai-sdk-provider": "2.2.3", "@opentui/core": "0.1.86", "@opentui/solid": "0.1.86", "@parcel/watcher": "2.5.1", @@ -313,7 +312,7 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", - "ai-gateway-provider": "2.3.1", + "ai-gateway-provider": "3.1.1", "bonjour-service": "1.3.0", "bun-pty": "0.4.8", "chokidar": "4.0.3", @@ -322,6 +321,7 @@ "diff": "catalog:", "drizzle-orm": "1.0.0-beta.12-a5629fb", "fuzzysort": "3.1.0", + "gitlab-ai-provider": "5.0.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -536,7 +536,6 @@ "tree-sitter-bash", ], "patchedDependencies": { - "@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", }, "overrides": { @@ -563,7 +562,7 @@ "@types/node": "22.13.9", "@types/semver": "7.7.1", "@typescript/native-preview": "7.0.0-dev.20251207.1", - "ai": "5.0.124", + "ai": "6.0.111", "diff": "8.0.2", "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.12-a5629fb", @@ -603,51 +602,51 @@ "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.14.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-b6r3PS3Nly+Wyw9U+0nOr47bV8tfS476EgyEMhoKvJCZLbgqoDFN7DJwkxL88RR0aiOqOYV1ZnESHqb+RmdH8w=="], - "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.82", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.65", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", "@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-yb1EkRCMWex0tnpHPLGQxoJEiJvMGOizuxzlXFOpuGFiYgE679NsWE/F8pHwtoAWsqLlylgGAJvJDIJ8us8LEw=="], + "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.73", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.54", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17", "@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-saXerRo7ChlrIyIQ67o24TOsMPBNRJDNlAhJiiAkWO1OR+CuodScmGae0RbE8C7LWYEPRKbTxN5fAwiQU6/R7w=="], - "@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="], + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HVuVLUt4VtioGKV5gxRg7Wu2g0BvX3zzrAa51OHPG9sJlP+caPyZu3n4j4FHjZmg++8A9JHDCMWBQfHra3gtWg=="], - "@ai-sdk/azure": ["@ai-sdk/azure@2.0.91", "", { "dependencies": { "@ai-sdk/openai": "2.0.89", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9tznVSs6LGQNKKxb8pKd7CkBV9yk+a/ENpFicHCj2CmBUKefxzwJ9JbUqrlK3VF6dGZw3LXq0dWxt7/Yekaj1w=="], + "@ai-sdk/azure": ["@ai-sdk/azure@3.0.40", "", { "dependencies": { "@ai-sdk/openai": "3.0.39", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4BVhJR/LdGoBgQ5fw225hvv4/2Ou34W5Wi79URiTS+KDnIkF7okH5iy0oxBbwUWafa+07LYf5sTMXUS7cKW5Xg=="], - "@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.36", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zoJYL33+ieyd86FSP0Whm86D79d1lKPR7wUzh1SZ1oTxwYmsGyvIrmMf2Ll0JA9Ds2Es6qik4VaFCrjwGYRTIQ=="], + "@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.37", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.33", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-X+4gya3p6b61CiWEwBwN0rKk1pMtrAZLurM2z3C9IFZwT9nzrfi9aQtN7X9isiZSrkRp1sa8mzVN74Pv8wSKUw=="], - "@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJ9kP5cEDJwo8qpITq5TQFD8YNfNtW+HbyvWwrKMbFzmiMvIZuk95HIaFXE7PCTuZsqMA05yYu+qX/vQ3rNKjA=="], + "@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Xe50S7q08RX+kfjarOb1aK3P9X4ojhai0XOzA+sQ1ICkfFcmV2cutSI1RzoKc7CJGmFoyuTv06GpaVIhEfASyQ=="], - "@ai-sdk/deepgram": ["@ai-sdk/deepgram@1.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yQ5izccuO7+WDtitbJsqH7qX7BqVVonUbPZBxQypF3zqBXbCI3/3CH+0XbsWRVRWFN8/rmCAbgHg8DXjaqVQsw=="], + "@ai-sdk/deepgram": ["@ai-sdk/deepgram@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-JzRuzti8Hib3o5e4kc7iM+dDW8cdlf0K2i9OdUnlWGGp7zs0x/s5RjwU3whF+cjQt9Tub2nYdTTj25Gl0qmdqA=="], - "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.36", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.33", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LndvRktEgY2IFu4peDJMEXcjhHEEFtM0upLx/J64kCpFHCifalXpK4PPSX3PVndnn0bJzvamO5+fc0z2ooqBZw=="], + "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@2.0.37", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.33", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ah+7/9LrWL2XQ1HKpD9WJNsxySt+PAJ7k2Aht/gQXNyXTZGMJNt2pdvCCBnlfvH0g8Y73gw4x55F2TqtHhx0wg=="], - "@ai-sdk/deepseek": ["@ai-sdk/deepseek@1.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-H+5UGOGGZB5tYpX+3fcWxoPPDzRTEH1w6z+yD7053PmKZfHcxSJWv9HwLEyEkAv3ef1E7MIyG5EB+HmkclQ+KQ=="], + "@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cawiX4oYAJnov14ky9hN30lLOsmjfJCnZNi7JjF/SUgHnEzXr0SWyY40vzF8pxzeRybt9Ukl+EewfGPMPNgrGQ=="], - "@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@1.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B9uz4+KEB5RkphL9d9XL03iA24g3f0VAeklNlq7StY7L8Mo2sBx3Bg8Udzv7G3xJmT41GuzR5pR0FkKUTju0Rg=="], + "@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BAOIgSu0O1jT04sbFoCK3SHCs4sFo2jsXBO4kC7uvI5GenZxWqsJy+WDm//b3JIvymCYraNyrnwSqS+F/rP9/w=="], - "@ai-sdk/fireworks": ["@ai-sdk/fireworks@1.0.34", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.33", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-JvRdp8bMokbmp/mFz0qHPAhvAZT+vR+c9o4lTkENkDcbRBcNYUN05sSWCuwiVDdz9T+8GW7goAec6fXJBzjIFw=="], + "@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.38", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.33", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-xms6HYOMv5Rx1GoszzdO0ySCPu2yXH+6DREDQnL1fBqAfvO/S0ZcsqreI5FosBuK4i5fwEsAcJWtvweq5m7liA=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5Nrkj8B4MzkkOfjjA+Cs5pamkbkK4lI11bx80QV7TFcen/hWA8wEC+UVzwuM5H2zpekoNMjvl6GonHnR62XIZw=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.63", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0jwdkN3elC4Q9aT2ALxjXtGGVoye15zYgof6GfvuH1a9QKx9Rj4Wi2vy6SyyLvtSA/lB786dTZgC+cGwe6vzmA=="], - "@ai-sdk/google": ["@ai-sdk/google@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-VKguP0x/PUYpdQyuA/uy5pDGJy6reL0X/yDKxHfL207aCUXpFIBmyMhVs4US39dkEVhtmIFSwXauY0Pt170JRw=="], + "@ai-sdk/google": ["@ai-sdk/google@3.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hZ7nO55+GfQEWJe2B5XRoLNeEubMTuk6OjJJUDS+XCtjKLCd973rRkc62vS86rHw5VuCGITwn3gUZRhSbuX5vw=="], - "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.106", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.65", "@ai-sdk/google": "2.0.54", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-f9sA66bmhgJoTwa+pHWFSdYxPa0lgdQ/MgYNxZptzVyGptoziTf1a9EIXEL3jiCD0qIBAg+IhDAaYalbvZaDqQ=="], + "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.73", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.54", "@ai-sdk/google": "3.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-JFgDf2OWpfsxcQEkAnlJN2/xCBanqvG2ngS06QT+1v2mH4tYHdz+wOwtQLUsvt1ZzCVvtXMs+uUviIeOxJFnPA=="], - "@ai-sdk/groq": ["@ai-sdk/groq@2.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wfCYkVgmVjxNA32T57KbLabVnv9aFUflJ4urJ7eWgTwbnmGQHElCTu+rJ3ydxkXSqxOkXPwMOttDm7XNrvPjmg=="], + "@ai-sdk/groq": ["@ai-sdk/groq@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-IZLwdA7s70BZQMG5JUFgaXMw61UvLR9sBSf7+vhc4YjMMj0UpRK+3ERaVxQah/wrjVhoqEYRUA4JgcBIrafxRg=="], - "@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gaptHgaXjMw3+eA0Q4FABcsj5nQNP6EpFaGUR+Pj5WJy7Kn6mApl975/x57224MfeJIShNpt8wFKK3tvh5ewKg=="], + "@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0D7epG78iAr8g2EWAzA6jke/+oIpqedPoTWkd/1NXQ9zOlXq+JHbde3/Smls86Px69DrJAvmbnEbkV0k7YeFtg=="], - "@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="], + "@ai-sdk/openai": ["@ai-sdk/openai@3.0.39", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EZrs4L6kMkPQhpodagpEvqLSryOIK99WgblN0IsVHr1xhajWizQOZ0XMa7c5JpSYgIjV6u8GCpGV6hS3Mk2Bug=="], - "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="], + "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.30", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iTjumHf1/u4NhjXYFn/aONM2GId3/o7J1Lp5ql8FCbgIMyRwrmanR5xy1S3aaVkfTscuDvLTzWiy1mAbGzK3nQ=="], - "@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-aiaRvnc6mhQZKhTTSXPCjPH8Iqr5D/PfCN1hgVP/3RGTBbJtsd9HemIBSABeSdAKbsMH/PwJxgnqH75HEamcBA=="], + "@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-t4VvhQ1a8Ab9eXyeQ5POqxhxzXsfjighcDQAPc5DefIigtq87yP6UihpMmh4kETdNCpYogpejtVQv/TtQvTZAg=="], - "@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="], + "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.17", "", { "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-oyCeFINTYK0B8ZGUBiQc05G5vytPlKSmTTtm19xfJuUgoi8zkvvRcoPQci4mSnyfpPn2XSFFDfsALG8uGcapfg=="], - "@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.34", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jjJmJms6kdEc4nC3MDGFJfhV8F1ifY4nolV2dbnT7BM4ab+Wkskc0GwCsJ7G7WdRMk7xDbFh4he3DPL8KJ/cyA=="], + "@ai-sdk/togetherai": ["@ai-sdk/togetherai@2.0.37", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.33", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-P1TtX3S4nqa5WXL7REX2f2iwN9pS+KAflAjUlOkGLLnGc7eWraqhlMmSy+UXd3ah0spgDjYS4nvKjvI8hTcEkw=="], - "@ai-sdk/vercel": ["@ai-sdk/vercel@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Qwjm+HdwKasu7L9bDUryBMGKDMscIEzMUkjw/33uGdJpktzyNW13YaNIObOZ2HkskqDMIQJSd4Ao2BBT8fEYLw=="], + "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.35", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.33", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NesCs4qRK8tsk9R2qOy76xRsBaerHbhHqg7V1PEaThg518eiVLPvJlJXgcG27LfoDpDjnpgF7p1X36UNVETFqg=="], - "@ai-sdk/xai": ["@ai-sdk/xai@2.0.51", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AI3le03qiegkZvn9hpnpDwez49lOvQLj4QUBT8H41SMbrdTYOxn3ktTwrsSu90cNDdzKGMvoH0u2GHju1EdnCg=="], + "@ai-sdk/xai": ["@ai-sdk/xai@3.0.64", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.33", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-1brA0N0smiFu2IkpBqJZl1AAtwOOMUD2H662WkMHEulpHFWT28lXevnjM6tlLYyxPWvslUEgg0h8a78Kk7T1rQ=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -1029,8 +1028,6 @@ "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], - "@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="], - "@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="], "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], @@ -1339,9 +1336,7 @@ "@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"], - "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.5.4", "", { "dependencies": { "@openrouter/sdk": "^0.1.27" }, "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-xrSQPUIH8n9zuyYZR0XK7Ba0h2KsjJcMkxnwaYfmv13pKs3sDkjPzVPPhlhzqBGddHb5cFEwJ9VFuFeDcxCDSw=="], - - "@openrouter/sdk": ["@openrouter/sdk@0.1.27", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.2.3", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-NovC+BaCfEeJwhToDrs8JeDYXXlJdEyz7lcxkjtyePSE4eoAKik872SyDK0MzXKcz8MRkv7XlNhPI6zz4TQp0g=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -2123,9 +2118,9 @@ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], - "ai": ["ai@5.0.124", "", { "dependencies": { "@ai-sdk/gateway": "2.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Li6Jw9F9qsvFJXZPBfxj38ddP2iURCnMs96f9Q3OeQzrDVcl1hvtwSEAuxA/qmfh6SDV2ERqFUOFzigvr0697g=="], + "ai": ["ai@6.0.111", "", { "dependencies": { "@ai-sdk/gateway": "3.0.63", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-K5aikNm4JGfJkzwIr3yA/qhOYIOIvOqjCxSQjQQ7bWWqm0uuPO2/qgdXL23gYJdTLPPYfvi2TTS+bg2Yp+r2Lw=="], - "ai-gateway-provider": ["ai-gateway-provider@2.3.1", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.19", "ai": "^5.0.116" }, "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^3.0.71", "@ai-sdk/anthropic": "^2.0.56", "@ai-sdk/azure": "^2.0.90", "@ai-sdk/cerebras": "^1.0.33", "@ai-sdk/cohere": "^2.0.21", "@ai-sdk/deepgram": "^1.0.21", "@ai-sdk/deepseek": "^1.0.32", "@ai-sdk/elevenlabs": "^1.0.21", "@ai-sdk/fireworks": "^1.0.30", "@ai-sdk/google": "^2.0.51", "@ai-sdk/google-vertex": "3.0.90", "@ai-sdk/groq": "^2.0.33", "@ai-sdk/mistral": "^2.0.26", "@ai-sdk/openai": "^2.0.88", "@ai-sdk/perplexity": "^2.0.22", "@ai-sdk/xai": "^2.0.42", "@openrouter/ai-sdk-provider": "^1.5.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^1.0.29" } }, "sha512-PqI6TVNEDNwr7kOhy7XUGnA8XJB1SpeA9aLqGjr0CyWkKgH+y+ofPm8MZGZ74DOwVejDF+POZq0Qs9jKEKUeYg=="], + "ai-gateway-provider": ["ai-gateway-provider@3.1.1", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.37", "@ai-sdk/anthropic": "^3.0.29", "@ai-sdk/azure": "^3.0.21", "@ai-sdk/cerebras": "^2.0.23", "@ai-sdk/cohere": "^3.0.12", "@ai-sdk/deepgram": "^2.0.12", "@ai-sdk/deepseek": "^2.0.12", "@ai-sdk/elevenlabs": "^2.0.12", "@ai-sdk/fireworks": "^2.0.22", "@ai-sdk/google": "^3.0.16", "@ai-sdk/google-vertex": "^4.0.35", "@ai-sdk/groq": "^3.0.16", "@ai-sdk/mistral": "^3.0.13", "@ai-sdk/openai": "^3.0.21", "@ai-sdk/perplexity": "^3.0.12", "@ai-sdk/xai": "^3.0.40", "@openrouter/ai-sdk-provider": "^2.1.1" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-RTHkboNz9JocplBSKdDLSMEiN3OkfS1UeZqM93zuQ3BVDR5BmTisThoQR/3pkAVACIYL/8OEQ3kuSuDo820gVw=="], "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], @@ -2769,6 +2764,8 @@ "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + "gitlab-ai-provider": ["gitlab-ai-provider@5.0.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-CeQsnU+/jYYh9AxKb2kpBMFDL53350Yj+bGqINjWxYJu+IbqtTzi8fsyDz1ioVR8NCevF0MBNvZxnlpMSw+6+w=="], + "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -4315,57 +4312,27 @@ "@actions/http-client/undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="], - "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="], + "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-UhSPZ63FsTNO7PQCfxsqJIgkij1sivU3qfXydlSd4ugshpkNhd2v9s78G/40/G5C3pKSRfp/CfaSvivrneQfCg=="], "@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="], - "@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], - - "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], - - "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="], - - "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - - "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="], - - "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - - "@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - - "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="], - - "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="], - - "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HwptqeUS4vtDyjSSjmKCQExjoQMwPVq0C4pHH18i7c+3CQ0QN81HLvz3BdpULo0n/UtdQwTNISRqx3G5miPZhw=="], - "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="], + "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HwptqeUS4vtDyjSSjmKCQExjoQMwPVq0C4pHH18i7c+3CQ0QN81HLvz3BdpULo0n/UtdQwTNISRqx3G5miPZhw=="], - "@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HwptqeUS4vtDyjSSjmKCQExjoQMwPVq0C4pHH18i7c+3CQ0QN81HLvz3BdpULo0n/UtdQwTNISRqx3G5miPZhw=="], - "@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-UhSPZ63FsTNO7PQCfxsqJIgkij1sivU3qfXydlSd4ugshpkNhd2v9s78G/40/G5C3pKSRfp/CfaSvivrneQfCg=="], - "@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], - "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], + "@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], + "@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HwptqeUS4vtDyjSSjmKCQExjoQMwPVq0C4pHH18i7c+3CQ0QN81HLvz3BdpULo0n/UtdQwTNISRqx3G5miPZhw=="], - "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], + "@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HwptqeUS4vtDyjSSjmKCQExjoQMwPVq0C4pHH18i7c+3CQ0QN81HLvz3BdpULo0n/UtdQwTNISRqx3G5miPZhw=="], - "@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - - "@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="], - - "@ai-sdk/togetherai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - - "@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="], - - "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - - "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="], - - "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HwptqeUS4vtDyjSSjmKCQExjoQMwPVq0C4pHH18i7c+3CQ0QN81HLvz3BdpULo0n/UtdQwTNISRqx3G5miPZhw=="], "@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -4553,10 +4520,6 @@ "@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], - "@gitlab/gitlab-ai-provider/openai": ["openai@6.22.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw=="], - - "@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], "@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -4769,19 +4732,25 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.59", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.43", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15", "@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-reIzGwpstdses389Uh8X+MRI7MjpemJmNGynyrKJF2+ib214fAMdlcNwuwITdzt0t6FhJBeMW7g2WbEjLnL5XA=="], + + "ai-gateway-provider/@ai-sdk/azure": ["@ai-sdk/azure@3.0.29", "", { "dependencies": { "@ai-sdk/openai": "3.0.28", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z+nhcjBpu7Rmgfh+fmF9vpANVEwu7loNWlCvDo8fxrDpL44caOIKLjqKerEcgbBj3+NLfgZot1FOzvdjgEpvbw=="], - "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.79", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.62", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", "@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-GfAQUb1GEmdTjLu5Ud1d5sieNHDpwoQdb4S14KmJlA5RsGREUZ1tfSKngFaiClxFtL0xPSZjePhTMV6Z65A7/g=="], + "ai-gateway-provider/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.30", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qT7H/q45vD/XezA78X6ZWwmHiSH50V0AdLcHMyfgrThFUDkjgPgNuGADnZOm/sopMuRASV2eUzL60F1zTuH1ew=="], - "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.63", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zXlUPCkumnvp8lWS9VFcen/MLF6CL/t1zAKDhpobYj9y/nmylQrKtRvn3RwH871Wd3dF3KYEUXd6M2c6dfCKOA=="], + "ai-gateway-provider/@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HDUvNX2UhGpX0VyeAdOStFy07ZvjFtL+hXT5cXcwK/TBNiIhPFovTYHKVNSdNAxbH2eO7zRzygjMVUEX1X0Btw=="], - "ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@2.0.53", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ccCxr5mrd3AC2CjLq4e1ST7+UiN5T2Pdmgi0XdWM3QohmNBwUQ/RBG7BvL+cB/ex/j6y64tkMmpYz9zBw/SEFQ=="], + "ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.29", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-x0hcU10AA+i1ZUQHloGD5qXWsB+Y8qnxlmFUef6Ly4rB53MGVbQExkI9nOKiCO3mu2TGiiNoQMeKWSeQVLfRUA=="], - "ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.90", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.56", "@ai-sdk/google": "2.0.46", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-C9MLe1KZGg1ZbupV2osygHtL5qngyCDA6ATatunyfTbIe8TXKG8HGni/3O6ifbnI5qxTidIn150Ox7eIFZVMYg=="], + "ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.57", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.43", "@ai-sdk/google": "3.0.29", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dVRGqC0Kgsb4KsLO4OSLELyse746KOwYibqP62FpRlVqPeTnHZPDzs8Dub4341p/WfUq+U0s98lpmH++M/4A1g=="], - "ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="], + "ai-gateway-provider/@ai-sdk/groq": ["@ai-sdk/groq@3.0.24", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-J6UMMVKBDf1vxYN8TS4nBzCEImhon1vuqpJYkRYdbxul6Hlf0r0pT5/+1AD1nbQ1SJsOPlDqMRSYJuBnNYrNfQ=="], - "ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="], + "ai-gateway-provider/@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-oZcx2pE6nJ+Qj/U6HFV5mJ52jXJPBSpvki/NtIocZkI/rKxphKBaecOH1h0Y7yK3HIbBxsMqefB1pb72cAHGVg=="], + + "ai-gateway-provider/@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7yxvyw0OFlHjCXgf+BDgmjefQmSk9FxSF5DPiFtLrow1zzLcvZvh6fkKEx+kgRQXNKhV9vvtH0U4NyVXgGMr0g=="], + + "ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.56", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.30", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EbceQ0YkrAi0y4TRxFkgNgc/y8KhJhf+Gtxvmn7+hoV7OETP/q9Q3pgZLcLkLAHptM4hlliOMYpZux/9ADuY2Q=="], "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=="], @@ -4863,6 +4832,10 @@ "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "gitlab-ai-provider/openai": ["openai@6.22.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw=="], + + "gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], "globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -4911,11 +4884,9 @@ "nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], - "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="], - - "opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="], + "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-UhSPZ63FsTNO7PQCfxsqJIgkij1sivU3qfXydlSd4ugshpkNhd2v9s78G/40/G5C3pKSRfp/CfaSvivrneQfCg=="], - "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="], + "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HwptqeUS4vtDyjSSjmKCQExjoQMwPVq0C4pHH18i7c+3CQ0QN81HLvz3BdpULo0n/UtdQwTNISRqx3G5miPZhw=="], "opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="], @@ -5065,11 +5036,7 @@ "@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], - "@ai-sdk/anthropic/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - - "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - - "@ai-sdk/openai/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], @@ -5359,17 +5326,33 @@ "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.62", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="], + "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.43", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hfhOa13HqNYIe2GtC7blUMVOKrmt5RzxbE7StFGXKN3hIn4xuppLLQY/gFOr+oEhQw1pO8j6CpffrRrWYz1mVQ=="], + + "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], + + "ai-gateway-provider/@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="], + + "ai-gateway-provider/@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.28", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-m2Dm6fwUzMksqnPrd5f/WZ4cZ9GTZHpzsVO6jxKQwwc84gFHzAFZmUCG0C5mV7XlPOw4mwaiYV3HfLiEfphvvA=="], + + "ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], + + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], + + "ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], + + "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], + + "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.43", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hfhOa13HqNYIe2GtC7blUMVOKrmt5RzxbE7StFGXKN3hIn4xuppLLQY/gFOr+oEhQw1pO8j6CpffrRrWYz1mVQ=="], - "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="], + "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], - "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@2.0.46", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8PK6u4sGE/kXebd7ZkTp+0aya4kNqzoqpS5m7cHY2NfTK6fhPc6GNvE+MZIZIoHQTp5ed86wGBdeBPpFaaUtyg=="], + "ai-gateway-provider/@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], - "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], + "ai-gateway-provider/@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], - "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], + "ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], - "ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "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-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -5423,10 +5406,6 @@ "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - - "opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - "opencontrol/@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="], @@ -5763,6 +5742,26 @@ "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], + "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "archiver-utils/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], diff --git a/flake.nix b/flake.nix index 40e9d337f58b..6a871acd058d 100644 --- a/flake.nix +++ b/flake.nix @@ -26,7 +26,12 @@ pkg-config openssl git + playwright-driver.browsers ]; + shellHook = '' + export PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}" + export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true + ''; }; }); diff --git a/package.json b/package.json index dc78d14e84f2..ad4506c5ea5d 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.12-a5629fb", "drizzle-orm": "1.0.0-beta.12-a5629fb", - "ai": "5.0.124", + "ai": "6.0.111", "hono": "4.10.7", "hono-openapi": "1.1.2", "fuzzysort": "3.1.0", @@ -106,7 +106,6 @@ "@types/node": "catalog:" }, "patchedDependencies": { - "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", - "@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch" + "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch" } } diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 6cdf752432cc..88e8cc7d847f 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -17,9 +17,9 @@ "@typescript/native-preview": "catalog:" }, "dependencies": { - "@ai-sdk/anthropic": "2.0.0", - "@ai-sdk/openai": "2.0.2", - "@ai-sdk/openai-compatible": "1.0.1", + "@ai-sdk/anthropic": "3.0.53", + "@ai-sdk/openai": "3.0.39", + "@ai-sdk/openai-compatible": "2.0.30", "@hono/zod-validator": "catalog:", "@opencode-ai/console-core": "workspace:*", "@opencode-ai/console-resource": "workspace:*", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 45ecafaa23d6..604df2a40f3f 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -55,28 +55,27 @@ "@actions/core": "1.11.1", "@actions/github": "6.0.1", "@agentclientprotocol/sdk": "0.14.1", - "@ai-sdk/amazon-bedrock": "3.0.82", - "@ai-sdk/anthropic": "2.0.65", - "@ai-sdk/azure": "2.0.91", - "@ai-sdk/cerebras": "1.0.36", - "@ai-sdk/cohere": "2.0.22", - "@ai-sdk/deepinfra": "1.0.36", - "@ai-sdk/gateway": "2.0.30", - "@ai-sdk/google": "2.0.54", - "@ai-sdk/google-vertex": "3.0.106", - "@ai-sdk/groq": "2.0.34", - "@ai-sdk/mistral": "2.0.27", - "@ai-sdk/openai": "2.0.89", - "@ai-sdk/openai-compatible": "1.0.32", - "@ai-sdk/perplexity": "2.0.23", - "@ai-sdk/provider": "2.0.1", - "@ai-sdk/provider-utils": "3.0.21", - "@ai-sdk/togetherai": "1.0.34", - "@ai-sdk/vercel": "1.0.33", - "@ai-sdk/xai": "2.0.51", + "@ai-sdk/amazon-bedrock": "4.0.73", + "@ai-sdk/anthropic": "3.0.54", + "@ai-sdk/azure": "3.0.40", + "@ai-sdk/cerebras": "2.0.37", + "@ai-sdk/cohere": "3.0.23", + "@ai-sdk/deepinfra": "2.0.37", + "@ai-sdk/gateway": "3.0.63", + "@ai-sdk/google": "3.0.37", + "@ai-sdk/google-vertex": "4.0.73", + "@ai-sdk/groq": "3.0.27", + "@ai-sdk/mistral": "3.0.22", + "@ai-sdk/openai": "3.0.39", + "@ai-sdk/openai-compatible": "2.0.33", + "@ai-sdk/perplexity": "3.0.21", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.17", + "@ai-sdk/togetherai": "2.0.37", + "@ai-sdk/vercel": "2.0.35", + "@ai-sdk/xai": "3.0.64", "@aws-sdk/credential-providers": "3.993.0", "@clack/prompts": "1.0.0-alpha.1", - "@gitlab/gitlab-ai-provider": "3.6.0", "@gitlab/opencode-gitlab-auth": "1.3.3", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", @@ -88,7 +87,7 @@ "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", - "@openrouter/ai-sdk-provider": "1.5.4", + "@openrouter/ai-sdk-provider": "2.2.3", "@opentui/core": "0.1.86", "@opentui/solid": "0.1.86", "@parcel/watcher": "2.5.1", @@ -98,7 +97,7 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", - "ai-gateway-provider": "2.3.1", + "ai-gateway-provider": "3.1.1", "bonjour-service": "1.3.0", "bun-pty": "0.4.8", "chokidar": "4.0.3", @@ -107,6 +106,7 @@ "diff": "catalog:", "drizzle-orm": "1.0.0-beta.12-a5629fb", "fuzzysort": "3.1.0", + "gitlab-ai-provider": "5.0.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index c53ca04e2383..fb1cfb7ed3d3 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -1,7 +1,7 @@ import { Config } from "../config/config" import z from "zod" import { Provider } from "../provider/provider" -import { generateObject, streamObject, type ModelMessage } from "ai" +import { generateText, streamText, Output, type ModelMessage } from "ai" import { SystemPrompt } from "../session/system" import { Instance } from "../project/instance" import { Truncate } from "../tool/truncation" @@ -311,15 +311,17 @@ export namespace Agent { }, ], model: language, - schema: z.object({ - identifier: z.string(), - whenToUse: z.string(), - systemPrompt: z.string(), + output: Output.object({ + schema: z.object({ + identifier: z.string(), + whenToUse: z.string(), + systemPrompt: z.string(), + }), }), - } satisfies Parameters[0] + } satisfies Parameters[0] if (defaultModel.providerID === "openai" && (await Auth.get(defaultModel.providerID))?.type === "oauth") { - const result = streamObject({ + const result = streamText({ ...params, providerOptions: ProviderTransform.providerOptions(model, { instructions: SystemPrompt.instructions(), @@ -330,10 +332,13 @@ export namespace Agent { for await (const part of result.fullStream) { if (part.type === "error") throw part.error } - return result.object + const output = await result.output + if (!output) throw new Error("Failed to generate agent configuration") + return output } - const result = await generateObject(params) - return result.object + const result = await generateText(params) + if (!result.output) throw new Error("Failed to generate agent configuration") + return result.output } } diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 817038365246..8123ba74489e 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -3,7 +3,7 @@ import os from "os" import fuzzysort from "fuzzysort" import { Config } from "../config/config" import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda" -import { NoSuchModelError, type Provider as SDK } from "ai" +import { NoSuchModelError, type Provider as SDK, type LanguageModel } from "ai" import { Log } from "../util/log" import { BunProc } from "../bun" import { Plugin } from "../plugin" @@ -27,7 +27,7 @@ import { createVertex } from "@ai-sdk/google-vertex" import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic" import { createOpenAI } from "@ai-sdk/openai" import { createOpenAICompatible } from "@ai-sdk/openai-compatible" -import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider" +import { createOpenRouter } from "@openrouter/ai-sdk-provider" import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/copilot" import { createXai } from "@ai-sdk/xai" import { createMistral } from "@ai-sdk/mistral" @@ -39,11 +39,12 @@ import { createGateway } from "@ai-sdk/gateway" import { createTogetherAI } from "@ai-sdk/togetherai" import { createPerplexity } from "@ai-sdk/perplexity" import { createVercel } from "@ai-sdk/vercel" -import { createGitLab, VERSION as GITLAB_PROVIDER_VERSION } from "@gitlab/gitlab-ai-provider" +import { createGitLab, VERSION as GITLAB_PROVIDER_VERSION } from "gitlab-ai-provider" import { fromNodeProviderChain } from "@aws-sdk/credential-providers" import { GoogleAuth } from "google-auth-library" import { ProviderTransform } from "./transform" import { Installation } from "../installation" +import { shimLanguageModel, shimProvider } from "./shim" export namespace Provider { const log = Log.create({ service: "provider" }) @@ -84,7 +85,12 @@ export namespace Provider { }) } - const BUNDLED_PROVIDERS: Record SDK> = { + // Provider factories return varying types: most @ai-sdk/* packages are ProviderV3 while legacy + // third-party packages (e.g. GitLab, Venice) may still return ProviderV2. shimProvider() in + // ./shim.ts wraps v2 providers/models to v3 transparently at load time so all downstream code + // sees a consistent v3 interface. Typed as `any` because not all providers expose the full + // provider interface (e.g. missing embeddingModel). + const BUNDLED_PROVIDERS: Record any> = { "@ai-sdk/amazon-bedrock": createAmazonBedrock, "@ai-sdk/anthropic": createAnthropic, "@ai-sdk/azure": createAzure, @@ -104,7 +110,7 @@ export namespace Provider { "@ai-sdk/togetherai": createTogetherAI, "@ai-sdk/perplexity": createPerplexity, "@ai-sdk/vercel": createVercel, - "@gitlab/gitlab-ai-provider": createGitLab, + "gitlab-ai-provider": createGitLab, // @ts-ignore (TODO: kill this code so we dont have to maintain it) "@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible, } @@ -791,7 +797,7 @@ export namespace Provider { } const providers: { [providerID: string]: Info } = {} - const languages = new Map() + const languages = new Map() const modelLoaders: { [providerID: string]: CustomModelLoader } = {} @@ -1138,8 +1144,9 @@ export namespace Provider { name: model.providerID, ...options, }) - s.sdk.set(key, loaded) - return loaded as SDK + const shimmed = shimProvider(loaded) + s.sdk.set(key, shimmed) + return shimmed as SDK } let installedPath: string @@ -1150,15 +1157,23 @@ export namespace Provider { installedPath = model.api.npm } - const mod = await import(installedPath) - - const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!] - const loaded = fn({ - name: model.providerID, - ...options, - }) - s.sdk.set(key, loaded) - return loaded as SDK + try { + const mod = await import(installedPath) + const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!] + const loaded = fn({ + name: model.providerID, + ...options, + }) + const shimmed = shimProvider(loaded) + s.sdk.set(key, shimmed) + return shimmed as SDK + } catch (e) { + log.warn("failed to load provider - it may not be compatible with AI SDK v6", { + providerID: model.providerID, + pkg: model.api.npm, + }) + throw e + } } catch (e) { throw new InitError({ providerID: model.providerID }, { cause: e }) } @@ -1188,7 +1203,7 @@ export namespace Provider { return info } - export async function getLanguage(model: Model): Promise { + export async function getLanguage(model: Model): Promise { const s = await state() const key = `${model.providerID}/${model.id}` if (s.models.has(key)) return s.models.get(key)! @@ -1197,9 +1212,10 @@ export namespace Provider { const sdk = await getSDK(model) try { - const language = s.modelLoaders[model.providerID] + const raw = s.modelLoaders[model.providerID] ? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options) : sdk.languageModel(model.api.id) + const language = shimLanguageModel(raw) s.models.set(key, language) return language } catch (e) { diff --git a/packages/opencode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts b/packages/opencode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts index e1e3ed4c2019..4bf7de41d93a 100644 --- a/packages/opencode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +++ b/packages/opencode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts @@ -1,16 +1,16 @@ import { - type LanguageModelV2Prompt, - type SharedV2ProviderMetadata, + type LanguageModelV3Prompt, + type SharedV3ProviderMetadata, UnsupportedFunctionalityError, } from "@ai-sdk/provider" import type { OpenAICompatibleChatPrompt } from "./openai-compatible-api-types" import { convertToBase64 } from "@ai-sdk/provider-utils" -function getOpenAIMetadata(message: { providerOptions?: SharedV2ProviderMetadata }) { +function getOpenAIMetadata(message: { providerOptions?: SharedV3ProviderMetadata }) { return message?.providerOptions?.copilot ?? {} } -export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Prompt): OpenAICompatibleChatPrompt { +export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV3Prompt): OpenAICompatibleChatPrompt { const messages: OpenAICompatibleChatPrompt = [] for (const { role, content, ...message } of prompt) { const metadata = getOpenAIMetadata({ ...message }) @@ -126,10 +126,13 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro } case "tool": { - for (const toolResponse of content) { + for (const toolResponseRaw of content) { + // Skip tool approval responses (V3 addition) - only process tool results + if (toolResponseRaw.type !== "tool-result") continue + const toolResponse = toolResponseRaw const output = toolResponse.output - let contentValue: string + let contentValue = "" switch (output.type) { case "text": case "error-text": @@ -140,9 +143,14 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro case "error-json": contentValue = JSON.stringify(output.value) break + default: { + // Unknown tool output type — skip rather than forward an empty string + console.warn(`[copilot] unsupported tool output type: ${(output as any).type} — skipping`) + continue + } } - const toolResponseMetadata = getOpenAIMetadata(toolResponse) + const toolResponseMetadata = getOpenAIMetadata(toolResponse as any) messages.push({ role: "tool", tool_call_id: toolResponse.toolCallId, diff --git a/packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts b/packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts index 82e2ca02e985..50a2fd0abada 100644 --- a/packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +++ b/packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts @@ -1,6 +1,6 @@ -import type { LanguageModelV2FinishReason } from "@ai-sdk/provider" +type V3FinishReasonUnified = "stop" | "length" | "content-filter" | "tool-calls" | "error" | "other" -export function mapOpenAICompatibleFinishReason(finishReason: string | null | undefined): LanguageModelV2FinishReason { +export function mapOpenAICompatibleFinishReason(finishReason: string | null | undefined): V3FinishReasonUnified { switch (finishReason) { case "stop": return "stop" @@ -12,6 +12,6 @@ export function mapOpenAICompatibleFinishReason(finishReason: string | null | un case "tool_calls": return "tool-calls" default: - return "unknown" + return "other" } } diff --git a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts b/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts index c85d3f3d1780..13ad37892be9 100644 --- a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +++ b/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts @@ -1,12 +1,10 @@ import { APICallError, InvalidResponseDataError, - type LanguageModelV2, - type LanguageModelV2CallWarning, - type LanguageModelV2Content, - type LanguageModelV2FinishReason, - type LanguageModelV2StreamPart, - type SharedV2ProviderMetadata, + type LanguageModelV3, + type LanguageModelV3Content, + type LanguageModelV3StreamPart, + type SharedV3ProviderMetadata, } from "@ai-sdk/provider" import { combineHeaders, @@ -29,6 +27,7 @@ import { type OpenAICompatibleChatModelId, openaiCompatibleProviderOptions } fro import { defaultOpenAICompatibleErrorStructure, type ProviderErrorStructure } from "../openai-compatible-error" import type { MetadataExtractor } from "./openai-compatible-metadata-extractor" import { prepareTools } from "./openai-compatible-prepare-tools" +import { type SharedV3Warning, unsupportedSetting } from "../warnings" export type OpenAICompatibleChatConfig = { provider: string @@ -47,11 +46,11 @@ export type OpenAICompatibleChatConfig = { /** * The supported URLs for the model. */ - supportedUrls?: () => LanguageModelV2["supportedUrls"] + supportedUrls?: () => LanguageModelV3["supportedUrls"] } -export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { - readonly specificationVersion = "v2" +export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 { + readonly specificationVersion = "v3" as const readonly supportsStructuredOutputs: boolean @@ -98,8 +97,8 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { seed, toolChoice, tools, - }: Parameters[0]) { - const warnings: LanguageModelV2CallWarning[] = [] + }: Parameters[0]) { + const warnings: SharedV3Warning[] = [] // Parse provider options const compatibleOptions = Object.assign( @@ -116,15 +115,13 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { ) if (topK != null) { - warnings.push({ type: "unsupported-setting", setting: "topK" }) + warnings.push(unsupportedSetting("topK")) } if (responseFormat?.type === "json" && responseFormat.schema != null && !this.supportsStructuredOutputs) { - warnings.push({ - type: "unsupported-setting", - setting: "responseFormat", - details: "JSON response format schema is only supported with structuredOutputs", - }) + warnings.push( + unsupportedSetting("responseFormat", "JSON response format schema is only supported with structuredOutputs"), + ) } const { @@ -190,8 +187,8 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { } async doGenerate( - options: Parameters[0], - ): Promise>> { + options: Parameters[0], + ): Promise>> { const { args, warnings } = await this.getArgs({ ...options }) const body = JSON.stringify(args) @@ -214,7 +211,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { }) const choice = responseBody.choices[0] - const content: Array = [] + const content: Array = [] // text content: const text = choice.message.content @@ -257,7 +254,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { } // provider metadata: - const providerMetadata: SharedV2ProviderMetadata = { + const providerMetadata: SharedV3ProviderMetadata = { [this.providerOptionsName]: {}, ...(await this.config.metadataExtractor?.extractMetadata?.({ parsedBody: rawResponse, @@ -275,13 +272,22 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { return { content, - finishReason: mapOpenAICompatibleFinishReason(choice.finish_reason), + finishReason: { + unified: mapOpenAICompatibleFinishReason(choice.finish_reason), + raw: choice.finish_reason ?? undefined, + }, usage: { - inputTokens: responseBody.usage?.prompt_tokens ?? undefined, - outputTokens: responseBody.usage?.completion_tokens ?? undefined, - totalTokens: responseBody.usage?.total_tokens ?? undefined, - reasoningTokens: responseBody.usage?.completion_tokens_details?.reasoning_tokens ?? undefined, - cachedInputTokens: responseBody.usage?.prompt_tokens_details?.cached_tokens ?? undefined, + inputTokens: { + total: responseBody.usage?.prompt_tokens ?? undefined, + noCache: undefined, + cacheRead: responseBody.usage?.prompt_tokens_details?.cached_tokens ?? undefined, + cacheWrite: undefined, + }, + outputTokens: { + total: responseBody.usage?.completion_tokens ?? undefined, + text: undefined, + reasoning: responseBody.usage?.completion_tokens_details?.reasoning_tokens ?? undefined, + }, }, providerMetadata, request: { body }, @@ -295,8 +301,8 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { } async doStream( - options: Parameters[0], - ): Promise>> { + options: Parameters[0], + ): Promise>> { const { args, warnings } = await this.getArgs({ ...options }) const body = { @@ -332,7 +338,8 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { hasFinished: boolean }> = [] - let finishReason: LanguageModelV2FinishReason = "unknown" + let finishReason: "stop" | "length" | "content-filter" | "tool-calls" | "error" | "other" = "other" + let finishReasonRaw: string | undefined const usage: { completionTokens: number | undefined completionTokensDetails: { @@ -366,7 +373,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { return { stream: response.pipeThrough( - new TransformStream>, LanguageModelV2StreamPart>({ + new TransformStream>, LanguageModelV3StreamPart>({ start(controller) { controller.enqueue({ type: "stream-start", warnings }) }, @@ -436,6 +443,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { if (choice?.finish_reason != null) { finishReason = mapOpenAICompatibleFinishReason(choice.finish_reason) + finishReasonRaw = choice.finish_reason } if (choice?.delta == null) { @@ -652,7 +660,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { }) } - const providerMetadata: SharedV2ProviderMetadata = { + const providerMetadata: SharedV3ProviderMetadata = { [providerOptionsName]: {}, // Include reasoning_opaque for Copilot multi-turn reasoning ...(reasoningOpaque ? { copilot: { reasoningOpaque } } : {}), @@ -669,13 +677,19 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 { controller.enqueue({ type: "finish", - finishReason, + finishReason: { unified: finishReason, raw: finishReasonRaw }, usage: { - inputTokens: usage.promptTokens ?? undefined, - outputTokens: usage.completionTokens ?? undefined, - totalTokens: usage.totalTokens ?? undefined, - reasoningTokens: usage.completionTokensDetails.reasoningTokens ?? undefined, - cachedInputTokens: usage.promptTokensDetails.cachedTokens ?? undefined, + inputTokens: { + total: usage.promptTokens ?? undefined, + noCache: undefined, + cacheRead: usage.promptTokensDetails.cachedTokens ?? undefined, + cacheWrite: undefined, + }, + outputTokens: { + total: usage.completionTokens ?? undefined, + text: undefined, + reasoning: usage.completionTokensDetails.reasoningTokens ?? undefined, + }, }, providerMetadata, }) diff --git a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts b/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts index ba233fbc1b3f..40335f87f61d 100644 --- a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +++ b/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts @@ -1,4 +1,4 @@ -import type { SharedV2ProviderMetadata } from "@ai-sdk/provider" +import type { SharedV3ProviderMetadata } from "@ai-sdk/provider" /** Extracts provider-specific metadata from API responses. @@ -14,7 +14,7 @@ export type MetadataExtractor = { * @returns Provider-specific metadata or undefined if no metadata is available. * The metadata should be under a key indicating the provider id. */ - extractMetadata: ({ parsedBody }: { parsedBody: unknown }) => Promise + extractMetadata: ({ parsedBody }: { parsedBody: unknown }) => Promise /** * Creates an extractor for handling streaming responses. The returned object provides @@ -39,6 +39,6 @@ export type MetadataExtractor = { * @returns Provider-specific metadata or undefined if no metadata is available. * The metadata should be under a key indicating the provider id. */ - buildMetadata(): SharedV2ProviderMetadata | undefined + buildMetadata(): SharedV3ProviderMetadata | undefined } } diff --git a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts b/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts index 8879d6481b3e..2f5e48cfbd3d 100644 --- a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +++ b/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts @@ -1,15 +1,12 @@ -import { - type LanguageModelV2CallOptions, - type LanguageModelV2CallWarning, - UnsupportedFunctionalityError, -} from "@ai-sdk/provider" +import { type LanguageModelV3CallOptions, UnsupportedFunctionalityError } from "@ai-sdk/provider" +import { type SharedV3Warning, unsupportedTool } from "../warnings" export function prepareTools({ tools, toolChoice, }: { - tools: LanguageModelV2CallOptions["tools"] - toolChoice?: LanguageModelV2CallOptions["toolChoice"] + tools: LanguageModelV3CallOptions["tools"] + toolChoice?: LanguageModelV3CallOptions["toolChoice"] }): { tools: | undefined @@ -22,12 +19,12 @@ export function prepareTools({ } }> toolChoice: { type: "function"; function: { name: string } } | "auto" | "none" | "required" | undefined - toolWarnings: LanguageModelV2CallWarning[] + toolWarnings: SharedV3Warning[] } { // when the tools array is empty, change it to undefined to prevent errors: tools = tools?.length ? tools : undefined - const toolWarnings: LanguageModelV2CallWarning[] = [] + const toolWarnings: SharedV3Warning[] = [] if (tools == null) { return { tools: undefined, toolChoice: undefined, toolWarnings } @@ -43,8 +40,8 @@ export function prepareTools({ }> = [] for (const tool of tools) { - if (tool.type === "provider-defined") { - toolWarnings.push({ type: "unsupported-tool", tool }) + if (tool.type === "provider") { + toolWarnings.push(unsupportedTool(tool)) } else { openaiCompatTools.push({ type: "function", diff --git a/packages/opencode/src/provider/sdk/copilot/copilot-provider.ts b/packages/opencode/src/provider/sdk/copilot/copilot-provider.ts index 1dc373ff3c7e..b9cbb6c7ccb2 100644 --- a/packages/opencode/src/provider/sdk/copilot/copilot-provider.ts +++ b/packages/opencode/src/provider/sdk/copilot/copilot-provider.ts @@ -1,4 +1,4 @@ -import type { LanguageModelV2 } from "@ai-sdk/provider" +import type { LanguageModelV3 } from "@ai-sdk/provider" import { type FetchFunction, withoutTrailingSlash, withUserAgentSuffix } from "@ai-sdk/provider-utils" import { OpenAICompatibleChatLanguageModel } from "./chat/openai-compatible-chat-language-model" import { OpenAIResponsesLanguageModel } from "./responses/openai-responses-language-model" @@ -36,10 +36,10 @@ export interface OpenaiCompatibleProviderSettings { } export interface OpenaiCompatibleProvider { - (modelId: OpenaiCompatibleModelId): LanguageModelV2 - chat(modelId: OpenaiCompatibleModelId): LanguageModelV2 - responses(modelId: OpenaiCompatibleModelId): LanguageModelV2 - languageModel(modelId: OpenaiCompatibleModelId): LanguageModelV2 + (modelId: OpenaiCompatibleModelId): LanguageModelV3 + chat(modelId: OpenaiCompatibleModelId): LanguageModelV3 + responses(modelId: OpenaiCompatibleModelId): LanguageModelV3 + languageModel(modelId: OpenaiCompatibleModelId): LanguageModelV3 // embeddingModel(modelId: any): EmbeddingModelV2 diff --git a/packages/opencode/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts b/packages/opencode/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts index 807f6ea57cdc..e72de1df7343 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts @@ -1,9 +1,9 @@ import { - type LanguageModelV2CallWarning, - type LanguageModelV2Prompt, - type LanguageModelV2ToolCallPart, + type LanguageModelV3Prompt, + type LanguageModelV3ToolCallPart, UnsupportedFunctionalityError, } from "@ai-sdk/provider" +import type { SharedV3Warning } from "../warnings" import { convertToBase64, parseProviderOptions } from "@ai-sdk/provider-utils" import { z } from "zod/v4" import type { OpenAIResponsesInput, OpenAIResponsesReasoning } from "./openai-responses-api-types" @@ -25,17 +25,17 @@ export async function convertToOpenAIResponsesInput({ store, hasLocalShellTool = false, }: { - prompt: LanguageModelV2Prompt + prompt: LanguageModelV3Prompt systemMessageMode: "system" | "developer" | "remove" fileIdPrefixes?: readonly string[] store: boolean hasLocalShellTool?: boolean }): Promise<{ input: OpenAIResponsesInput - warnings: Array + warnings: Array }> { const input: OpenAIResponsesInput = [] - const warnings: Array = [] + const warnings: Array = [] for (const { role, content } of prompt) { switch (role) { @@ -118,7 +118,7 @@ export async function convertToOpenAIResponsesInput({ case "assistant": { const reasoningMessages: Record = {} - const toolCallParts: Record = {} + const toolCallParts: Record = {} for (const part of content) { switch (part.type) { @@ -250,7 +250,10 @@ export async function convertToOpenAIResponsesInput({ } case "tool": { - for (const part of content) { + for (const partRaw of content) { + // Skip tool approval responses (V3 addition) - only process tool results + if (partRaw.type !== "tool-result") continue + const part = partRaw const output = part.output if (hasLocalShellTool && part.toolName === "local_shell" && output.type === "json") { @@ -262,7 +265,7 @@ export async function convertToOpenAIResponsesInput({ break } - let contentValue: string + let contentValue = "" switch (output.type) { case "text": case "error-text": @@ -273,6 +276,14 @@ export async function convertToOpenAIResponsesInput({ case "error-json": contentValue = JSON.stringify(output.value) break + default: { + // Unknown tool output type — skip rather than forward an empty string + warnings.push({ + type: "other", + message: `Unsupported tool output type: ${(output as any).type} for tool ${part.toolName} — skipping`, + }) + continue + } } input.push({ diff --git a/packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts b/packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts index 54bb9056d79e..be9a44972f50 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts @@ -1,4 +1,4 @@ -import type { LanguageModelV2FinishReason } from "@ai-sdk/provider" +type V3FinishReasonUnified = "stop" | "length" | "content-filter" | "tool-calls" | "error" | "other" export function mapOpenAIResponseFinishReason({ finishReason, @@ -7,7 +7,7 @@ export function mapOpenAIResponseFinishReason({ finishReason: string | null | undefined // flag that checks if there have been client-side tool calls (not executed by openai) hasFunctionCall: boolean -}): LanguageModelV2FinishReason { +}): V3FinishReasonUnified { switch (finishReason) { case undefined: case null: @@ -17,6 +17,6 @@ export function mapOpenAIResponseFinishReason({ case "content_filter": return "content-filter" default: - return hasFunctionCall ? "tool-calls" : "unknown" + return hasFunctionCall ? "tool-calls" : "other" } } diff --git a/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts b/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts index 0a575bc02bb7..c9ddb3b4b361 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts @@ -1,13 +1,11 @@ import { APICallError, - type LanguageModelV2, - type LanguageModelV2CallWarning, - type LanguageModelV2Content, - type LanguageModelV2FinishReason, - type LanguageModelV2ProviderDefinedTool, - type LanguageModelV2StreamPart, - type LanguageModelV2Usage, - type SharedV2ProviderMetadata, + type JSONValue, + type LanguageModelV3, + type LanguageModelV3Content, + type LanguageModelV3ProviderTool, + type LanguageModelV3StreamPart, + type SharedV3ProviderMetadata, } from "@ai-sdk/provider" import { combineHeaders, @@ -30,6 +28,7 @@ import type { OpenAIResponsesIncludeOptions, OpenAIResponsesIncludeValue } from import { prepareResponsesTools } from "./openai-responses-prepare-tools" import type { OpenAIResponsesModelId } from "./openai-responses-settings" import { localShellInputSchema } from "./tool/local-shell" +import { type SharedV3Warning, unsupportedSetting } from "../warnings" const webSearchCallItem = z.object({ type: z.literal("web_search_call"), @@ -128,8 +127,8 @@ const LOGPROBS_SCHEMA = z.array( }), ) -export class OpenAIResponsesLanguageModel implements LanguageModelV2 { - readonly specificationVersion = "v2" +export class OpenAIResponsesLanguageModel implements LanguageModelV3 { + readonly specificationVersion = "v3" as const readonly modelId: OpenAIResponsesModelId @@ -163,34 +162,28 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { tools, toolChoice, responseFormat, - }: Parameters[0]) { - const warnings: LanguageModelV2CallWarning[] = [] + }: Parameters[0]) { + const warnings: SharedV3Warning[] = [] const modelConfig = getResponsesModelConfig(this.modelId) if (topK != null) { - warnings.push({ type: "unsupported-setting", setting: "topK" }) + warnings.push(unsupportedSetting("topK")) } if (seed != null) { - warnings.push({ type: "unsupported-setting", setting: "seed" }) + warnings.push(unsupportedSetting("seed")) } if (presencePenalty != null) { - warnings.push({ - type: "unsupported-setting", - setting: "presencePenalty", - }) + warnings.push(unsupportedSetting("presencePenalty")) } if (frequencyPenalty != null) { - warnings.push({ - type: "unsupported-setting", - setting: "frequencyPenalty", - }) + warnings.push(unsupportedSetting("frequencyPenalty")) } if (stopSequences != null) { - warnings.push({ type: "unsupported-setting", setting: "stopSequences" }) + warnings.push(unsupportedSetting("stopSequences")) } const openaiOptions = await parseProviderOptions({ @@ -218,7 +211,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { } function hasOpenAITool(id: string) { - return tools?.find((tool) => tool.type === "provider-defined" && tool.id === id) != null + return tools?.find((tool) => tool.type === "provider" && tool.id === id) != null } // when logprobs are requested, automatically include them: @@ -237,9 +230,8 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { const webSearchToolName = ( tools?.find( (tool) => - tool.type === "provider-defined" && - (tool.id === "openai.web_search" || tool.id === "openai.web_search_preview"), - ) as LanguageModelV2ProviderDefinedTool | undefined + tool.type === "provider" && (tool.id === "openai.web_search" || tool.id === "openai.web_search_preview"), + ) as LanguageModelV3ProviderTool | undefined )?.name if (webSearchToolName) { @@ -315,8 +307,8 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { if (baseArgs.temperature != null) { baseArgs.temperature = undefined warnings.push({ - type: "unsupported-setting", - setting: "temperature", + type: "unsupported", + feature: "temperature", details: "temperature is not supported for reasoning models", }) } @@ -324,24 +316,24 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { if (baseArgs.top_p != null) { baseArgs.top_p = undefined warnings.push({ - type: "unsupported-setting", - setting: "topP", + type: "unsupported", + feature: "topP", details: "topP is not supported for reasoning models", }) } } else { if (openaiOptions?.reasoningEffort != null) { warnings.push({ - type: "unsupported-setting", - setting: "reasoningEffort", + type: "unsupported", + feature: "reasoningEffort", details: "reasoningEffort is not supported for non-reasoning models", }) } if (openaiOptions?.reasoningSummary != null) { warnings.push({ - type: "unsupported-setting", - setting: "reasoningSummary", + type: "unsupported", + feature: "reasoningSummary", details: "reasoningSummary is not supported for non-reasoning models", }) } @@ -350,8 +342,8 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { // Validate flex processing support if (openaiOptions?.serviceTier === "flex" && !modelConfig.supportsFlexProcessing) { warnings.push({ - type: "unsupported-setting", - setting: "serviceTier", + type: "unsupported", + feature: "serviceTier", details: "flex processing is only available for o3, o4-mini, and gpt-5 models", }) // Remove from args if not supported @@ -361,8 +353,8 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { // Validate priority processing support if (openaiOptions?.serviceTier === "priority" && !modelConfig.supportsPriorityProcessing) { warnings.push({ - type: "unsupported-setting", - setting: "serviceTier", + type: "unsupported", + feature: "serviceTier", details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported", }) @@ -392,8 +384,8 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { } async doGenerate( - options: Parameters[0], - ): Promise>> { + options: Parameters[0], + ): Promise>> { const { args: body, warnings, webSearchToolName } = await this.getArgs(options) const url = this.config.url({ path: "/responses", @@ -508,7 +500,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { }) } - const content: Array = [] + const content: Array = [] const logprobs: Array> = [] // flag that checks if there have been client-side tool calls (not executed by openai) @@ -544,7 +536,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: part.id, toolName: "image_generation", input: "{}", - providerExecuted: true, }) content.push({ @@ -554,7 +545,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { result: { result: part.result, } satisfies z.infer, - providerExecuted: true, }) break @@ -640,7 +630,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: part.id, toolName: webSearchToolName ?? "web_search", input: JSON.stringify({ action: part.action }), - providerExecuted: true, }) content.push({ @@ -648,7 +637,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: part.id, toolName: webSearchToolName ?? "web_search", result: { status: part.status }, - providerExecuted: true, }) break @@ -660,7 +648,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: part.id, toolName: "computer_use", input: "", - providerExecuted: true, }) content.push({ @@ -671,7 +658,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { type: "computer_use_tool_result", status: part.status || "completed", }, - providerExecuted: true, }) break } @@ -682,7 +668,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: part.id, toolName: "file_search", input: "{}", - providerExecuted: true, }) content.push({ @@ -693,14 +678,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { queries: part.queries, results: part.results?.map((result) => ({ - attributes: result.attributes, + attributes: result.attributes as Record, fileId: result.file_id, filename: result.filename, score: result.score, text: result.text, })) ?? null, } satisfies z.infer, - providerExecuted: true, }) break } @@ -714,7 +698,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { code: part.code, containerId: part.container_id, } satisfies z.infer), - providerExecuted: true, }) content.push({ @@ -724,14 +707,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { result: { outputs: part.outputs, } satisfies z.infer, - providerExecuted: true, }) break } } } - const providerMetadata: SharedV2ProviderMetadata = { + const providerMetadata: SharedV3ProviderMetadata = { openai: { responseId: response.id }, } @@ -743,18 +725,28 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { providerMetadata.openai.serviceTier = response.service_tier } + const rawFinishReason = response.incomplete_details?.reason return { content, - finishReason: mapOpenAIResponseFinishReason({ - finishReason: response.incomplete_details?.reason, - hasFunctionCall, - }), + finishReason: { + unified: mapOpenAIResponseFinishReason({ + finishReason: rawFinishReason, + hasFunctionCall, + }), + raw: rawFinishReason, + }, usage: { - inputTokens: response.usage.input_tokens, - outputTokens: response.usage.output_tokens, - totalTokens: response.usage.input_tokens + response.usage.output_tokens, - reasoningTokens: response.usage.output_tokens_details?.reasoning_tokens ?? undefined, - cachedInputTokens: response.usage.input_tokens_details?.cached_tokens ?? undefined, + inputTokens: { + total: response.usage.input_tokens, + noCache: undefined, + cacheRead: response.usage.input_tokens_details?.cached_tokens ?? undefined, + cacheWrite: undefined, + }, + outputTokens: { + total: response.usage.output_tokens, + text: undefined, + reasoning: response.usage.output_tokens_details?.reasoning_tokens ?? undefined, + }, }, request: { body }, response: { @@ -770,8 +762,8 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { } async doStream( - options: Parameters[0], - ): Promise>> { + options: Parameters[0], + ): Promise>> { const { args: body, warnings, webSearchToolName } = await this.getArgs(options) const { responseHeaders, value: response } = await postJsonToApi({ @@ -792,11 +784,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { const self = this - let finishReason: LanguageModelV2FinishReason = "unknown" - const usage: LanguageModelV2Usage = { - inputTokens: undefined, - outputTokens: undefined, - totalTokens: undefined, + let finishReason: "stop" | "length" | "content-filter" | "tool-calls" | "error" | "other" = "other" + let finishReasonRaw: string | undefined + const usageTokens = { + inputTotal: undefined as number | undefined, + inputCacheRead: undefined as number | undefined, + outputTotal: undefined as number | undefined, + outputReasoning: undefined as number | undefined, } const logprobs: Array> = [] let responseId: string | null = null @@ -837,7 +831,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { return { stream: response.pipeThrough( - new TransformStream>, LanguageModelV2StreamPart>({ + new TransformStream>, LanguageModelV3StreamPart>({ start(controller) { controller.enqueue({ type: "stream-start", warnings }) }, @@ -916,7 +910,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: value.item.id, toolName: "file_search", input: "{}", - providerExecuted: true, }) } else if (value.item.type === "image_generation_call") { controller.enqueue({ @@ -924,7 +917,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: value.item.id, toolName: "image_generation", input: "{}", - providerExecuted: true, }) } else if (value.item.type === "message") { // Start a stable text part for this assistant message @@ -991,7 +983,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: value.item.id, toolName: "web_search", input: JSON.stringify({ action: value.item.action }), - providerExecuted: true, }) controller.enqueue({ @@ -999,7 +990,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: value.item.id, toolName: "web_search", result: { status: value.item.status }, - providerExecuted: true, }) } else if (value.item.type === "computer_call") { ongoingToolCalls[value.output_index] = undefined @@ -1014,7 +1004,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { toolCallId: value.item.id, toolName: "computer_use", input: "", - providerExecuted: true, }) controller.enqueue({ @@ -1025,7 +1014,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { type: "computer_use_tool_result", status: value.item.status || "completed", }, - providerExecuted: true, }) } else if (value.item.type === "file_search_call") { ongoingToolCalls[value.output_index] = undefined @@ -1038,14 +1026,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { queries: value.item.queries, results: value.item.results?.map((result) => ({ - attributes: result.attributes, + attributes: result.attributes as Record, fileId: result.file_id, filename: result.filename, score: result.score, text: result.text, })) ?? null, } satisfies z.infer, - providerExecuted: true, }) } else if (value.item.type === "code_interpreter_call") { ongoingToolCalls[value.output_index] = undefined @@ -1057,7 +1044,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { result: { outputs: value.item.outputs, } satisfies z.infer, - providerExecuted: true, }) } else if (value.item.type === "image_generation_call") { controller.enqueue({ @@ -1067,7 +1053,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { result: { result: value.item.result, } satisfies z.infer, - providerExecuted: true, }) } else if (value.item.type === "local_shell_call") { ongoingToolCalls[value.output_index] = undefined @@ -1137,7 +1122,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { result: { result: value.partial_image_b64, } satisfies z.infer, - providerExecuted: true, }) } else if (isResponseCodeInterpreterCallCodeDeltaChunk(value)) { const toolCall = ongoingToolCalls[value.output_index] @@ -1175,7 +1159,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { code: value.code, containerId: toolCall.codeInterpreter!.containerId, } satisfies z.infer), - providerExecuted: true, }) } } else if (isResponseCreatedChunk(value)) { @@ -1244,15 +1227,15 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { }) } } else if (isResponseFinishedChunk(value)) { + finishReasonRaw = value.response.incomplete_details?.reason finishReason = mapOpenAIResponseFinishReason({ - finishReason: value.response.incomplete_details?.reason, + finishReason: finishReasonRaw, hasFunctionCall, }) - usage.inputTokens = value.response.usage.input_tokens - usage.outputTokens = value.response.usage.output_tokens - usage.totalTokens = value.response.usage.input_tokens + value.response.usage.output_tokens - usage.reasoningTokens = value.response.usage.output_tokens_details?.reasoning_tokens ?? undefined - usage.cachedInputTokens = value.response.usage.input_tokens_details?.cached_tokens ?? undefined + usageTokens.inputTotal = value.response.usage.input_tokens + usageTokens.inputCacheRead = value.response.usage.input_tokens_details?.cached_tokens ?? undefined + usageTokens.outputTotal = value.response.usage.output_tokens + usageTokens.outputReasoning = value.response.usage.output_tokens_details?.reasoning_tokens ?? undefined if (typeof value.response.service_tier === "string") { serviceTier = value.response.service_tier } @@ -1287,7 +1270,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { currentTextId = null } - const providerMetadata: SharedV2ProviderMetadata = { + const providerMetadata: SharedV3ProviderMetadata = { openai: { responseId, }, @@ -1303,8 +1286,20 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 { controller.enqueue({ type: "finish", - finishReason, - usage, + finishReason: { unified: finishReason, raw: finishReasonRaw }, + usage: { + inputTokens: { + total: usageTokens.inputTotal, + noCache: undefined, + cacheRead: usageTokens.inputCacheRead, + cacheWrite: undefined, + }, + outputTokens: { + total: usageTokens.outputTotal, + text: undefined, + reasoning: usageTokens.outputReasoning, + }, + }, providerMetadata, }) }, @@ -1687,12 +1682,11 @@ function getResponsesModelConfig(modelId: string): ResponsesModelConfig { } } -// TODO AI SDK 6: use optional here instead of nullish const openaiResponsesProviderOptionsSchema = z.object({ include: z .array(z.enum(["reasoning.encrypted_content", "file_search_call.results", "message.output_text.logprobs"])) - .nullish(), - instructions: z.string().nullish(), + .optional(), + instructions: z.string().optional(), /** * Return the log probabilities of the tokens. @@ -1713,20 +1707,20 @@ const openaiResponsesProviderOptionsSchema = z.object({ * This maximum number applies across all built-in tool calls, not per individual tool. * Any further attempts to call a tool by the model will be ignored. */ - maxToolCalls: z.number().nullish(), - - metadata: z.any().nullish(), - parallelToolCalls: z.boolean().nullish(), - previousResponseId: z.string().nullish(), - promptCacheKey: z.string().nullish(), - reasoningEffort: z.string().nullish(), - reasoningSummary: z.string().nullish(), - safetyIdentifier: z.string().nullish(), - serviceTier: z.enum(["auto", "flex", "priority"]).nullish(), - store: z.boolean().nullish(), - strictJsonSchema: z.boolean().nullish(), - textVerbosity: z.enum(["low", "medium", "high"]).nullish(), - user: z.string().nullish(), + maxToolCalls: z.number().optional(), + + metadata: z.any().optional(), + parallelToolCalls: z.boolean().optional(), + previousResponseId: z.string().optional(), + promptCacheKey: z.string().optional(), + reasoningEffort: z.string().optional(), + reasoningSummary: z.string().optional(), + safetyIdentifier: z.string().optional(), + serviceTier: z.enum(["auto", "flex", "priority"]).optional(), + store: z.boolean().optional(), + strictJsonSchema: z.boolean().optional(), + textVerbosity: z.enum(["low", "medium", "high"]).optional(), + user: z.string().optional(), }) export type OpenAIResponsesProviderOptions = z.infer diff --git a/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts b/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts index 791de3e7cfa7..214fb2b6b3d7 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts @@ -1,8 +1,5 @@ -import { - type LanguageModelV2CallOptions, - type LanguageModelV2CallWarning, - UnsupportedFunctionalityError, -} from "@ai-sdk/provider" +import { type LanguageModelV3CallOptions, UnsupportedFunctionalityError } from "@ai-sdk/provider" +import { type SharedV3Warning, unsupportedTool } from "../warnings" import { codeInterpreterArgsSchema } from "./tool/code-interpreter" import { fileSearchArgsSchema } from "./tool/file-search" import { webSearchArgsSchema } from "./tool/web-search" @@ -15,8 +12,8 @@ export function prepareResponsesTools({ toolChoice, strictJsonSchema, }: { - tools: LanguageModelV2CallOptions["tools"] - toolChoice?: LanguageModelV2CallOptions["toolChoice"] + tools: LanguageModelV3CallOptions["tools"] + toolChoice?: LanguageModelV3CallOptions["toolChoice"] strictJsonSchema: boolean }): { tools?: Array @@ -30,12 +27,12 @@ export function prepareResponsesTools({ | { type: "function"; name: string } | { type: "code_interpreter" } | { type: "image_generation" } - toolWarnings: LanguageModelV2CallWarning[] + toolWarnings: SharedV3Warning[] } { // when the tools array is empty, change it to undefined to prevent errors: tools = tools?.length ? tools : undefined - const toolWarnings: LanguageModelV2CallWarning[] = [] + const toolWarnings: SharedV3Warning[] = [] if (tools == null) { return { tools: undefined, toolChoice: undefined, toolWarnings } @@ -54,7 +51,7 @@ export function prepareResponsesTools({ strict: strictJsonSchema, }) break - case "provider-defined": { + case "provider": { switch (tool.id) { case "openai.file_search": { const args = fileSearchArgsSchema.parse(tool.args) @@ -138,7 +135,7 @@ export function prepareResponsesTools({ break } default: - toolWarnings.push({ type: "unsupported-tool", tool }) + toolWarnings.push(unsupportedTool(tool)) break } } diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/code-interpreter.ts b/packages/opencode/src/provider/sdk/copilot/responses/tool/code-interpreter.ts index 2bb4bce778d2..909694ec7d69 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/tool/code-interpreter.ts @@ -1,4 +1,4 @@ -import { createProviderDefinedToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils" +import { createProviderToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils" import { z } from "zod/v4" export const codeInterpreterInputSchema = z.object({ @@ -37,7 +37,7 @@ type CodeInterpreterArgs = { container?: string | { fileIds?: string[] } } -export const codeInterpreterToolFactory = createProviderDefinedToolFactoryWithOutputSchema< +export const codeInterpreterToolFactory = createProviderToolFactoryWithOutputSchema< { /** * The code to run, or null if not available. @@ -76,7 +76,6 @@ export const codeInterpreterToolFactory = createProviderDefinedToolFactoryWithOu CodeInterpreterArgs >({ id: "openai.code_interpreter", - name: "code_interpreter", inputSchema: codeInterpreterInputSchema, outputSchema: codeInterpreterOutputSchema, }) diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/file-search.ts b/packages/opencode/src/provider/sdk/copilot/responses/tool/file-search.ts index 1fccddaf63b4..12a490e19dd6 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/tool/file-search.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/tool/file-search.ts @@ -1,4 +1,4 @@ -import { createProviderDefinedToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils" +import { createProviderToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils" import type { OpenAIResponsesFileSearchToolComparisonFilter, OpenAIResponsesFileSearchToolCompoundFilter, @@ -43,7 +43,7 @@ export const fileSearchOutputSchema = z.object({ .nullable(), }) -export const fileSearch = createProviderDefinedToolFactoryWithOutputSchema< +export const fileSearch = createProviderToolFactoryWithOutputSchema< {}, { /** @@ -122,7 +122,6 @@ export const fileSearch = createProviderDefinedToolFactoryWithOutputSchema< } >({ id: "openai.file_search", - name: "file_search", inputSchema: z.object({}), outputSchema: fileSearchOutputSchema, }) diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/image-generation.ts b/packages/opencode/src/provider/sdk/copilot/responses/tool/image-generation.ts index 7367a4802b7d..b67bb76f9ce4 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/tool/image-generation.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/tool/image-generation.ts @@ -1,4 +1,4 @@ -import { createProviderDefinedToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils" +import { createProviderToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils" import { z } from "zod/v4" export const imageGenerationArgsSchema = z @@ -92,7 +92,7 @@ type ImageGenerationArgs = { size?: "auto" | "1024x1024" | "1024x1536" | "1536x1024" } -const imageGenerationToolFactory = createProviderDefinedToolFactoryWithOutputSchema< +const imageGenerationToolFactory = createProviderToolFactoryWithOutputSchema< {}, { /** @@ -103,7 +103,6 @@ const imageGenerationToolFactory = createProviderDefinedToolFactoryWithOutputSch ImageGenerationArgs >({ id: "openai.image_generation", - name: "image_generation", inputSchema: z.object({}), outputSchema: imageGenerationOutputSchema, }) diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/local-shell.ts b/packages/opencode/src/provider/sdk/copilot/responses/tool/local-shell.ts index 4ceca0d6cd84..45230d5ce55d 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/tool/local-shell.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/tool/local-shell.ts @@ -1,4 +1,4 @@ -import { createProviderDefinedToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils" +import { createProviderToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils" import { z } from "zod/v4" export const localShellInputSchema = z.object({ @@ -16,7 +16,7 @@ export const localShellOutputSchema = z.object({ output: z.string(), }) -export const localShell = createProviderDefinedToolFactoryWithOutputSchema< +export const localShell = createProviderToolFactoryWithOutputSchema< { /** * Execute a shell command on the server. @@ -59,7 +59,6 @@ export const localShell = createProviderDefinedToolFactoryWithOutputSchema< {} >({ id: "openai.local_shell", - name: "local_shell", inputSchema: localShellInputSchema, outputSchema: localShellOutputSchema, }) diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search-preview.ts b/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search-preview.ts index 69ea65ef0e50..3d9a308d8ac0 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search-preview.ts @@ -1,4 +1,4 @@ -import { createProviderDefinedToolFactory } from "@ai-sdk/provider-utils" +import { createProviderToolFactory } from "@ai-sdk/provider-utils" import { z } from "zod/v4" // Args validation schema @@ -40,7 +40,7 @@ export const webSearchPreviewArgsSchema = z.object({ .optional(), }) -export const webSearchPreview = createProviderDefinedToolFactory< +export const webSearchPreview = createProviderToolFactory< { // Web search doesn't take input parameters - it's controlled by the prompt }, @@ -81,7 +81,6 @@ export const webSearchPreview = createProviderDefinedToolFactory< } >({ id: "openai.web_search_preview", - name: "web_search_preview", inputSchema: z.object({ action: z .discriminatedUnion("type", [ diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search.ts b/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search.ts index 89622ad3cea4..e380bb13b62a 100644 --- a/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search.ts +++ b/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search.ts @@ -1,4 +1,4 @@ -import { createProviderDefinedToolFactory } from "@ai-sdk/provider-utils" +import { createProviderToolFactory } from "@ai-sdk/provider-utils" import { z } from "zod/v4" export const webSearchArgsSchema = z.object({ @@ -21,7 +21,7 @@ export const webSearchArgsSchema = z.object({ .optional(), }) -export const webSearchToolFactory = createProviderDefinedToolFactory< +export const webSearchToolFactory = createProviderToolFactory< { // Web search doesn't take input parameters - it's controlled by the prompt }, @@ -74,7 +74,6 @@ export const webSearchToolFactory = createProviderDefinedToolFactory< } >({ id: "openai.web_search", - name: "web_search", inputSchema: z.object({ action: z .discriminatedUnion("type", [ diff --git a/packages/opencode/src/provider/sdk/copilot/warnings.ts b/packages/opencode/src/provider/sdk/copilot/warnings.ts new file mode 100644 index 000000000000..02b60130bc64 --- /dev/null +++ b/packages/opencode/src/provider/sdk/copilot/warnings.ts @@ -0,0 +1,23 @@ +import type { SharedV3Warning } from "@ai-sdk/provider" + +export type { SharedV3Warning } + +export function unsupportedSetting(setting: string, details?: string): SharedV3Warning { + return { type: "unsupported", feature: setting, details } +} + +export function unsupportedTool( + tool?: { type: string; id?: string; name?: string }, + details?: string, +): SharedV3Warning { + // Build a descriptive identifier so callers can pinpoint which tool was skipped. + // Falls back to the full JSON serialisation when neither id nor name is present. + const desc = tool + ? tool.id + ? `${tool.type}:${tool.id}` + : tool.name + ? `${tool.type}:${tool.name}` + : JSON.stringify(tool) + : undefined + return { type: "unsupported", feature: "tool", details: desc ?? details } +} diff --git a/packages/opencode/src/provider/shim.ts b/packages/opencode/src/provider/shim.ts new file mode 100644 index 000000000000..d5d3278d1761 --- /dev/null +++ b/packages/opencode/src/provider/shim.ts @@ -0,0 +1,138 @@ +/** + * v2→v3 provider/model shim for AI SDK legacy compatibility. + * + * AI SDK v6 uses specification version "v3" for providers and language models. + * Third-party packages that were built against AI SDK v5 may still produce + * v2-spec objects. This module wraps those objects transparently so all + * downstream code in opencode consistently sees v3-spec providers and models. + * + * The shim mirrors the internal asProviderV3 / asLanguageModelV3 logic from + * the "ai" package (which is not exported publicly) and adds structured logging + * so legacy providers are easy to identify at runtime. + * + * Fast-path: already-v3 objects are returned unchanged with no overhead. + */ + +import { Log } from "../util/log" + +const log = Log.create({ service: "provider-shim" }) + +function convertFinishReason(reason: string) { + return { unified: reason === "unknown" ? "other" : reason, raw: undefined } +} + +function convertUsage(u: { + inputTokens?: number + outputTokens?: number + cachedInputTokens?: number + reasoningTokens?: number +}) { + return { + inputTokens: { + total: u.inputTokens, + noCache: undefined, + cacheRead: u.cachedInputTokens, + cacheWrite: undefined, + }, + outputTokens: { + total: u.outputTokens, + text: undefined, + reasoning: u.reasoningTokens, + }, + } +} + +function convertStream(s: ReadableStream) { + return s.pipeThrough( + new TransformStream({ + transform(chunk, controller) { + if (chunk.type === "finish") { + controller.enqueue({ + ...chunk, + finishReason: convertFinishReason(chunk.finishReason), + usage: convertUsage(chunk.usage), + }) + } else { + controller.enqueue(chunk) + } + }, + }), + ) +} + +export function shimLanguageModel(model: any): any { + if (model.specificationVersion === "v3") return model + log.info("shimming v2 language model to v3", { + provider: model.provider, + modelId: model.modelId, + }) + return new Proxy(model, { + get(target, prop) { + switch (prop) { + case "specificationVersion": + return "v3" + case "doGenerate": + return async (...args: any[]) => { + const result = await target.doGenerate(...args) + return { + ...result, + finishReason: convertFinishReason(result.finishReason), + usage: convertUsage(result.usage), + } + } + case "doStream": + return async (...args: any[]) => { + const result = await target.doStream(...args) + return { ...result, stream: convertStream(result.stream) } + } + default: + return target[prop] + } + }, + }) +} + +export function shimProvider(sdk: any): any { + if (sdk.specificationVersion === "v3") return sdk + log.info("shimming v2 provider to v3", { provider: sdk.provider ?? sdk.id ?? "(unknown)" }) + + // Use a Proxy rather than a plain object so that callable providers (e.g. + // SAP AI Core exposes provider(modelId) as a shorthand) keep working after + // shimming. The apply trap forwards calls to the original and shims the + // returned language model. The get trap layers v3 property overrides on + // top of the original properties. + // + // NOTE: The apply trap is only reachable when the target is callable. For + // non-callable (plain object) providers it is never invoked — JavaScript + // raises a TypeError before the trap fires when you attempt to call a + // non-function Proxy. We guard with `typeof sdk === "function"` so we only + // emit the trap for providers that are actually callable. + const handler: ProxyHandler = { + get(target, prop) { + switch (prop) { + case "specificationVersion": + return "v3" + case "languageModel": + return (id: string) => shimLanguageModel(target.languageModel(id)) + case "embeddingModel": + // v2 providers expose textEmbeddingModel; v3 renames it to embeddingModel + return target.textEmbeddingModel + ? (id: string) => target.textEmbeddingModel(id) + : target.embeddingModel + ? (id: string) => target.embeddingModel(id) + : undefined + case "rerankingModel": + // v2 providers have no reranking concept + return undefined + default: + return target[prop] + } + }, + } + + if (typeof sdk === "function") { + handler.apply = (target, thisArg, args) => shimLanguageModel(Reflect.apply(target, thisArg, args)) + } + + return new Proxy(sdk, handler) +} diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 6980be051888..c0aec3bb1e63 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1,4 +1,4 @@ -import type { ModelMessage } from "ai" +import type { ModelMessage, ToolCallPart, ToolResultPart } from "ai" import { mergeDeep, unique } from "remeda" import type { JSONSchema7 } from "@ai-sdk/provider" import type { JSONSchema } from "zod/v4/core" @@ -26,16 +26,18 @@ export namespace ProviderTransform { case "@ai-sdk/github-copilot": return "copilot" case "@ai-sdk/openai": - case "@ai-sdk/azure": return "openai" + case "@ai-sdk/azure": + return "azure" case "@ai-sdk/amazon-bedrock": return "bedrock" case "@ai-sdk/anthropic": case "@ai-sdk/google-vertex/anthropic": return "anthropic" - case "@ai-sdk/google-vertex": case "@ai-sdk/google": return "google" + case "@ai-sdk/google-vertex": + return "vertex" case "@ai-sdk/gateway": return "gateway" case "@openrouter/ai-sdk-provider": @@ -75,14 +77,14 @@ export namespace ProviderTransform { return msgs.map((msg) => { if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) { msg.content = msg.content.map((part) => { - if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) { + if (part.type === "tool-call" || part.type === "tool-result") { return { - ...part, - toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"), + ...(part as ToolCallPart | ToolResultPart), + toolCallId: (part as ToolCallPart | ToolResultPart).toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"), } } return part - }) + }) as typeof msg.content } return msg }) @@ -99,20 +101,20 @@ export namespace ProviderTransform { if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) { msg.content = msg.content.map((part) => { - if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) { + if (part.type === "tool-call" || part.type === "tool-result") { // Mistral requires alphanumeric tool call IDs with exactly 9 characters - const normalizedId = part.toolCallId + const normalizedId = (part as ToolCallPart | ToolResultPart).toolCallId .replace(/[^a-zA-Z0-9]/g, "") // Remove non-alphanumeric characters .substring(0, 9) // Take first 9 characters .padEnd(9, "0") // Pad with zeros if less than 9 characters return { - ...part, + ...(part as ToolCallPart | ToolResultPart), toolCallId: normalizedId, } } return part - }) + }) as typeof msg.content } result.push(msg) @@ -200,7 +202,8 @@ export namespace ProviderTransform { if (shouldUseContentOptions) { const lastContent = msg.content[msg.content.length - 1] if (lastContent && typeof lastContent === "object") { - lastContent.providerOptions = mergeDeep(lastContent.providerOptions ?? {}, providerOptions) + const part = lastContent as { providerOptions?: Record> } + part.providerOptions = mergeDeep(part.providerOptions ?? {}, providerOptions) continue } } @@ -266,7 +269,7 @@ export namespace ProviderTransform { // Remap providerOptions keys from stored providerID to expected SDK key const key = sdkKey(model.api.npm) - if (key && key !== model.providerID && model.api.npm !== "@ai-sdk/azure") { + if (key && key !== model.providerID) { const remap = (opts: Record | undefined) => { if (!opts) return opts if (!(model.providerID in opts)) return opts @@ -281,7 +284,10 @@ export namespace ProviderTransform { return { ...msg, providerOptions: remap(msg.providerOptions), - content: msg.content.map((part) => ({ ...part, providerOptions: remap(part.providerOptions) })), + content: msg.content.map((part) => { + const p = part as { providerOptions?: Record } + return { ...part, providerOptions: remap(p.providerOptions) } + }), } as typeof msg }) } @@ -454,20 +460,20 @@ export namespace ProviderTransform { ) case "@ai-sdk/cerebras": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/cerebras + // https://ai-sdk.dev/providers/ai-sdk-providers/cerebras case "@ai-sdk/togetherai": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/togetherai + // https://ai-sdk.dev/providers/ai-sdk-providers/togetherai case "@ai-sdk/xai": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/xai + // https://ai-sdk.dev/providers/ai-sdk-providers/xai case "@ai-sdk/deepinfra": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/deepinfra + // https://ai-sdk.dev/providers/ai-sdk-providers/deepinfra case "venice-ai-sdk-provider": // https://docs.venice.ai/overview/guides/reasoning-models#reasoning-effort case "@ai-sdk/openai-compatible": return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }])) case "@ai-sdk/azure": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure + // https://ai-sdk.dev/providers/ai-sdk-providers/azure if (id === "o1-mini") return {} const azureEfforts = ["low", "medium", "high"] if (id.includes("gpt-5-") || id === "gpt-5") { @@ -484,7 +490,7 @@ export namespace ProviderTransform { ]), ) case "@ai-sdk/openai": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai + // https://ai-sdk.dev/providers/ai-sdk-providers/openai if (id === "gpt-5-pro") return {} const openaiEfforts = iife(() => { if (id.includes("codex")) { @@ -515,9 +521,9 @@ export namespace ProviderTransform { ) case "@ai-sdk/anthropic": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic + // https://ai-sdk.dev/providers/ai-sdk-providers/anthropic case "@ai-sdk/google-vertex/anthropic": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider + // https://ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider if (isAnthropicAdaptive) { return Object.fromEntries( @@ -549,7 +555,7 @@ export namespace ProviderTransform { } case "@ai-sdk/amazon-bedrock": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock + // https://ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock if (isAnthropicAdaptive) { return Object.fromEntries( adaptiveEfforts.map((effort) => [ @@ -595,9 +601,9 @@ export namespace ProviderTransform { ) case "@ai-sdk/google-vertex": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex + // https://ai-sdk.dev/providers/ai-sdk-providers/google-vertex case "@ai-sdk/google": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-generative-ai + // https://ai-sdk.dev/providers/ai-sdk-providers/google-generative-ai if (id.includes("2.5")) { return { high: { @@ -632,15 +638,15 @@ export namespace ProviderTransform { ) case "@ai-sdk/mistral": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/mistral + // https://ai-sdk.dev/providers/ai-sdk-providers/mistral return {} case "@ai-sdk/cohere": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/cohere + // https://ai-sdk.dev/providers/ai-sdk-providers/cohere return {} case "@ai-sdk/groq": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/groq + // https://ai-sdk.dev/providers/ai-sdk-providers/groq const groqEffort = ["none", ...WIDELY_SUPPORTED_EFFORTS] return Object.fromEntries( groqEffort.map((effort) => [ @@ -652,7 +658,7 @@ export namespace ProviderTransform { ) case "@ai-sdk/perplexity": - // https://v5.ai-sdk.dev/providers/ai-sdk-providers/perplexity + // https://ai-sdk.dev/providers/ai-sdk-providers/perplexity return {} case "@mymediset/sap-ai-provider": diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 85049650c1f9..1f730b54e9cf 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -42,8 +42,8 @@ import { PermissionRoutes } from "./routes/permission" import { GlobalRoutes } from "./routes/global" import { MDNS } from "./mdns" -// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 -globalThis.AI_SDK_LOG_WARNINGS = false +// Disable AI SDK warning logger — the SDK reads `globalThis.AI_SDK_LOG_WARNINGS` +;(globalThis as Record).AI_SDK_LOG_WARNINGS = false export namespace Server { const log = Log.create({ service: "server" }) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 79884d641ea0..b03e35ce3fc2 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -207,7 +207,7 @@ When constructing the summary, try to stick to this template: tools: {}, system: [], messages: [ - ...MessageV2.toModelMessages(messages, model, { stripMedia: true }), + ...(await MessageV2.toModelMessages(messages, model, { stripMedia: true })), { role: "user", content: [ diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index b117632051f7..d9beb7701115 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -27,7 +27,7 @@ import { WorkspaceContext } from "../control-plane/workspace-context" import type { Provider } from "@/provider/provider" import { PermissionNext } from "@/permission/next" import { Global } from "@/global" -import type { LanguageModelV2Usage } from "@ai-sdk/provider" +import type { LanguageModelUsage } from "ai" import { iife } from "@/util/iife" export namespace Session { @@ -784,7 +784,7 @@ export namespace Session { export const getUsage = fn( z.object({ model: z.custom(), - usage: z.custom(), + usage: z.custom(), metadata: z.custom().optional(), }), (input) => { @@ -794,9 +794,9 @@ export namespace Session { } const inputTokens = safe(input.usage.inputTokens ?? 0) const outputTokens = safe(input.usage.outputTokens ?? 0) - const reasoningTokens = safe(input.usage.reasoningTokens ?? 0) + const reasoningTokens = safe(input.usage.outputTokenDetails?.reasoningTokens ?? 0) - const cacheReadInputTokens = safe(input.usage.cachedInputTokens ?? 0) + const cacheReadInputTokens = safe(input.usage.inputTokenDetails?.cacheReadTokens ?? 0) const cacheWriteInputTokens = safe( (input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? // @ts-expect-error diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 4e42fb0d2ec7..bd48b97bead1 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -11,6 +11,7 @@ import { tool, jsonSchema, } from "ai" +import type { LanguageModelV3 } from "@ai-sdk/provider" import { mergeDeep, pipe } from "remeda" import { ProviderTransform } from "@/provider/transform" import { Config } from "@/config/config" @@ -41,7 +42,7 @@ export namespace LLM { toolChoice?: "auto" | "required" | "none" } - export type StreamOutput = StreamTextResult + export type StreamOutput = StreamTextResult export async function stream(input: StreamInput) { const l = log @@ -232,9 +233,15 @@ export namespace LLM { ...input.messages, ], model: wrapLanguageModel({ - model: language, + // `getLanguage` returns `LanguageModel` from the `ai` package which is + // structurally identical to `LanguageModelV3` from `@ai-sdk/provider`. + // The cast is required because `wrapLanguageModel` is typed against the + // lower-level `@ai-sdk/provider` type. shimLanguageModel() in shim.ts + // guarantees all models entering here are already v3-spec. + model: language as LanguageModelV3, middleware: [ { + specificationVersion: "v3" as const, async transformParams(args) { if (args.type === "stream") { // @ts-expect-error diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 5b4e7bdbc044..60760340e24c 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -493,11 +493,11 @@ export namespace MessageV2 { }) export type WithParts = z.infer - export function toModelMessages( + export async function toModelMessages( input: WithParts[], model: Provider.Model, options?: { stripMedia?: boolean }, - ): ModelMessage[] { + ): Promise { const result: UIMessage[] = [] const toolNames = new Set() // Track media from tool results that need to be injected as user messages @@ -521,7 +521,7 @@ export namespace MessageV2 { return false })() - const toModelOutput = (output: unknown) => { + const toModelOutput = ({ output }: { output: unknown }) => { if (typeof output === "string") { return { type: "text", value: output } } @@ -719,7 +719,7 @@ export namespace MessageV2 { const tools = Object.fromEntries(Array.from(toolNames).map((toolName) => [toolName, { toModelOutput }])) - return convertToModelMessages( + return await convertToModelMessages( result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")), { //@ts-expect-error (convertToModelMessages expects a ToolSet but only actually needs tools[name]?.toModelOutput) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 4f77920cc987..20640380683f 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -10,7 +10,7 @@ import { SessionRevert } from "./revert" import { Session } from "." import { Agent } from "../agent/agent" import { Provider } from "../provider/provider" -import { type Tool as AITool, tool, jsonSchema, type ToolCallOptions, asSchema } from "ai" +import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai" import { SessionCompaction } from "./compaction" import { Instance } from "../project/instance" import { Bus } from "../bus" @@ -662,7 +662,7 @@ export namespace SessionPrompt { sessionID, system, messages: [ - ...MessageV2.toModelMessages(msgs, model), + ...(await MessageV2.toModelMessages(msgs, model)), ...(isLastStep ? [ { @@ -745,7 +745,7 @@ export namespace SessionPrompt { using _ = log.time("resolveTools") const tools: Record = {} - const context = (args: any, options: ToolCallOptions): Tool.Context => ({ + const context = (args: any, options: ToolExecutionOptions): Tool.Context => ({ sessionID: input.session.id, abort: options.abortSignal!, messageID: input.processor.message.id, @@ -831,7 +831,7 @@ export namespace SessionPrompt { const execute = item.execute if (!execute) continue - const transformed = ProviderTransform.schema(input.model, asSchema(item.inputSchema).jsonSchema) + const transformed = ProviderTransform.schema(input.model, await asSchema(item.inputSchema).jsonSchema) item.inputSchema = jsonSchema(transformed) // Wrap execute to add plugin hooks and format output item.execute = async (args, opts) => { @@ -944,10 +944,10 @@ export namespace SessionPrompt { metadata: { valid: true }, } }, - toModelOutput(result) { + toModelOutput(opts: any) { return { type: "text", - value: result.output, + value: opts.output.output, } }, }) @@ -1942,10 +1942,13 @@ NOTE: At any point in time through this workflow you should feel free to ask the }, ...(hasOnlySubtaskParts ? [{ role: "user" as const, content: subtaskParts.map((p) => p.prompt).join("\n") }] - : MessageV2.toModelMessages(contextMessages, model)), + : await MessageV2.toModelMessages(contextMessages, model)), ], }) - const text = await result.text.catch((err) => log.error("failed to generate title", { error: err })) + const text = await Promise.resolve(result.text).catch((err) => { + log.error("failed to generate title", { error: err }) + return undefined + }) if (text) { const cleaned = text .replace(/[\s\S]*?<\/think>\s*/g, "") diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts index 41028633e83e..ab0b59bb7f83 100644 --- a/packages/opencode/test/preload.ts +++ b/packages/opencode/test/preload.ts @@ -49,6 +49,12 @@ const cacheDir = path.join(dir, "cache", "opencode") await fs.mkdir(cacheDir, { recursive: true }) await fs.writeFile(path.join(cacheDir, "version"), "14") +// Set git identity for tests so commits work without a global ~/.gitconfig +process.env["GIT_AUTHOR_NAME"] = "opencode" +process.env["GIT_AUTHOR_EMAIL"] = "test@opencode.ai" +process.env["GIT_COMMITTER_NAME"] = "opencode" +process.env["GIT_COMMITTER_EMAIL"] = "test@opencode.ai" + // Clear provider env vars to ensure clean test state delete process.env["ANTHROPIC_API_KEY"] delete process.env["OPENAI_API_KEY"] diff --git a/packages/opencode/test/provider/copilot/copilot-chat-model.test.ts b/packages/opencode/test/provider/copilot/copilot-chat-model.test.ts index 562da4507d3f..bba25bae374e 100644 --- a/packages/opencode/test/provider/copilot/copilot-chat-model.test.ts +++ b/packages/opencode/test/provider/copilot/copilot-chat-model.test.ts @@ -1,6 +1,6 @@ import { OpenAICompatibleChatLanguageModel } from "@/provider/sdk/copilot/chat/openai-compatible-chat-language-model" import { describe, test, expect, mock } from "bun:test" -import type { LanguageModelV2Prompt } from "@ai-sdk/provider" +import type { LanguageModelV3Prompt } from "@ai-sdk/provider" async function convertReadableStreamToArray(stream: ReadableStream): Promise { const reader = stream.getReader() @@ -13,7 +13,7 @@ async function convertReadableStreamToArray(stream: ReadableStream): Promi return result } -const TEST_PROMPT: LanguageModelV2Prompt = [{ role: "user", content: [{ type: "text", text: "Hello" }] }] +const TEST_PROMPT: LanguageModelV3Prompt = [{ role: "user", content: [{ type: "text", text: "Hello" }] }] // Fixtures from copilot_test.exs const FIXTURES = { @@ -106,7 +106,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -123,7 +123,7 @@ describe("doStream", () => { { type: "text-delta", id: "txt-0", delta: " world" }, { type: "text-delta", id: "txt-0", delta: "!" }, { type: "text-end", id: "txt-0" }, - { type: "finish", finishReason: "stop" }, + { type: "finish", finishReason: { unified: "stop", raw: "stop" } }, ]) }) @@ -132,7 +132,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -201,10 +201,10 @@ describe("doStream", () => { const finish = parts.find((p) => p.type === "finish") expect(finish).toMatchObject({ type: "finish", - finishReason: "tool-calls", + finishReason: { unified: "tool-calls", raw: "tool_calls" }, usage: { - inputTokens: 19581, - outputTokens: 53, + inputTokens: { total: 19581 }, + outputTokens: { total: 53 }, }, }) }) @@ -214,7 +214,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -256,10 +256,10 @@ describe("doStream", () => { const finish = parts.find((p) => p.type === "finish") expect(finish).toMatchObject({ type: "finish", - finishReason: "stop", + finishReason: { unified: "stop", raw: "stop" }, usage: { - inputTokens: 5778, - outputTokens: 59, + inputTokens: { total: 5778 }, + outputTokens: { total: 59 }, }, providerMetadata: { copilot: { @@ -274,7 +274,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -315,7 +315,7 @@ describe("doStream", () => { const finish = parts.find((p) => p.type === "finish") expect(finish).toMatchObject({ type: "finish", - finishReason: "stop", + finishReason: { unified: "stop", raw: "stop" }, }) }) @@ -324,7 +324,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -388,10 +388,10 @@ describe("doStream", () => { const finish = parts.find((p) => p.type === "finish") expect(finish).toMatchObject({ type: "finish", - finishReason: "tool-calls", + finishReason: { unified: "tool-calls", raw: "tool_calls" }, usage: { - inputTokens: 3767, - outputTokens: 19, + inputTokens: { total: 3767 }, + outputTokens: { total: 19 }, }, }) }) @@ -401,7 +401,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -449,7 +449,7 @@ describe("doStream", () => { const finish = parts.find((p) => p.type === "finish") expect(finish).toMatchObject({ type: "finish", - finishReason: "tool-calls", + finishReason: { unified: "tool-calls", raw: "tool_calls" }, }) }) @@ -458,7 +458,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -487,7 +487,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -506,7 +506,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: false, }) @@ -524,7 +524,7 @@ describe("doStream", () => { const model = createModel(mockFetch) const { stream } = await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, includeRawChunks: true, }) @@ -554,7 +554,7 @@ describe("request body", () => { const model = createModel(mockFetch) await model.doStream({ - prompt: TEST_PROMPT, + prompt: TEST_PROMPT as any, tools: [ { type: "function", diff --git a/packages/opencode/test/provider/shim-integration.test.ts b/packages/opencode/test/provider/shim-integration.test.ts new file mode 100644 index 000000000000..4e68bf496269 --- /dev/null +++ b/packages/opencode/test/provider/shim-integration.test.ts @@ -0,0 +1,331 @@ +/** + * Integration tests for the v2→v3 shim against the real installed provider packages. + * + * These tests install the actual npm packages via BunProc.install (the same + * mechanism the runtime uses) then verify that shimProvider / shimLanguageModel + * work correctly against the real module shapes, not just hand-crafted fakes. + * + * If network is unavailable the packages will fail to install and all tests in + * this file are skipped automatically. + */ + +import { describe, test, expect, beforeAll } from "bun:test" +import { shimLanguageModel, shimProvider } from "../../src/provider/shim" +import { BunProc } from "../../src/bun" + +// --------------------------------------------------------------------------- +// Install real packages via BunProc.install, same as the runtime does. +// Tests are skipped if either package cannot be installed (e.g. no network). +// --------------------------------------------------------------------------- + +let createVenice: ((opts?: any) => any) | undefined +let createSAPAIProvider: ((opts?: any) => any) | undefined +let skipReason: string | undefined + +beforeAll(async () => { + const results = await Promise.allSettled([ + BunProc.install("venice-ai-sdk-provider", "latest"), + BunProc.install("@jerome-benoit/sap-ai-provider-v2", "latest"), + ]) + + const [venice, sap] = results + + if (venice.status === "rejected") { + skipReason = `venice-ai-sdk-provider install failed: ${venice.reason}` + return + } + if (sap.status === "rejected") { + skipReason = `@jerome-benoit/sap-ai-provider-v2 install failed: ${sap.reason}` + return + } + + const [veniceModule, sapModule] = await Promise.all([import(venice.value), import(sap.value)]) + + createVenice = veniceModule.createVenice + createSAPAIProvider = sapModule.createSAPAIProvider +}, 120_000) + +function skip() { + if (skipReason) return true + if (!createVenice || !createSAPAIProvider) return true + return false +} + +// --------------------------------------------------------------------------- +// venice-ai-sdk-provider (v2 object+callable, textEmbeddingModel) +// --------------------------------------------------------------------------- + +describe("venice-ai-sdk-provider real module", () => { + test("raw provider is callable and has specificationVersion undefined", () => { + if (skip()) return + const provider = createVenice!({ apiKey: "test-key" }) + expect(typeof provider).toBe("function") + expect(provider.specificationVersion).toBeUndefined() + }) + + test("raw language model has specificationVersion v2", () => { + if (skip()) return + const model = createVenice!({ apiKey: "test-key" }).languageModel("venice-uncensored") + expect(model.specificationVersion).toBe("v2") + }) + + test("raw provider has both textEmbeddingModel and embeddingModel", () => { + if (skip()) return + const provider = createVenice!({ apiKey: "test-key" }) + expect(typeof provider.textEmbeddingModel).toBe("function") + expect(typeof provider.embeddingModel).toBe("function") + }) + + test("shimProvider wraps venice to v3", () => { + if (skip()) return + expect(shimProvider(createVenice!({ apiKey: "test-key" })).specificationVersion).toBe("v3") + }) + + test("shimmed venice provider is still callable as a function", () => { + if (skip()) return + expect(() => shimProvider(createVenice!({ apiKey: "test-key" }))("venice-uncensored")).not.toThrow() + }) + + test("shimmed callable returns v3 language model", () => { + if (skip()) return + const model = shimProvider(createVenice!({ apiKey: "test-key" }))("venice-uncensored") + expect(model.specificationVersion).toBe("v3") + }) + + test("shimmed languageModel() returns v3 model", () => { + if (skip()) return + const model = shimProvider(createVenice!({ apiKey: "test-key" })).languageModel("venice-uncensored") + expect(model.specificationVersion).toBe("v3") + }) + + test("shimmed embeddingModel() routes to textEmbeddingModel on the raw provider", () => { + if (skip()) return + expect(() => shimProvider(createVenice!({ apiKey: "test-key" })).embeddingModel("venice-embedding")).not.toThrow() + }) + + test("shimmed venice language model preserves provider and modelId", () => { + if (skip()) return + const model = shimProvider(createVenice!({ apiKey: "test-key" })).languageModel("venice-llama-3-3-70b") + expect(model.provider).toContain("venice") + expect(model.modelId).toBe("venice-llama-3-3-70b") + }) + + test("shimmed model doGenerate is a function", () => { + if (skip()) return + const model = shimProvider(createVenice!({ apiKey: "test-key" })).languageModel("venice-uncensored") + expect(typeof model.doGenerate).toBe("function") + }) + + test("shimmed model doStream is a function", () => { + if (skip()) return + const model = shimProvider(createVenice!({ apiKey: "test-key" })).languageModel("venice-uncensored") + expect(typeof model.doStream).toBe("function") + }) + + test("shimProvider is idempotent on venice provider", () => { + if (skip()) return + const raw = createVenice!({ apiKey: "test-key" }) + const once = shimProvider(raw) + const twice = shimProvider(once) + expect(twice).toBe(once) + }) + + test("shimLanguageModel is idempotent on a shimmed venice model", () => { + if (skip()) return + const model = createVenice!({ apiKey: "test-key" }).languageModel("venice-uncensored") + const once = shimLanguageModel(model) + expect(shimLanguageModel(once)).toBe(once) + }) + + test("rerankingModel is undefined on shimmed venice provider", () => { + if (skip()) return + expect(shimProvider(createVenice!({ apiKey: "test-key" })).rerankingModel).toBeUndefined() + }) +}) + +// --------------------------------------------------------------------------- +// @jerome-benoit/sap-ai-provider-v2 (v2 callable, languageModel + textEmbeddingModel) +// --------------------------------------------------------------------------- + +describe("@jerome-benoit/sap-ai-provider-v2 real module", () => { + test("raw provider is callable and has specificationVersion undefined", () => { + if (skip()) return + const provider = createSAPAIProvider!() + expect(typeof provider).toBe("function") + expect(provider.specificationVersion).toBeUndefined() + }) + + test("raw language model via .languageModel() has specificationVersion v2", () => { + if (skip()) return + expect(createSAPAIProvider!().languageModel("anthropic--claude-4-sonnet").specificationVersion).toBe("v2") + }) + + test("raw language model via provider(id) call has specificationVersion v2", () => { + if (skip()) return + expect(createSAPAIProvider!()("anthropic--claude-4-sonnet").specificationVersion).toBe("v2") + }) + + test("raw provider has textEmbeddingModel (v2 name)", () => { + if (skip()) return + expect(typeof createSAPAIProvider!().textEmbeddingModel).toBe("function") + }) + + test("shimProvider wraps SAP provider to v3", () => { + if (skip()) return + expect(shimProvider(createSAPAIProvider!()).specificationVersion).toBe("v3") + }) + + test("shimmed SAP provider is still callable — sdk(modelId) pattern works", () => { + if (skip()) return + expect(() => shimProvider(createSAPAIProvider!())("anthropic--claude-4-sonnet")).not.toThrow() + }) + + test("sdk(modelId) call returns v3 language model", () => { + if (skip()) return + expect(shimProvider(createSAPAIProvider!())("anthropic--claude-4-sonnet").specificationVersion).toBe("v3") + }) + + test("sdk.languageModel(modelId) returns v3 language model", () => { + if (skip()) return + expect(shimProvider(createSAPAIProvider!()).languageModel("anthropic--claude-4-sonnet").specificationVersion).toBe( + "v3", + ) + }) + + test("both call paths return v3 models with same provider and modelId", () => { + if (skip()) return + const shimmed = shimProvider(createSAPAIProvider!()) + const byCall = shimmed("anthropic--claude-4-sonnet") + const byMethod = shimmed.languageModel("anthropic--claude-4-sonnet") + expect(byCall.specificationVersion).toBe("v3") + expect(byMethod.specificationVersion).toBe("v3") + expect(byCall.provider).toBe(byMethod.provider) + expect(byCall.modelId).toBe(byMethod.modelId) + }) + + test("shimmed SAP model preserves provider and modelId", () => { + if (skip()) return + const model = shimProvider(createSAPAIProvider!()).languageModel("anthropic--claude-4-sonnet") + expect(model.provider).toContain("sap") + expect(model.modelId).toBe("anthropic--claude-4-sonnet") + }) + + test("shimmed embeddingModel() routes to textEmbeddingModel on raw provider", () => { + if (skip()) return + expect(typeof shimProvider(createSAPAIProvider!()).embeddingModel).toBe("function") + }) + + test("shimmed model doGenerate is a function", () => { + if (skip()) return + expect(typeof shimProvider(createSAPAIProvider!()).languageModel("anthropic--claude-4-sonnet").doGenerate).toBe( + "function", + ) + }) + + test("shimmed model doStream is a function", () => { + if (skip()) return + expect(typeof shimProvider(createSAPAIProvider!()).languageModel("anthropic--claude-4-sonnet").doStream).toBe( + "function", + ) + }) + + test("shimProvider is idempotent on SAP provider", () => { + if (skip()) return + const raw = createSAPAIProvider!() + const once = shimProvider(raw) + const twice = shimProvider(once) + expect(twice).toBe(once) + }) + + test("shimLanguageModel is idempotent on shimmed SAP model", () => { + if (skip()) return + const model = createSAPAIProvider!().languageModel("anthropic--claude-4-sonnet") + const once = shimLanguageModel(model) + expect(shimLanguageModel(once)).toBe(once) + }) + + test("rerankingModel is undefined on shimmed SAP provider", () => { + if (skip()) return + expect(shimProvider(createSAPAIProvider!()).rerankingModel).toBeUndefined() + }) +}) + +// --------------------------------------------------------------------------- +// Cross-provider: verify shim converts v2→v3 on real model objects +// --------------------------------------------------------------------------- + +describe("shimLanguageModel on real v2 models", () => { + test("venice model: specificationVersion upgraded to v3", () => { + if (skip()) return + const raw = createVenice!({ apiKey: "test-key" }).languageModel("venice-uncensored") + expect(raw.specificationVersion).toBe("v2") + expect(shimLanguageModel(raw).specificationVersion).toBe("v3") + }) + + test("SAP model: specificationVersion upgraded to v3", () => { + if (skip()) return + const raw = createSAPAIProvider!().languageModel("anthropic--claude-4-sonnet") + expect(raw.specificationVersion).toBe("v2") + expect(shimLanguageModel(raw).specificationVersion).toBe("v3") + }) + + test("venice model: doGenerate wraps finishReason into v3 object shape", async () => { + if (skip()) return + const raw = createVenice!({ apiKey: "test-key" }).languageModel("venice-uncensored") + raw.doGenerate = async () => ({ + content: [], + finishReason: "stop", + usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }, + warnings: [], + }) + const result = await shimLanguageModel(raw).doGenerate({}) + expect(result.finishReason).toEqual({ unified: "stop", raw: undefined }) + expect(result.usage).toMatchObject({ + inputTokens: { total: 10 }, + outputTokens: { total: 5 }, + }) + }) + + test("SAP model: doGenerate wraps finishReason into v3 object shape", async () => { + if (skip()) return + const raw = createSAPAIProvider!().languageModel("anthropic--claude-4-sonnet") + raw.doGenerate = async () => ({ + content: [], + finishReason: "length", + usage: { inputTokens: 100, outputTokens: 50, totalTokens: 150, cachedInputTokens: 20 }, + warnings: [], + }) + const result = await shimLanguageModel(raw).doGenerate({}) + expect(result.finishReason).toEqual({ unified: "length", raw: undefined }) + expect(result.usage).toMatchObject({ + inputTokens: { total: 100, cacheRead: 20 }, + outputTokens: { total: 50 }, + }) + }) + + test("venice model: doStream wraps finish chunk into v3 shape", async () => { + if (skip()) return + const raw = createVenice!({ apiKey: "test-key" }).languageModel("venice-uncensored") + raw.doStream = async () => ({ + stream: new ReadableStream({ + start(c) { + c.enqueue({ type: "text-delta", textDelta: "hi" }) + c.enqueue({ type: "finish", finishReason: "stop", usage: { inputTokens: 5, outputTokens: 3 } }) + c.close() + }, + }), + }) + const { stream } = await shimLanguageModel(raw).doStream({}) + const chunks: any[] = [] + const reader = stream.getReader() + while (true) { + const { done, value } = await reader.read() + if (done) break + chunks.push(value) + } + const finish = chunks.find((c) => c.type === "finish") + expect(finish.finishReason).toEqual({ unified: "stop", raw: undefined }) + expect(finish.usage.inputTokens).toHaveProperty("total", 5) + expect(finish.usage.outputTokens).toHaveProperty("total", 3) + }) +}) diff --git a/packages/opencode/test/provider/shim.test.ts b/packages/opencode/test/provider/shim.test.ts new file mode 100644 index 000000000000..56329f3e9a94 --- /dev/null +++ b/packages/opencode/test/provider/shim.test.ts @@ -0,0 +1,562 @@ +import { describe, test, expect, mock } from "bun:test" +import { shimLanguageModel, shimProvider } from "../../src/provider/shim" + +// --------------------------------------------------------------------------- +// Helpers — minimal v2 / v3 fakes +// --------------------------------------------------------------------------- + +function makeV2LanguageModel(overrides: Record = {}) { + return { + specificationVersion: "v2" as const, + provider: "test-provider", + modelId: "test-model", + doGenerate: mock(async () => ({ + content: [], + finishReason: "stop", + usage: { inputTokens: 100, outputTokens: 50, totalTokens: 150 }, + warnings: [], + })), + doStream: mock(async () => ({ + stream: new ReadableStream({ + start(controller) { + controller.enqueue({ type: "text-delta", textDelta: "hello" }) + controller.enqueue({ + type: "finish", + finishReason: "stop", + usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }, + }) + controller.close() + }, + }), + })), + ...overrides, + } +} + +function makeV3LanguageModel(overrides: Record = {}) { + return { + specificationVersion: "v3" as const, + provider: "test-provider", + modelId: "test-model-v3", + doGenerate: mock(async () => ({})), + doStream: mock(async () => ({})), + ...overrides, + } +} + +function makeV2Provider(overrides: Record = {}) { + const model = makeV2LanguageModel() + return { + languageModel: mock((_id: string) => model), + textEmbeddingModel: mock((_id: string) => ({ specificationVersion: "v2", modelId: "embed" })), + imageModel: mock((_id: string) => ({ specificationVersion: "v2", modelId: "img" })), + transcriptionModel: undefined, + speechModel: undefined, + ...overrides, + } +} + +function makeCallableV2Provider(overrides: Record = {}) { + const model = makeV2LanguageModel() + const fn = Object.assign( + function (modelId: string) { + return model + }, + { + languageModel: mock((_id: string) => model), + textEmbeddingModel: mock((_id: string) => ({ specificationVersion: "v2", modelId: "embed" })), + imageModel: undefined, + transcriptionModel: undefined, + speechModel: undefined, + ...overrides, + }, + ) + return fn +} + +function makeV3Provider(overrides: Record = {}) { + return { + specificationVersion: "v3" as const, + languageModel: mock((_id: string) => makeV3LanguageModel()), + embeddingModel: mock((_id: string) => ({})), + imageModel: mock((_id: string) => ({})), + ...overrides, + } +} + +// Read all chunks from a ReadableStream into an array +async function collectStream(stream: ReadableStream) { + const chunks: any[] = [] + const reader = stream.getReader() + while (true) { + const { done, value } = await reader.read() + if (done) break + chunks.push(value) + } + return chunks +} + +// --------------------------------------------------------------------------- +// shimLanguageModel +// --------------------------------------------------------------------------- + +describe("shimLanguageModel", () => { + describe("fast-path", () => { + test("returns v3 model unchanged", () => { + const model = makeV3LanguageModel() + expect(shimLanguageModel(model)).toBe(model) + }) + + test("does not wrap twice — idempotent", () => { + const model = makeV2LanguageModel() + const once = shimLanguageModel(model) + const twice = shimLanguageModel(once) + expect(twice).toBe(once) + }) + }) + + describe("specificationVersion", () => { + test("reports v3 on shimmed model", () => { + const shimmed = shimLanguageModel(makeV2LanguageModel()) + expect(shimmed.specificationVersion).toBe("v3") + }) + + test("preserves other properties unchanged", () => { + const model = makeV2LanguageModel() + const shimmed = shimLanguageModel(model) + expect(shimmed.provider).toBe("test-provider") + expect(shimmed.modelId).toBe("test-model") + }) + }) + + describe("doGenerate", () => { + test("converts flat finishReason string to v3 object", async () => { + const shimmed = shimLanguageModel(makeV2LanguageModel()) + const result = await shimmed.doGenerate({}) + expect(result.finishReason).toEqual({ unified: "stop", raw: undefined }) + }) + + test('maps finishReason "unknown" → "other"', async () => { + const model = makeV2LanguageModel({ + doGenerate: mock(async () => ({ + content: [], + finishReason: "unknown", + usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, + warnings: [], + })), + }) + const result = await shimLanguageModel(model).doGenerate({}) + expect(result.finishReason).toEqual({ unified: "other", raw: undefined }) + }) + + test("preserves all other finishReason values verbatim", async () => { + for (const reason of ["length", "content-filter", "tool-calls", "error"]) { + const model = makeV2LanguageModel({ + doGenerate: mock(async () => ({ + content: [], + finishReason: reason, + usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, + warnings: [], + })), + }) + const result = await shimLanguageModel(model).doGenerate({}) + expect(result.finishReason.unified).toBe(reason) + } + }) + + test("converts flat usage to v3 nested structure", async () => { + const model = makeV2LanguageModel({ + doGenerate: mock(async () => ({ + content: [], + finishReason: "stop", + usage: { + inputTokens: 100, + outputTokens: 50, + totalTokens: 150, + cachedInputTokens: 20, + reasoningTokens: 10, + }, + warnings: [], + })), + }) + const result = await shimLanguageModel(model).doGenerate({}) + expect(result.usage).toEqual({ + inputTokens: { total: 100, noCache: undefined, cacheRead: 20, cacheWrite: undefined }, + outputTokens: { total: 50, text: undefined, reasoning: 10 }, + }) + }) + + test("handles undefined optional usage fields gracefully", async () => { + const result = await shimLanguageModel(makeV2LanguageModel()).doGenerate({}) + expect(result.usage.inputTokens.cacheRead).toBeUndefined() + expect(result.usage.outputTokens.reasoning).toBeUndefined() + }) + + test("spreads all other result fields through unchanged", async () => { + const model = makeV2LanguageModel({ + doGenerate: mock(async () => ({ + content: [{ type: "text", text: "hello" }], + finishReason: "stop", + usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 }, + warnings: [{ type: "other", message: "test" }], + providerMetadata: { foo: "bar" }, + })), + }) + const result = await shimLanguageModel(model).doGenerate({}) + expect(result.content).toEqual([{ type: "text", text: "hello" }]) + expect(result.warnings).toEqual([{ type: "other", message: "test" }]) + expect(result.providerMetadata).toEqual({ foo: "bar" }) + }) + + test("forwards call arguments to underlying doGenerate", async () => { + const model = makeV2LanguageModel() + const shimmed = shimLanguageModel(model) + const options = { prompt: [{ role: "user", content: "hi" }] } + await shimmed.doGenerate(options) + expect(model.doGenerate).toHaveBeenCalledWith(options) + }) + }) + + describe("doStream", () => { + test("passes non-finish chunks through unchanged", async () => { + const shimmed = shimLanguageModel(makeV2LanguageModel()) + const { stream } = await shimmed.doStream({}) + const chunks = await collectStream(stream) + expect(chunks[0]).toEqual({ type: "text-delta", textDelta: "hello" }) + }) + + test("converts finish chunk finishReason to v3 object", async () => { + const shimmed = shimLanguageModel(makeV2LanguageModel()) + const { stream } = await shimmed.doStream({}) + const chunks = await collectStream(stream) + const finish = chunks.find((c) => c.type === "finish") + expect(finish?.finishReason).toEqual({ unified: "stop", raw: undefined }) + }) + + test("converts finish chunk usage to v3 nested structure", async () => { + const model = makeV2LanguageModel({ + doStream: mock(async () => ({ + stream: new ReadableStream({ + start(controller) { + controller.enqueue({ + type: "finish", + finishReason: "stop", + usage: { inputTokens: 10, outputTokens: 5, cachedInputTokens: 3, reasoningTokens: 2 }, + }) + controller.close() + }, + }), + })), + }) + const { stream } = await shimLanguageModel(model).doStream({}) + const [finish] = await collectStream(stream) + expect(finish.usage).toEqual({ + inputTokens: { total: 10, noCache: undefined, cacheRead: 3, cacheWrite: undefined }, + outputTokens: { total: 5, text: undefined, reasoning: 2 }, + }) + }) + + test("preserves finish chunk fields beyond finishReason and usage", async () => { + const model = makeV2LanguageModel({ + doStream: mock(async () => ({ + stream: new ReadableStream({ + start(controller) { + controller.enqueue({ + type: "finish", + finishReason: "stop", + usage: { inputTokens: 1, outputTokens: 1 }, + providerMetadata: { extra: true }, + }) + controller.close() + }, + }), + })), + }) + const { stream } = await shimLanguageModel(model).doStream({}) + const [chunk] = await collectStream(stream) + expect(chunk.providerMetadata).toEqual({ extra: true }) + }) + + test("spreads other doStream result fields through unchanged", async () => { + const model = makeV2LanguageModel({ + doStream: mock(async () => ({ + stream: new ReadableStream({ + start(c) { + c.close() + }, + }), + response: { headers: { "x-custom": "yes" } }, + })), + }) + const result = await shimLanguageModel(model).doStream({}) + expect(result.response).toEqual({ headers: { "x-custom": "yes" } }) + }) + + test("forwards call arguments to underlying doStream", async () => { + const model = makeV2LanguageModel() + const shimmed = shimLanguageModel(model) + const options = { prompt: [{ role: "user", content: "hi" }] } + await shimmed.doStream(options) + expect(model.doStream).toHaveBeenCalledWith(options) + }) + }) +}) + +// --------------------------------------------------------------------------- +// shimProvider +// --------------------------------------------------------------------------- + +describe("shimProvider", () => { + describe("fast-path", () => { + test("returns v3 provider unchanged", () => { + const provider = makeV3Provider() + expect(shimProvider(provider)).toBe(provider) + }) + + test("idempotent — shimming a shimmed provider returns the same shim", () => { + const provider = makeV2Provider() + const once = shimProvider(provider) + const twice = shimProvider(once) + expect(twice).toBe(once) + }) + }) + + describe("specificationVersion", () => { + test("reports v3 on shimmed object provider", () => { + expect(shimProvider(makeV2Provider()).specificationVersion).toBe("v3") + }) + + test("reports v3 on shimmed callable provider", () => { + expect(shimProvider(makeCallableV2Provider()).specificationVersion).toBe("v3") + }) + }) + + describe("languageModel()", () => { + test("returns a shimmed language model (specificationVersion v3)", () => { + const shimmed = shimProvider(makeV2Provider()) + const model = shimmed.languageModel("test-model") + expect(model.specificationVersion).toBe("v3") + }) + + test("forwards modelId to the underlying languageModel()", () => { + const provider = makeV2Provider() + shimProvider(provider).languageModel("my-model") + expect(provider.languageModel).toHaveBeenCalledWith("my-model") + }) + + test("doGenerate on the returned model converts v2 usage to v3", async () => { + const provider = makeV2Provider() + const model = shimProvider(provider).languageModel("test") + const result = await model.doGenerate({}) + expect(result.usage).toHaveProperty("inputTokens") + expect(result.usage.inputTokens).toHaveProperty("total") + }) + }) + + describe("callable provider (apply trap)", () => { + test("shimmed callable provider can be called as a function", () => { + const provider = makeCallableV2Provider() + const shimmed = shimProvider(provider) + expect(() => shimmed("some-model")).not.toThrow() + }) + + test("calling shimmed provider as function returns shimmed language model", () => { + const shimmed = shimProvider(makeCallableV2Provider()) + const model = shimmed("some-model") + expect(model.specificationVersion).toBe("v3") + }) + + test("doGenerate on the model returned from callable shim converts usage", async () => { + const shimmed = shimProvider(makeCallableV2Provider()) + const model = shimmed("some-model") + const result = await model.doGenerate({}) + expect(result.usage.inputTokens).toHaveProperty("total") + expect(result.finishReason).toHaveProperty("unified") + }) + }) + + describe("embeddingModel()", () => { + test("maps textEmbeddingModel (v2 name) to embeddingModel (v3)", () => { + const provider = makeV2Provider() + const shimmed = shimProvider(provider) + shimmed.embeddingModel("embed-model") + expect(provider.textEmbeddingModel).toHaveBeenCalledWith("embed-model") + }) + + test("falls back to embeddingModel if textEmbeddingModel absent", () => { + const embeddingModel = mock((_id: string) => ({})) + const provider = makeV2Provider({ textEmbeddingModel: undefined, embeddingModel }) + shimProvider(provider).embeddingModel("embed-model") + expect(embeddingModel).toHaveBeenCalledWith("embed-model") + }) + + test("returns undefined if neither textEmbeddingModel nor embeddingModel present", () => { + const provider = makeV2Provider({ textEmbeddingModel: undefined, embeddingModel: undefined }) + expect(shimProvider(provider).embeddingModel).toBeUndefined() + }) + }) + + describe("imageModel()", () => { + test("proxies through to underlying imageModel()", () => { + const provider = makeV2Provider() + shimProvider(provider).imageModel("img-model") + expect(provider.imageModel).toHaveBeenCalledWith("img-model") + }) + + test("returns undefined when imageModel absent on provider", () => { + const provider = makeV2Provider({ imageModel: undefined }) + expect(shimProvider(provider).imageModel).toBeUndefined() + }) + }) + + describe("transcriptionModel()", () => { + test("proxies through to underlying transcriptionModel()", () => { + const transcriptionModel = mock((_id: string) => ({})) + const provider = makeV2Provider({ transcriptionModel }) + shimProvider(provider).transcriptionModel("whisper") + expect(transcriptionModel).toHaveBeenCalledWith("whisper") + }) + + test("returns undefined when transcriptionModel absent", () => { + const provider = makeV2Provider({ transcriptionModel: undefined }) + expect(shimProvider(provider).transcriptionModel).toBeUndefined() + }) + }) + + describe("speechModel()", () => { + test("proxies through to underlying speechModel()", () => { + const speechModel = mock((_id: string) => ({})) + const provider = makeV2Provider({ speechModel }) + shimProvider(provider).speechModel("tts") + expect(speechModel).toHaveBeenCalledWith("tts") + }) + + test("returns undefined when speechModel absent", () => { + expect(shimProvider(makeV2Provider({ speechModel: undefined })).speechModel).toBeUndefined() + }) + }) + + describe("rerankingModel()", () => { + test("always returns undefined — v2 providers have no reranking concept", () => { + const provider = makeV2Provider({ rerankingModel: mock(() => ({})) }) + expect(shimProvider(provider).rerankingModel).toBeUndefined() + }) + }) + + describe("other properties", () => { + test("forwards unknown properties from the underlying provider", () => { + const provider = Object.assign(makeV2Provider(), { customProp: "hello" }) + expect(shimProvider(provider).customProp).toBe("hello") + }) + }) +}) + +// --------------------------------------------------------------------------- +// Venice-style provider (v2 object provider, no callable) +// --------------------------------------------------------------------------- + +describe("venice-style provider compatibility", () => { + function makeVeniceProvider() { + return { + languageModel: mock((id: string) => makeV2LanguageModel({ modelId: id })), + textEmbeddingModel: mock((_id: string) => ({ specificationVersion: "v2", modelId: "embed" })), + imageModel: mock((_id: string) => ({ specificationVersion: "v2", modelId: "img" })), + } + } + + test("shimProvider wraps a venice-style v2 provider", () => { + const shimmed = shimProvider(makeVeniceProvider()) + expect(shimmed.specificationVersion).toBe("v3") + }) + + test("languageModel() returns v3-compatible model", () => { + const model = shimProvider(makeVeniceProvider()).languageModel("venice-llama-3-3-70b") + expect(model.specificationVersion).toBe("v3") + }) + + test("doGenerate produces v3 usage shape", async () => { + const shimmed = shimProvider(makeVeniceProvider()) + const result = await shimmed.languageModel("test").doGenerate({}) + expect(result.usage).toEqual({ + inputTokens: { total: 100, noCache: undefined, cacheRead: undefined, cacheWrite: undefined }, + outputTokens: { total: 50, text: undefined, reasoning: undefined }, + }) + }) + + test("doStream produces v3 finish chunk shape", async () => { + const shimmed = shimProvider(makeVeniceProvider()) + const { stream } = await shimmed.languageModel("test").doStream({}) + const chunks = await collectStream(stream) + const finish = chunks.find((c) => c.type === "finish") + expect(finish?.finishReason).toEqual({ unified: "stop", raw: undefined }) + expect(finish?.usage.inputTokens).toHaveProperty("total") + }) + + test("textEmbeddingModel is accessible via embeddingModel", () => { + const provider = makeVeniceProvider() + shimProvider(provider).embeddingModel("embed-id") + expect(provider.textEmbeddingModel).toHaveBeenCalledWith("embed-id") + }) +}) + +// --------------------------------------------------------------------------- +// SAP-AI-Core-style provider (callable function + .languageModel()) +// --------------------------------------------------------------------------- + +describe("sap-ai-core-style provider compatibility", () => { + function makeSapProvider() { + const model = makeV2LanguageModel({ modelId: "anthropic--claude-4-sonnet" }) + const fn = Object.assign( + function (modelId: string) { + return model + }, + { + languageModel: mock((_id: string) => model), + textEmbeddingModel: mock((_id: string) => ({ specificationVersion: "v2" })), + imageModel: (_id: string) => { + throw new Error("No image models") + }, + }, + ) + return fn + } + + test("shimProvider wraps a callable v2 provider", () => { + const shimmed = shimProvider(makeSapProvider()) + expect(shimmed.specificationVersion).toBe("v3") + }) + + test("calling shimmed provider as function (sdk(modelId)) works", () => { + const shimmed = shimProvider(makeSapProvider()) + expect(() => shimmed("anthropic--claude-4-sonnet")).not.toThrow() + }) + + test("sdk(modelId) returns a v3-compatible language model", () => { + const shimmed = shimProvider(makeSapProvider()) + const model = shimmed("anthropic--claude-4-sonnet") + expect(model.specificationVersion).toBe("v3") + }) + + test("sdk(modelId) doGenerate converts v2 usage and finishReason", async () => { + const shimmed = shimProvider(makeSapProvider()) + const model = shimmed("anthropic--claude-4-sonnet") + const result = await model.doGenerate({}) + expect(result.finishReason).toEqual({ unified: "stop", raw: undefined }) + expect(result.usage.inputTokens).toHaveProperty("total") + }) + + test("sdk.languageModel(id) also works and returns v3 model", () => { + const shimmed = shimProvider(makeSapProvider()) + const model = shimmed.languageModel("anthropic--claude-4-sonnet") + expect(model.specificationVersion).toBe("v3") + }) + + test("both call paths return equivalent shimmed models", async () => { + const shimmed = shimProvider(makeSapProvider()) + const byCall = shimmed("test-model") + const byMethod = shimmed.languageModel("test-model") + // Both should be shimmed v3 models + expect(byCall.specificationVersion).toBe("v3") + expect(byMethod.specificationVersion).toBe("v3") + }) +}) diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 452926d12e1b..82391089faf4 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -252,6 +252,8 @@ describe("session.getUsage", () => { inputTokens: 1000, outputTokens: 500, totalTokens: 1500, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, }, }) @@ -270,7 +272,15 @@ describe("session.getUsage", () => { inputTokens: 1000, outputTokens: 500, totalTokens: 1500, - cachedInputTokens: 200, + inputTokenDetails: { + noCacheTokens: undefined, + cacheReadTokens: 200, + cacheWriteTokens: undefined, + }, + outputTokenDetails: { + textTokens: undefined, + reasoningTokens: undefined, + }, }, }) @@ -286,6 +296,8 @@ describe("session.getUsage", () => { inputTokens: 1000, outputTokens: 500, totalTokens: 1500, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, }, metadata: { anthropic: { @@ -305,7 +317,15 @@ describe("session.getUsage", () => { inputTokens: 1000, outputTokens: 500, totalTokens: 1500, - cachedInputTokens: 200, + inputTokenDetails: { + noCacheTokens: undefined, + cacheReadTokens: 200, + cacheWriteTokens: undefined, + }, + outputTokenDetails: { + textTokens: undefined, + reasoningTokens: undefined, + }, }, metadata: { anthropic: {}, @@ -324,7 +344,15 @@ describe("session.getUsage", () => { inputTokens: 1000, outputTokens: 500, totalTokens: 1500, - reasoningTokens: 100, + inputTokenDetails: { + noCacheTokens: undefined, + cacheReadTokens: undefined, + cacheWriteTokens: undefined, + }, + outputTokenDetails: { + textTokens: undefined, + reasoningTokens: 100, + }, }, }) @@ -339,6 +367,8 @@ describe("session.getUsage", () => { inputTokens: 0, outputTokens: 0, totalTokens: 0, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, }, }) @@ -366,6 +396,8 @@ describe("session.getUsage", () => { inputTokens: 1_000_000, outputTokens: 100_000, totalTokens: 1_100_000, + inputTokenDetails: { noCacheTokens: undefined, cacheReadTokens: undefined, cacheWriteTokens: undefined }, + outputTokenDetails: { textTokens: undefined, reasoningTokens: undefined }, }, }) @@ -382,7 +414,15 @@ describe("session.getUsage", () => { // These providers typically report total as input + output only, // excluding cache read/write. totalTokens: 1500, - cachedInputTokens: 200, + inputTokenDetails: { + noCacheTokens: undefined, + cacheReadTokens: 200, + cacheWriteTokens: undefined, + }, + outputTokenDetails: { + textTokens: undefined, + reasoningTokens: undefined, + }, } if (npm === "@ai-sdk/amazon-bedrock") { const result = Session.getUsage({ diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index a89a00ebc05e..8f614751918e 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -557,7 +557,8 @@ describe("session.llm.stream", () => { expect(body.model).toBe(resolved.api.id) expect(body.max_tokens).toBe(ProviderTransform.maxOutputTokens(resolved)) expect(body.temperature).toBe(0.4) - expect(body.top_p).toBe(0.9) + // Anthropic v3 SDK doesn't send top_p when temperature is set (mutual exclusivity) + expect(body.top_p).toBeUndefined() }, }) }) diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index c043754bdb4e..68f37317900d 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -104,7 +104,7 @@ function basePart(messageID: string, id: string) { } describe("session.message-v2.toModelMessage", () => { - test("filters out messages with no parts", () => { + test("filters out messages with no parts", async () => { const input: MessageV2.WithParts[] = [ { info: userInfo("m-empty"), @@ -122,7 +122,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "user", content: [{ type: "text", text: "hello" }], @@ -130,7 +130,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("filters out messages with only ignored parts", () => { + test("filters out messages with only ignored parts", async () => { const messageID = "m-user" const input: MessageV2.WithParts[] = [ @@ -147,10 +147,10 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([]) + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([]) }) - test("includes synthetic text parts", () => { + test("includes synthetic text parts", async () => { const messageID = "m-user" const input: MessageV2.WithParts[] = [ @@ -178,7 +178,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "user", content: [{ type: "text", text: "hello" }], @@ -190,7 +190,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("converts user text/file parts and injects compaction/subtask prompts", () => { + test("converts user text/file parts and injects compaction/subtask prompts", async () => { const messageID = "m-user" const input: MessageV2.WithParts[] = [ @@ -245,7 +245,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "user", content: [ @@ -263,7 +263,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("converts assistant tool completion into tool-call + tool-result messages with attachments", () => { + test("converts assistant tool completion into tool-call + tool-result messages with attachments", async () => { const userID = "m-user" const assistantID = "m-assistant" @@ -315,7 +315,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "user", content: [{ type: "text", text: "run tool" }], @@ -355,7 +355,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("omits provider metadata when assistant model differs", () => { + test("omits provider metadata when assistant model differs", async () => { const userID = "m-user" const assistantID = "m-assistant" @@ -398,7 +398,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "user", content: [{ type: "text", text: "run tool" }], @@ -430,7 +430,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("replaces compacted tool output with placeholder", () => { + test("replaces compacted tool output with placeholder", async () => { const userID = "m-user" const assistantID = "m-assistant" @@ -466,7 +466,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "user", content: [{ type: "text", text: "run tool" }], @@ -497,7 +497,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("converts assistant tool error into error-text tool result", () => { + test("converts assistant tool error into error-text tool result", async () => { const userID = "m-user" const assistantID = "m-assistant" @@ -533,7 +533,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "user", content: [{ type: "text", text: "run tool" }], @@ -566,7 +566,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("filters assistant messages with non-abort errors", () => { + test("filters assistant messages with non-abort errors", async () => { const assistantID = "m-assistant" const input: MessageV2.WithParts[] = [ @@ -586,10 +586,10 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([]) + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([]) }) - test("includes aborted assistant messages only when they have non-step-start/reasoning content", () => { + test("includes aborted assistant messages only when they have non-step-start/reasoning content", async () => { const assistantID1 = "m-assistant-1" const assistantID2 = "m-assistant-2" @@ -629,7 +629,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "assistant", content: [ @@ -640,7 +640,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("splits assistant messages on step-start boundaries", () => { + test("splits assistant messages on step-start boundaries", async () => { const assistantID = "m-assistant" const input: MessageV2.WithParts[] = [ @@ -665,7 +665,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([ + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ { role: "assistant", content: [{ type: "text", text: "first" }], @@ -677,7 +677,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("drops messages that only contain step-start parts", () => { + test("drops messages that only contain step-start parts", async () => { const assistantID = "m-assistant" const input: MessageV2.WithParts[] = [ @@ -692,10 +692,10 @@ describe("session.message-v2.toModelMessage", () => { }, ] - expect(MessageV2.toModelMessages(input, model)).toStrictEqual([]) + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([]) }) - test("converts pending/running tool calls to error results to prevent dangling tool_use", () => { + test("converts pending/running tool calls to error results to prevent dangling tool_use", async () => { const userID = "m-user" const assistantID = "m-assistant" @@ -739,7 +739,7 @@ describe("session.message-v2.toModelMessage", () => { }, ] - const result = MessageV2.toModelMessages(input, model) + const result = await MessageV2.toModelMessages(input, model) expect(result).toStrictEqual([ { @@ -787,7 +787,7 @@ describe("session.message-v2.toModelMessage", () => { }) describe("session.message-v2.fromError", () => { - test("serializes context_length_exceeded as ContextOverflowError", () => { + test("serializes context_length_exceeded as ContextOverflowError", async () => { const input = { type: "error", error: { @@ -805,7 +805,7 @@ describe("session.message-v2.fromError", () => { }) }) - test("serializes response error codes", () => { + test("serializes response error codes", async () => { const cases = [ { code: "insufficient_quota", @@ -842,7 +842,7 @@ describe("session.message-v2.fromError", () => { }) }) - test("maps github-copilot 403 to reauth guidance", () => { + test("maps github-copilot 403 to reauth guidance", async () => { const error = new APICallError({ message: "forbidden", url: "https://api.githubcopilot.com/v1/chat/completions", @@ -871,7 +871,7 @@ describe("session.message-v2.fromError", () => { }) }) - test("detects context overflow from APICallError provider messages", () => { + test("detects context overflow from APICallError provider messages", async () => { const cases = [ "prompt is too long: 213462 tokens > 200000 maximum", "Your input exceeds the context window of this model", @@ -895,7 +895,7 @@ describe("session.message-v2.fromError", () => { }) }) - test("does not classify 429 no body as context overflow", () => { + test("does not classify 429 no body as context overflow", async () => { const result = MessageV2.fromError( new APICallError({ message: "429 status code (no body)", @@ -911,7 +911,7 @@ describe("session.message-v2.fromError", () => { expect(MessageV2.APIError.isInstance(result)).toBe(true) }) - test("serializes unknown inputs", () => { + test("serializes unknown inputs", async () => { const result = MessageV2.fromError(123, { providerID: "test" }) expect(result).toStrictEqual({ diff --git a/packages/opencode/test/session/structured-output.test.ts b/packages/opencode/test/session/structured-output.test.ts index 2be4257dc788..6cafc68d475e 100644 --- a/packages/opencode/test/session/structured-output.test.ts +++ b/packages/opencode/test/session/structured-output.test.ts @@ -362,21 +362,27 @@ describe("structured-output.createStructuredOutputTool", () => { expect(inputSchema.jsonSchema?.properties?.tags?.items?.type).toBe("string") }) - test("toModelOutput returns text value", () => { + test("toModelOutput returns text value", async () => { const tool = SessionPrompt.createStructuredOutputTool({ schema: { type: "object" }, onSuccess: () => {}, }) expect(tool.toModelOutput).toBeDefined() - const modelOutput = tool.toModelOutput!({ - output: "Test output", - title: "Test", - metadata: { valid: true }, - }) + const modelOutput = await Promise.resolve( + tool.toModelOutput!({ + toolCallId: "test-id", + input: {}, + output: { + output: "Test output", + title: "Test", + metadata: { valid: true }, + }, + }), + ) - expect(modelOutput.type).toBe("text") - expect(modelOutput.value).toBe("Test output") + expect((modelOutput as any)?.type).toBe("text") + expect((modelOutput as any)?.value).toBe("Test output") }) // Note: Retry behavior is handled by the AI SDK and the prompt loop, not the tool itself diff --git a/packages/opencode/test/tool/fixtures/models-api.json b/packages/opencode/test/tool/fixtures/models-api.json index 391e783699e9..715224cd3a5b 100644 --- a/packages/opencode/test/tool/fixtures/models-api.json +++ b/packages/opencode/test/tool/fixtures/models-api.json @@ -32933,7 +32933,7 @@ "gitlab": { "id": "gitlab", "env": ["GITLAB_TOKEN"], - "npm": "@gitlab/gitlab-ai-provider", + "npm": "gitlab-ai-provider", "name": "GitLab Duo", "doc": "https://docs.gitlab.com/user/duo_agent_platform/", "models": {