Compare commits

..

1 Commits

Author SHA1 Message Date
Aiden Cline 9e7b5f1701 feat(opencode): cache-friendly compaction via primary loop request path
Default compaction no longer uses a dedicated compaction agent. It inherits
the session's active agent and delegates request assembly to the primary
loop's shared envelope (same system prompt, instructions, skills, MCP
context, and tool definitions), replaying the typed message head and
appending the summary instruction as the final user message. This keeps the
compaction request prefix-identical to the preceding turn so provider
prompt/KV caches are reused instead of fully re-prefilled.

An explicitly configured agent.compaction preserves the legacy behavior
exactly: dedicated hidden agent, optional model override, and the
serialized-transcript summary request.
2026-08-13 23:10:07 -05:00
38 changed files with 441 additions and 550 deletions
+34 -88
View File
@@ -332,7 +332,7 @@
"@opentelemetry/sdk-trace-base": "2.6.1",
"@parcel/watcher": "2.5.1",
"@silvia-odwyer/photon-node": "0.3.4",
"ai-gateway-provider": "3.2.0",
"ai-gateway-provider": "3.1.2",
"bun-pty": "0.4.8",
"cross-spawn": "catalog:",
"diff": "catalog:",
@@ -623,7 +623,7 @@
"@types/ws": "8.18.1",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
"ai-gateway-provider": "3.2.0",
"ai-gateway-provider": "3.1.2",
"bonjour-service": "1.3.0",
"chokidar": "4.0.3",
"cross-spawn": "catalog:",
@@ -1183,15 +1183,15 @@
"@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OqcCq2PiFY1dbK/0Ck45KuvE8jfdxRuuAE9Y5w46dAk6U+9vPOeg1CDcmR+ncqmrYrhRl3nmyDttyDahyjCzAw=="],
"@ai-sdk/deepgram": ["@ai-sdk/deepgram@2.0.51", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DPoKiCXDwzopJI/pHcZnXaycZ5qqCL4xCWcfsb1s8M8OUPchK2GUHcCXt6v056fAqtKWLF/hrW+RcHFFY0qyHQ=="],
"@ai-sdk/deepgram": ["@ai-sdk/deepgram@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-VscTV68g6sXRY4O1yl72/O8y6+tBDvSQax6bqX06hRKWBGxsJ8Jr3LZsNmZnK9Od5Icx565ijK0QgrlNaN4TdQ=="],
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-y6RoOP7DGWmDSiSxrUSt5p18sbz+Ixe5lMVPmdE7x+Tr5rlrzvftyHhjWHfqlAtoYERZTGFbP6tPW1OfQcrb4A=="],
"@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MzcQ321JO8OY+TVLFI81A7cIIuoeLLxrLCDD+8C1E3Ro6UFyfMtRXo9bw9OhTMRSDMo6hgSDOo4Fekz8aJtQYQ=="],
"@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.51", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XFONX6rAsu6d13cJVUfZfkq4a+qdThlxvoEfzYlSRa1AvALlzwX7Y6bunXxevuifT3n882+nDdWrdiYvFP0+Fw=="],
"@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EtvsWfGrqx3OhzJdoi82qH+4yzEPPKZr2utyQ+w8cHKoFeg0+8Lou9Z3uixy73WEwz8Z1+AR8QT9fZ64AWGYPA=="],
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.76", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.67", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yg1ulgemh6BLMrs2vBNbtjV70NhenFI3Z7psB7s94FOEcGevvyV1qdFoqsgBZ7QyuUGwnC645c+eBFFtPAr5SQ=="],
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.53", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HjeiGsdxSzrCkOf2l2V+K+opzlqxBtduBq6BCiohAdgQk2KdZmI/67SMkBM6Kdze/BjUXiZlv0d7zNICPhxVDA=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.104", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="],
@@ -3063,7 +3063,7 @@
"ai": ["ai@6.0.168", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="],
"ai-gateway-provider": ["ai-gateway-provider@3.2.0", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.117", "@ai-sdk/anthropic": "^3.0.84", "@ai-sdk/azure": "^3.0.74", "@ai-sdk/cerebras": "^2.0.56", "@ai-sdk/cohere": "^3.0.38", "@ai-sdk/deepgram": "^2.0.35", "@ai-sdk/deepseek": "^2.0.38", "@ai-sdk/elevenlabs": "^2.0.35", "@ai-sdk/fireworks": "^2.0.56", "@ai-sdk/google": "^3.0.82", "@ai-sdk/google-vertex": "^4.0.145", "@ai-sdk/groq": "^3.0.41", "@ai-sdk/mistral": "^3.0.39", "@ai-sdk/openai": "^3.0.71", "@ai-sdk/perplexity": "^3.0.35", "@ai-sdk/xai": "^3.0.95", "@openrouter/ai-sdk-provider": "^2.10.0" }, "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-IGSV96IqAfiZd20CWSMVQk5sVeLcJR2uQcoWLB8GdkxyvQrsU3x4U0o1Ok6bfVJug7SrlX6I8ibz9cHIkUyRtg=="],
"ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "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-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="],
"ajv": ["ajv@8.20.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-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
@@ -5667,9 +5667,9 @@
"@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"@ai-sdk/deepgram/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"@ai-sdk/deepgram/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
@@ -5677,15 +5677,15 @@
"@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-glcEJC2mBXJKj7joFI0fRhcbdDYKTBgXMPcT6Vcnlym67tTzuNG9pFx3zblxVv8TdOxhojJja5zGG19yeGJxuA=="],
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="],
"@ai-sdk/fireworks/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"@ai-sdk/fireworks/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
@@ -6171,25 +6171,21 @@
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.153", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.110", "@ai-sdk/openai": "3.0.96", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45", "@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-iEXrLgWylCHJmznqlKLU3CqRh8UWibv+illrwmsk136FVBBvyXiGnpQrI1pGWCScVLQjBQSFQu7GJDkUEomf/A=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.107", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.78", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8nT08pGPy25rleJNk56ep00UHK6kCtCmu+ZNqVVSSPDieADlIZqcaN1iRXAFBoCH0Fb9F6C2EjFDaySdsargfQ=="],
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.110", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rNkamQCeAUOUGr5Npg5pXZyYFH4fS1U6Mbdy3dF/NNBEI3D2Chc/ruRrwNegP0gfpX3cllP3O4jSibGBbWPZ7A=="],
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.78", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA=="],
"ai-gateway-provider/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.60", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.54", "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rnok3cThg6awBwaDSyiZpgRpbV7pqxGYrA89LODCo5cuEHeP2h0AM0lLHP7zIkclAdXfOm4wldKi/S2T/DGCOw=="],
"ai-gateway-provider/@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="],
"ai-gateway-provider/@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cXLjIsSzUriPHe704IH6d+ipJ/OvczTB700p9Zma7DPgQzvxG/diyr8q/2LEsbTRiTopiKhky8dn1PJNQcJToQ=="],
"ai-gateway-provider/@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9DhYurbAvcurOEGN6u2myYDybrrzGfcrkG8hwmFjwTrePW6KCMggm0YxP7e8RkLYcQKqCEMgFlyEB4BM6EmiKg=="],
"ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.108", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kwvYpRNghqt0VRKE7Hx1UWZQCUJJFqUITj24baxy+ApS0Hru0PkBJHD75a36Wc+e6e+wHcKR2MconTeJiBZigA=="],
"ai-gateway-provider/@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZXe7nZQgliDdjz5ufH5RKpHWxbN72AzmzzKGbF/z+0K9GN5tUCnftrQRvTRFHA5jAzTapcm2BEevmGLVbMkW+A=="],
"ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.181", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.110", "@ai-sdk/google": "3.0.108", "@ai-sdk/openai-compatible": "2.0.67", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-57b5Qor8V53vubkxCj09tbHWpzpCLUbzmll2FwShuLvyEAsCH6mh3sAowDhiwUWPXnLzU+rC3RVMKCPscqICcg=="],
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
"ai-gateway-provider/@ai-sdk/groq": ["@ai-sdk/groq@3.0.59", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-X4h60TGq4pIOXPsthatUr+bfTaYCaKGX597hG9JgcueEl4+nboCdw99ixjFKGkvYlBJwLCCfI957EmGA2QlF0w=="],
"ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="],
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="],
"ai-gateway-provider/@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bL3SWrPltTuxVNg/bZ5APbJLVe0+BIU+BYAbT8Yo0eSAZ5eMTImN2murQHZa08Wi8lUMm8MNolaYBQkb0JDbFw=="],
"ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.10.0", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-FMsAEjLUt5pWuRE2LDC/LCvVrFjLlrEzUITH5+5SZtfq7KZ2wrOHjQVxzz92sju8S9ltpzW87CLW8/b0oBXVCw=="],
"ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="],
"ajv-keywords/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
@@ -6599,20 +6595,14 @@
"@ai-sdk/deepgram/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/deepgram/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@ai-sdk/deepinfra/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@ai-sdk/fireworks/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/fireworks/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
@@ -6987,51 +6977,29 @@
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/openai": ["@ai-sdk/openai@3.0.96", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Pex8vOj1y05j7jtBS39cJJRDjJbMIyCY9+01cSIp1hwEJTKImrFejMgsAazMWXSi/HU+B9ZE6ElftCOwvg4mmQ=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OyXt0zK8y2/ZIyWlbxTv2r1M7AK227S+Gl4BYOEF42q0wz1n5m4fwR8L4Fy/MQ4Ho6xje47MPsFcRdIqIyP6Rw=="],
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="],
"ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-nJ0bAfegMAIJtrzMJtbzer1cS3nb7c7DsyU1S4nrPm7ZU0Mn6SBBZv5IGZZGTbpWTJwqKTSPeZJTXalbAxt1BA=="],
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-glcEJC2mBXJKj7joFI0fRhcbdDYKTBgXMPcT6Vcnlym67tTzuNG9pFx3zblxVv8TdOxhojJja5zGG19yeGJxuA=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/groq/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
"ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
"ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
@@ -7451,35 +7419,13 @@
"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/amazon-bedrock/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"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/deepseek/@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/cohere/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"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-vertex/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"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/google/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"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/groq/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/openai/@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/perplexity/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"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=="],
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+1 -1
View File
@@ -101,7 +101,7 @@
"@parcel/watcher": "2.5.1",
"@silvia-odwyer/photon-node": "0.3.4",
"@openrouter/ai-sdk-provider": "2.9.0",
"ai-gateway-provider": "3.2.0",
"ai-gateway-provider": "3.1.2",
"bun-pty": "0.4.8",
"cross-spawn": "catalog:",
"diff": "catalog:",
@@ -24,15 +24,9 @@ export const CloudflareAIGatewayPlugin = define({
apiKey: config.apiKey,
options: gatewayOptions(evt.options, metadata),
} as any)
const unified = createUnified({ apiKey: config.apiKey })
evt.sdk = {
languageModel(modelID: string) {
// Workers AI is the only first-party provider whose upstream is Cloudflare itself, so it is
// the only one that should receive the Cloudflare token as its upstream Authorization header.
// The Unified API addresses Workers AI both with the explicit "workers-ai/" prefix and as
// bare "@cf/..." ids. Third-party providers must not receive the token; they rely on the
// gateway's stored/BYOK keys instead.
const isWorkersAi = modelID.startsWith("workers-ai/") || modelID.startsWith("@cf/")
const unified = createUnified(isWorkersAi ? { apiKey: config.apiKey } : {})
return gateway(unified(modelID))
},
}
+12
View File
@@ -173,6 +173,18 @@ export const buildPrompt = (input: { readonly previousSummary?: string; readonly
].join("\n\n")
}
export const buildReplayPrompt = (previousSummary?: string) => {
const instruction =
"Create a new anchored summary from the conversation messages above so another coding agent can continue the work."
if (!previousSummary) return [instruction, SUMMARY_TEMPLATE].join("\n\n")
return [
instruction,
`Here is the summary of the conversation before the messages above:\n\n<prior-summary>\n${previousSummary}\n</prior-summary>`,
SUMMARY_UPDATE_INSTRUCTIONS,
SUMMARY_TEMPLATE,
].join("\n\n")
}
export const make = (dependencies: Dependencies) => {
const config = settings(dependencies.config)
const compactAfterOverflow = Effect.fn("SessionCompaction.compactAfterOverflow")(function* (input: Input) {
+3
View File
@@ -12,6 +12,7 @@ import { SessionMessage } from "./message"
import { SessionMessageUpdater } from "./message-updater"
import { SessionInput } from "./input"
import { WorkspaceV2 } from "../workspace"
import { SessionContextEpoch } from "./context-epoch"
import { MessageTable, PartTable, SessionInputTable, SessionMessageTable, SessionTable } from "./sql"
import type { DeepMutable } from "../schema"
@@ -252,6 +253,7 @@ const layer = Layer.effectDiscard(
.where(eq(SessionTable.id, event.data.sessionID))
.run()
.pipe(Effect.orDie)
yield* SessionContextEpoch.reset(db, event.data.sessionID)
}),
)
yield* events.project(SessionV1.Event.Deleted, (event) =>
@@ -447,6 +449,7 @@ const layer = Layer.effectDiscard(
.where(eq(SessionTable.id, event.data.sessionID))
.run()
.pipe(Effect.orDie)
yield* SessionContextEpoch.reset(db, event.data.sessionID)
}),
)
}),
+1 -35
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { DateTime, Effect, Schema } from "effect"
import { asc, eq, sql } from "drizzle-orm"
import { asc, eq } from "drizzle-orm"
import { Database } from "@opencode-ai/core/database/database"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
@@ -22,7 +22,6 @@ import { SessionInput } from "@opencode-ai/core/session/input"
import { SessionInputTable, SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql"
import { testEffect } from "./lib/effect"
import { Snapshot } from "@opencode-ai/core/snapshot"
import { Location } from "@opencode-ai/core/location"
const it = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node])))
const sessionsLayer = AppNodeBuilder.build(SessionV2.node, [[SessionExecution.node, SessionExecution.noopLayer]])
@@ -45,39 +44,6 @@ const assistantRow = (
}
describe("SessionProjector", () => {
it.effect("projects moved sessions without the transitional context epoch table", () =>
Effect.gen(function* () {
const { db } = yield* Database.Service
const events = yield* EventV2.Service
yield* db
.insert(ProjectTable)
.values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
.run()
yield* db
.insert(SessionTable)
.values({
id: sessionID,
project_id: Project.ID.global,
slug: "test",
directory: "/project",
title: "test",
version: "test",
})
.run()
yield* db.run(sql`DROP TABLE session_context_epoch`)
yield* events.publish(SessionEvent.Moved, {
sessionID,
timestamp: DateTime.makeUnsafe(1),
location: Location.Ref.make({ directory: AbsolutePath.make("/project/subdir") }),
})
expect(yield* db.select({ directory: SessionTable.directory }).from(SessionTable).get()).toEqual({
directory: "/project/subdir",
})
}),
)
it.effect("projects staged, cleared, and committed reverts", () =>
Effect.gen(function* () {
const db = (yield* Database.Service).db
@@ -703,6 +703,13 @@ describe("SessionRunnerLLM", () => {
timestamp: DateTime.makeUnsafe(1),
location: Location.Ref.make({ directory: AbsolutePath.make("/moved") }),
})
expect(
yield* db
.select()
.from(SessionContextEpochTable)
.where(eq(SessionContextEpochTable.session_id, sessionID))
.get(),
).toBeUndefined()
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
+1 -1
View File
@@ -111,7 +111,7 @@
"@types/ws": "8.18.1",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
"ai-gateway-provider": "3.2.0",
"ai-gateway-provider": "3.1.2",
"bonjour-service": "1.3.0",
"chokidar": "4.0.3",
"cross-spawn": "catalog:",
+20 -15
View File
@@ -216,21 +216,6 @@ const layer = Layer.effect(
mode: "subagent",
native: true,
},
compaction: {
name: "compaction",
mode: "primary",
native: true,
hidden: true,
prompt: PROMPT_COMPACTION,
permission: Permission.merge(
defaults,
Permission.fromConfig({
"*": "deny",
}),
user,
),
options: {},
},
title: {
name: "title",
mode: "primary",
@@ -270,6 +255,26 @@ const layer = Layer.effect(
continue
}
let item = agents[key]
// The compaction agent is not registered by default: compaction inherits
// the session's active agent so its request stays prefix-identical for
// prompt caching. An explicit `agent.compaction` config opts into the
// legacy dedicated agent, seeded here so overrides merge as before.
if (!item && key === "compaction")
item = agents[key] = {
name: "compaction",
mode: "primary",
native: true,
hidden: true,
prompt: PROMPT_COMPACTION,
permission: Permission.merge(
defaults,
Permission.fromConfig({
"*": "deny",
}),
user,
),
options: {},
}
if (!item)
item = agents[key] = {
name: key,
@@ -714,7 +714,6 @@ const layer = Layer.effect(
})
const list = Effect.fn("Workspace.list")(function* (project: Project.Info) {
if (!flags.experimentalWorkspaces) return []
return (yield* db
.select()
.from(WorkspaceTable)
@@ -852,7 +851,6 @@ const layer = Layer.effect(
})
const startWorkspaceSyncing = Effect.fn("Workspace.startWorkspaceSyncing")(function* (projectID: ProjectV2.ID) {
if (!flags.experimentalWorkspaces) return
const rows = yield* db
.selectDistinct({ workspace: WorkspaceTable })
.from(WorkspaceTable)
@@ -61,5 +61,16 @@ export async function CloudflareAIGatewayAuthPlugin(_input: PluginInput): Promis
},
],
},
"chat.params": async (input, output) => {
if (input.model.providerID !== "cloudflare-ai-gateway") return
// The unified gateway routes through @ai-sdk/openai-compatible, which
// always emits max_tokens. OpenAI reasoning models (gpt-5.x, o-series)
// reject that field and require max_completion_tokens instead, and the
// compatible SDK has no way to rename it. Drop the cap so OpenAI falls
// back to the model's default output budget.
if (!input.model.api.id.toLowerCase().startsWith("openai/")) return
if (!input.model.capabilities.reasoning) return
output.maxOutputTokens = undefined
},
}
}
+5 -37
View File
@@ -800,10 +800,9 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
)
}
// Use official ai-gateway-provider package (v2.x for AI SDK v5 compatibility)
const { createAiGateway } = yield* Effect.promise(() => import("ai-gateway-provider"))
const { createUnified } = yield* Effect.promise(() => import("ai-gateway-provider/providers/unified"))
const { createOpenAI } = yield* Effect.promise(() => import("ai-gateway-provider/providers/openai"))
const { createAnthropic } = yield* Effect.promise(() => import("ai-gateway-provider/providers/anthropic"))
const metadata = iife(() => {
if (input.options?.metadata) return input.options.metadata
@@ -830,24 +829,12 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
apiKey: apiToken,
...(Object.values(opts).some((v) => v !== undefined) ? { options: opts } : {}),
})
const unified = createUnified({ apiKey: apiToken })
return {
autoload: true,
async getModel(_sdk: any, modelID: string, _options?: Record<string, any>) {
// Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5").
// OpenAI and Anthropic ride their native passthrough routes so agents get the Responses
// and Messages APIs; new OpenAI models reject tools+reasoning_effort on chat completions.
// The passthrough wrappers inject a CF_TEMP_TOKEN sentinel that the gateway strips before
// dispatch, so upstream billing stays on the gateway (Unified Billing / stored BYOK).
if (modelID.startsWith("openai/")) return aigateway(createOpenAI()(modelID.slice("openai/".length)))
if (modelID.startsWith("anthropic/"))
return aigateway(createAnthropic()(modelID.slice("anthropic/".length)))
// Workers AI is the only first-party provider whose upstream is Cloudflare itself, so it is
// the only one that should receive the Cloudflare token as its upstream Authorization header.
// The Unified API addresses Workers AI both with the explicit "workers-ai/" prefix and as
// bare "@cf/..." ids. Third-party providers must not receive the token; they rely on the
// gateway's stored/BYOK keys instead.
const isWorkersAi = modelID.startsWith("workers-ai/") || modelID.startsWith("@cf/")
const unified = createUnified(isWorkersAi ? { apiKey: apiToken } : {})
// Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5")
return aigateway(unified(modelID))
},
options: {},
@@ -1222,17 +1209,6 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
return result
}
// Cloudflare AI Gateway routes OpenAI and Anthropic models through their native
// passthrough SDKs (Responses / Messages APIs). Resolving the native npm before
// variants are computed makes reasoning variants produce payloads the native
// SDKs understand (e.g. anthropic `effort` instead of compat `reasoningEffort`).
function cloudflareGatewayNpm(providerID: string, modelID: string) {
if (providerID !== "cloudflare-ai-gateway") return undefined
if (modelID.startsWith("openai/")) return "@ai-sdk/openai"
if (modelID.startsWith("anthropic/")) return "@ai-sdk/anthropic"
return undefined
}
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
const base: Model = {
id: ModelV2.ID.make(model.id),
@@ -1242,11 +1218,7 @@ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model
api: {
id: model.id,
url: model.provider?.api ?? provider.api ?? "",
npm:
cloudflareGatewayNpm(provider.id, model.id) ??
model.provider?.npm ??
provider.npm ??
"@ai-sdk/openai-compatible",
npm: model.provider?.npm ?? provider.npm ?? "@ai-sdk/openai-compatible",
},
status: model.status ?? "active",
headers: {},
@@ -1468,9 +1440,6 @@ const layer = Layer.effect(
model.provider?.npm ??
provider.npm ??
existingModel?.api.npm ??
// Config-defined gateway models bypass fromModelsDevModel, so resolve the
// native passthrough npm here before falling back to the catalog default.
cloudflareGatewayNpm(providerID, apiID) ??
modelsDev[providerID]?.npm ??
"@ai-sdk/openai-compatible"
const name = iife(() => {
@@ -1650,7 +1619,6 @@ const layer = Layer.effect(
for (const [modelID, model] of Object.entries(provider.models)) {
model.api.id = model.api.id ?? model.id ?? modelID
if (
// These chat aliases are invalid for the special handling in the
// built-in providers below, but custom providers may support them.
+61 -37
View File
@@ -20,7 +20,7 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2Bridge } from "@/event-v2-bridge"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
import { buildPrompt } from "@opencode-ai/core/session/compaction"
import { buildPrompt, buildReplayPrompt } from "@opencode-ai/core/session/compaction"
import { SessionCompactionEvent } from "@opencode-ai/schema/session-compaction-event"
export const Event = SessionCompactionEvent
@@ -162,6 +162,15 @@ function splitTurn(input: {
})
}
export type Run = (input: {
user: SessionV1.User
agent: Agent.Info
model: Provider.Model
processor: SessionProcessor.Handle
messages: SessionV1.WithParts[]
prompt: string
}) => Effect.Effect<SessionProcessor.Result>
export interface Interface {
readonly isOverflow: (input: {
tokens: SessionV1.Assistant["tokens"]
@@ -174,6 +183,7 @@ export interface Interface {
sessionID: SessionID
auto: boolean
overflow?: boolean
run?: Run
}) => Effect.Effect<"continue" | "stop">
readonly create: (input: {
sessionID: SessionID
@@ -322,6 +332,7 @@ const layer = Layer.effect(
sessionID: SessionID
auto: boolean
overflow?: boolean
run?: Run
}) {
const parent = input.messages.findLast((m) => m.info.id === input.parentID)
if (!parent || parent.info.role !== "user") {
@@ -355,11 +366,13 @@ const layer = Layer.effect(
}
}
const agent = yield* agents.get("compaction")
const model = agent.model
? yield* provider.getModel(agent.model.providerID, agent.model.modelID).pipe(Effect.orDie)
: yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID).pipe(Effect.orDie)
const cfg = yield* config.get()
const configured = cfg.agent?.compaction !== undefined
const agent = yield* agents.get(configured ? "compaction" : userMessage.agent)
const model =
configured && agent.model
? yield* provider.getModel(agent.model.providerID, agent.model.modelID).pipe(Effect.orDie)
: yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID).pipe(Effect.orDie)
const history = compactionPart && messages.at(-1)?.info.id === input.parentID ? messages.slice(0, -1) : messages
const prior = completedCompactions(history)
const hidden = new Set(prior.flatMap((item) => [item.userIndex, item.assistantIndex]))
@@ -377,18 +390,16 @@ const layer = Layer.effect(
)
const msgs = structuredClone(selected.head)
yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
const conversation = msgs.map(serialize).filter(Boolean).join("\n\n")
const nextPrompt =
// A configured compaction agent keeps the legacy serialized-transcript path.
const run = configured ? undefined : input.run
const conversation = run ? "" : msgs.map(serialize).filter(Boolean).join("\n\n")
const nextPrompt = [
compacting.prompt ??
[
buildPrompt({
previousSummary,
context: [conversation],
}),
...compacting.context,
]
.filter(Boolean)
.join("\n\n")
(run ? buildReplayPrompt(previousSummary) : buildPrompt({ previousSummary, context: [conversation] })),
...compacting.context,
]
.filter(Boolean)
.join("\n\n")
const ctx = yield* InstanceState.context
const msg: SessionV1.Assistant = {
id: MessageID.ascending(),
@@ -422,30 +433,43 @@ const layer = Layer.effect(
sessionID: input.sessionID,
model,
})
const result = yield* processor.process({
user: userMessage,
agent,
sessionID: input.sessionID,
tools: {},
system: [],
messages: [
{
role: "user",
content: [
// The synthetic compaction marker carries no per-turn system/tools settings.
const active = msgs.findLast(
(message) => message.info.role === "user" && !message.parts.some((part) => part.type === "compaction"),
)
const result = run
? yield* run({
user: active?.info.role === "user" ? active.info : userMessage,
agent,
model,
processor,
messages: msgs,
prompt: nextPrompt,
})
: yield* processor.process({
user: userMessage,
agent,
sessionID: input.sessionID,
tools: {},
system: [],
messages: [
{
type: "text",
text: [
nextPrompt,
...(compacting.prompt ? ["The following is the conversation history:", conversation] : []),
]
.filter(Boolean)
.join("\n\n"),
role: "user",
content: [
{
type: "text",
text: [
nextPrompt,
...(compacting.prompt ? ["The following is the conversation history:", conversation] : []),
]
.filter(Boolean)
.join("\n\n"),
},
],
},
],
},
],
model,
})
model,
})
if (result === "compact") {
processor.message.error = new SessionV1.ContextOverflowError({
+67 -47
View File
@@ -149,6 +149,45 @@ const layer = Layer.effect(
} satisfies TaskPromptOps
})
// One assembly path keeps compaction requests prefix-identical to normal turns for prompt caching.
const prepare = Effect.fn("SessionPrompt.prepare")(function* (input: {
session: Session.Info
agent: Agent.Info
model: Provider.Model
processor: SessionProcessor.Handle
messages: SessionV1.WithParts[]
}) {
const lastUserMsg = input.messages.findLast((message) => message.info.role === "user")
const tools = yield* SessionTools.resolve({
agent: input.agent,
session: input.session,
model: input.model,
processor: input.processor,
bypassAgentCheck: lastUserMsg?.parts.some((part) => part.type === "agent") ?? false,
messages: input.messages,
promptOps: yield* ops(),
}).pipe(
Effect.provideService(Plugin.Service, plugin),
Effect.provideService(Permission.Service, permission),
Effect.provideService(ToolRegistry.Service, registry),
Effect.provideService(MCP.Service, mcp),
Effect.provideService(Truncate.Service, truncate),
Effect.provideService(RuntimeFlags.Service, flags),
)
const [skills, env, instructions, mcpInstructions, modelMessages] = yield* Effect.all([
sys.skills(input.agent),
sys.environment(input.model),
instruction.system().pipe(Effect.orDie),
sys.mcp(input.agent, input.session.permission),
MessageV2.toModelMessagesEffect(input.messages, input.model),
])
return {
tools,
modelMessages,
system: [...env, ...instructions, ...(mcpInstructions ? [mcpInstructions] : []), ...(skills ? [skills] : [])],
}
})
const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) {
yield* Effect.logInfo("cancel", { "session.id": sessionID })
yield* state.cancel(sessionID)
@@ -1153,6 +1192,21 @@ const layer = Layer.effect(
sessionID,
auto: task.auto,
overflow: task.overflow,
run: ({ user, agent, model, processor, messages, prompt }) =>
Effect.gen(function* () {
const prepared = yield* prepare({ session, agent, model, processor, messages })
return yield* processor.process({
user,
agent,
permission: session.permission,
sessionID,
parentSessionID: session.parentID,
system: prepared.system,
messages: [...prepared.modelMessages, { role: "user", content: prompt }],
tools: prepared.tools,
model,
})
}),
})
if (result === "stop") break
continue
@@ -1219,68 +1273,34 @@ const layer = Layer.effect(
.pipe(Effect.onInterrupt(() => finalizeInterruptedAssistant))
const outcome: "break" | "continue" = yield* Effect.gen(function* () {
const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false
const promptOps = yield* ops()
const tools = yield* SessionTools.resolve({
agent,
session,
model,
processor: handle,
bypassAgentCheck,
messages: msgs,
promptOps,
}).pipe(
Effect.provideService(Plugin.Service, plugin),
Effect.provideService(Permission.Service, permission),
Effect.provideService(ToolRegistry.Service, registry),
Effect.provideService(MCP.Service, mcp),
Effect.provideService(Truncate.Service, truncate),
Effect.provideService(RuntimeFlags.Service, flags),
)
if (lastUser.format?.type === "json_schema") {
tools["StructuredOutput"] = createStructuredOutputTool({
schema: lastUser.format.schema,
onSuccess(output) {
structured = output
},
})
}
if (step === 1)
yield* summary.summarize({ sessionID, messageID: lastUser.id }).pipe(Effect.ignore, Effect.forkIn(scope))
yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
const [skills, env, instructions, mcpInstructions, modelMsgs] = yield* Effect.all([
sys.skills(agent),
sys.environment(model),
instruction.system().pipe(Effect.orDie),
sys.mcp(agent, session.permission),
MessageV2.toModelMessagesEffect(msgs, model),
])
const system = [
...env,
...instructions,
...(mcpInstructions ? [mcpInstructions] : []),
...(skills ? [skills] : []),
]
const prepared = yield* prepare({ session, agent, model, processor: handle, messages: msgs })
const format = lastUser.format ?? { type: "text" as const }
if (format.type === "json_schema") system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
if (format.type === "json_schema") {
prepared.system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
prepared.tools["StructuredOutput"] = createStructuredOutputTool({
schema: format.schema,
onSuccess(output) {
structured = output
},
})
}
const result = yield* handle.process({
user: lastUser,
agent,
permission: session.permission,
sessionID,
parentSessionID: session.parentID,
system,
system: prepared.system,
messages: [
...modelMsgs,
...prepared.modelMessages,
...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS_PROMPT }] : []),
],
tools,
tools: prepared.tools,
model,
toolChoice: format.type === "json_schema" ? "required" : undefined,
})
+27 -7
View File
@@ -52,7 +52,7 @@ it.instance("returns default native agents when no config", () =>
expect(names).toContain("plan")
expect(names).toContain("general")
expect(names).toContain("explore")
expect(names).toContain("compaction")
expect(names).not.toContain("compaction")
expect(names).toContain("title")
expect(names).toContain("summary")
}),
@@ -170,17 +170,34 @@ it.instance("general agent denies todo tools", () =>
}),
)
it.instance("compaction agent denies all permissions", () =>
it.instance("compaction agent is absent without configuration", () =>
Effect.gen(function* () {
const compaction = yield* load((svc) => svc.get("compaction"))
expect(compaction).toBeDefined()
expect(compaction?.hidden).toBe(true)
expect(evalPerm(compaction, "bash")).toBe("deny")
expect(evalPerm(compaction, "edit")).toBe("deny")
expect(evalPerm(compaction, "read")).toBe("deny")
expect(compaction).toBeUndefined()
}),
)
it.instance(
"configured compaction agent keeps native defaults",
() =>
Effect.gen(function* () {
const compaction = yield* load((svc) => svc.get("compaction"))
expect(compaction).toBeDefined()
expect(compaction?.native).toBe(true)
expect(compaction?.hidden).toBe(true)
expect(evalPerm(compaction, "bash")).toBe("deny")
expect(evalPerm(compaction, "edit")).toBe("deny")
expect(evalPerm(compaction, "read")).toBe("deny")
}),
{
config: {
agent: {
compaction: {},
},
},
},
)
it.instance(
"custom agent from config creates new agent",
() =>
@@ -710,6 +727,9 @@ it.instance(
{
config: {
default_agent: "compaction",
agent: {
compaction: {},
},
},
},
)
@@ -8,7 +8,6 @@ import { Effect, Exit, Fiber, Layer, Schema } from "effect"
import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { eq } from "drizzle-orm"
import { GlobalBus, type GlobalEvent } from "@/bus/global"
import { Project } from "@/project/project"
import { Database } from "@opencode-ai/core/database/database"
import { ProjectV2 } from "@opencode-ai/core/project"
import { ProjectTable } from "@opencode-ai/core/project/sql"
@@ -134,9 +133,6 @@ const startWorkspaceSyncingWithFlag = (projectID: ProjectV2.ID, experimentalWork
Workspace.use.startWorkspaceSyncing(projectID).pipe(Effect.provide(workspaceLayer(experimentalWorkspaces))),
)
const listWithFlag = (project: Project.Info, experimentalWorkspaces: boolean) =>
Effect.runPromise(Workspace.use.list(project).pipe(Effect.provide(workspaceLayer(experimentalWorkspaces))))
function captureGlobalEvents() {
const events: GlobalEvent[] = []
const handler = (event: GlobalEvent) => events.push(event)
@@ -421,18 +417,6 @@ describe("workspace CRUD", () => {
{ git: true },
)
it.instance(
"list is disabled by the experimental workspace flag",
() =>
Effect.gen(function* () {
const instance = yield* requireInstance
yield* insertWorkspace(workspaceInfo(instance.project.id, "manual"))
expect(yield* Effect.promise(() => listWithFlag(instance.project, false))).toEqual([])
}),
{ git: true },
)
it.instance(
"create configures, persists, creates, starts local sync, and passes environment",
() =>
@@ -13,13 +13,56 @@ const pluginInput = {
$: {} as never,
}
test("registers the cloudflare-ai-gateway auth method", async () => {
function makeHookInput(overrides: { providerID?: string; apiId?: string; reasoning?: boolean }) {
return {
sessionID: "s",
agent: "a",
provider: {} as never,
message: {} as never,
model: {
providerID: overrides.providerID ?? "cloudflare-ai-gateway",
api: { id: overrides.apiId ?? "openai/gpt-5.2-codex", url: "", npm: "ai-gateway-provider" },
capabilities: {
reasoning: overrides.reasoning ?? true,
temperature: false,
attachment: true,
toolcall: true,
input: { text: true, audio: false, image: false, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
} as never,
}
}
function makeHookOutput() {
return { temperature: 0, topP: 1, topK: 0, maxOutputTokens: 32_000 as number | undefined, options: {} }
}
test("omits maxOutputTokens for openai reasoning models on cloudflare-ai-gateway", async () => {
const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
expect(hooks.auth?.provider).toBe("cloudflare-ai-gateway")
expect(hooks.auth?.methods).toHaveLength(1)
const out = makeHookOutput()
await hooks["chat.params"]!(makeHookInput({ apiId: "openai/gpt-5.2-codex", reasoning: true }), out)
expect(out.maxOutputTokens).toBeUndefined()
})
test("no longer drops maxOutputTokens; OpenAI models ride the Responses API passthrough", async () => {
test("keeps maxOutputTokens for openai non-reasoning models", async () => {
const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
expect(hooks["chat.params"]).toBeUndefined()
const out = makeHookOutput()
await hooks["chat.params"]!(makeHookInput({ apiId: "openai/gpt-4-turbo", reasoning: false }), out)
expect(out.maxOutputTokens).toBe(32_000)
})
test("keeps maxOutputTokens for non-openai reasoning models on cloudflare-ai-gateway", async () => {
const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
const out = makeHookOutput()
await hooks["chat.params"]!(makeHookInput({ apiId: "anthropic/claude-sonnet-4-5", reasoning: true }), out)
expect(out.maxOutputTokens).toBe(32_000)
})
test("ignores non-cloudflare-ai-gateway providers", async () => {
const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
const out = makeHookOutput()
await hooks["chat.params"]!(makeHookInput({ providerID: "openai", apiId: "gpt-5.2-codex", reasoning: true }), out)
expect(out.maxOutputTokens).toBe(32_000)
})
@@ -1,26 +1,22 @@
// End-to-end regression tests for opencode#24432 and opencode#32051/#32052.
// End-to-end regression test for opencode#24432.
//
// Routes through the actual ai-gateway-provider chain that provider.ts builds at
// runtime, with only the network boundary stubbed:
// - openai/* -> native OpenAI passthrough (Responses API)
// - anthropic/* -> native Anthropic passthrough (Messages API)
// - everything else -> unified /compat (openai-compatible chat completions)
// Asserts what actually lands in the envelope body Cloudflare AI Gateway
// forwards upstream, which is the only place these bugs were observable.
// Routes through the actual ai-gateway-provider + @ai-sdk/openai-compatible
// chain that provider.ts:811 builds at runtime, with only the network boundary
// stubbed. Asserts that `reasoning_effort` (and other provider options the
// transform emits) actually land in the body Cloudflare AI Gateway forwards
// upstream, which is the only place the bug was observable.
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
import type { JSONValue } from "ai"
import { generateText } from "ai"
import { createAiGateway } from "ai-gateway-provider"
import { createUnified } from "ai-gateway-provider/providers/unified"
import { createOpenAI } from "ai-gateway-provider/providers/openai"
import { createAnthropic } from "ai-gateway-provider/providers/anthropic"
import { ProviderTransform } from "@/provider/transform"
import type * as Provider from "@/provider/provider"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
type Captured = { url: string; outerBody: unknown; headers: Record<string, string> }
type Captured = { url: string; outerBody: unknown }
type ProviderOptions = Record<string, Record<string, JSONValue>>
const realFetch = globalThis.fetch
@@ -30,76 +26,24 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
// The gateway returns the upstream provider's response body verbatim, so the
// mock must answer in the wire format of the step's target provider.
function upstreamResponseBody(provider: string | undefined) {
if (provider === "openai")
return {
id: "resp_test",
object: "response",
created_at: 0,
model: "gpt-5.4",
status: "completed",
error: null,
incomplete_details: null,
output: [
{
type: "message",
role: "assistant",
id: "msg_1",
status: "completed",
content: [{ type: "output_text", text: "ok", annotations: [] }],
},
],
usage: {
input_tokens: 1,
input_tokens_details: { cached_tokens: 0 },
output_tokens: 1,
output_tokens_details: { reasoning_tokens: 0 },
total_tokens: 2,
},
}
if (provider === "anthropic")
return {
id: "msg_test",
type: "message",
role: "assistant",
model: "claude-sonnet-4-6",
content: [{ type: "text", text: "ok" }],
stop_reason: "end_turn",
stop_sequence: null,
usage: { input_tokens: 1, output_tokens: 1 },
}
return {
id: "chatcmpl-test",
object: "chat.completion",
created: 0,
model: "test",
choices: [{ index: 0, message: { role: "assistant", content: "ok" }, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}
}
beforeEach(() => {
captured = null
const handle = async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]): Promise<Response> => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url
if (url.startsWith("https://gateway.ai.cloudflare.com/")) {
const bodyText = typeof init?.body === "string" ? init.body : ""
const outerBody = bodyText ? JSON.parse(bodyText) : null
captured = {
url,
outerBody,
headers: Object.fromEntries(new Headers(init?.headers).entries()),
}
const provider =
Array.isArray(outerBody) && isRecord(outerBody[0]) && typeof outerBody[0].provider === "string"
? outerBody[0].provider
: undefined
return new Response(JSON.stringify(upstreamResponseBody(provider)), {
status: 200,
headers: { "Content-Type": "application/json" },
})
captured = { url, outerBody: bodyText ? JSON.parse(bodyText) : null }
return new Response(
JSON.stringify({
id: "chatcmpl-test",
object: "chat.completion",
created: 0,
model: "openai/gpt-5.4",
choices: [{ index: 0, message: { role: "assistant", content: "ok" }, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
)
}
return realFetch(input, init)
}
@@ -112,19 +56,11 @@ afterEach(() => {
globalThis.fetch = realFetch
})
// Mirrors the runtime npm rewrite in provider.ts: openai/anthropic models carry
// their native SDK package so transforms key provider options correctly.
const cfNpm = (apiId: string) => {
if (apiId.startsWith("openai/")) return "@ai-sdk/openai"
if (apiId.startsWith("anthropic/")) return "@ai-sdk/anthropic"
return "ai-gateway-provider"
}
const cfModel = (apiId: string, releaseDate = "2026-03-05"): Provider.Model => ({
id: ModelV2.ID.make(`cloudflare-ai-gateway/${apiId}`),
providerID: ProviderV2.ID.make("cloudflare-ai-gateway"),
name: apiId,
api: { id: apiId, url: "https://gateway.ai.cloudflare.com/v1/compat", npm: cfNpm(apiId) },
api: { id: apiId, url: "https://gateway.ai.cloudflare.com/v1/compat", npm: "ai-gateway-provider" },
capabilities: {
reasoning: true,
temperature: false,
@@ -144,165 +80,53 @@ const cfModel = (apiId: string, releaseDate = "2026-03-05"): Provider.Model => (
// ai-gateway-provider sends an array of step descriptors; each entry's `query`
// is the body forwarded to the upstream provider.
function firstStep(body: unknown): Record<string, unknown> | undefined {
function extractUpstreamQuery(body: unknown): Record<string, unknown> | undefined {
if (!Array.isArray(body) || body.length === 0) return undefined
const first = body[0]
return isRecord(first) ? first : undefined
}
function extractUpstreamQuery(body: unknown): Record<string, unknown> | undefined {
const query = firstStep(body)?.query
if (!isRecord(first)) return undefined
const query = first.query
return isRecord(query) ? query : undefined
}
// Each step descriptor also carries the `headers` forwarded to the upstream provider.
function extractUpstreamHeaders(body: unknown): Record<string, unknown> | undefined {
const headers = firstStep(body)?.headers
return isRecord(headers) ? headers : undefined
}
// Mirrors the runtime routing in provider.ts getModel.
function gatewayModel(apiId: string, gatewayToken = "test") {
const aigateway = createAiGateway({ accountId: "test", gateway: "test", apiKey: gatewayToken })
if (apiId.startsWith("openai/")) return aigateway(createOpenAI()(apiId.slice("openai/".length)))
if (apiId.startsWith("anthropic/")) return aigateway(createAnthropic()(apiId.slice("anthropic/".length)))
const isWorkersAi = apiId.startsWith("workers-ai/") || apiId.startsWith("@cf/")
const unified = createUnified(isWorkersAi ? { apiKey: gatewayToken } : {})
return aigateway(unified(apiId))
}
async function callThroughGateway(apiId: string, providerOptions: ProviderOptions, gatewayToken = "test") {
await generateText({ model: gatewayModel(apiId, gatewayToken), prompt: "hi", providerOptions })
async function callThroughGateway(apiId: string, providerOptions: ProviderOptions) {
const aigateway = createAiGateway({ accountId: "test", gateway: "test", apiKey: "test" })
const unified = createUnified()
await generateText({ model: aigateway(unified(apiId)), prompt: "hi", providerOptions })
return extractUpstreamQuery(captured?.outerBody)
}
describe("cf-ai-gateway routing", () => {
test("openai/* rides the native OpenAI passthrough on the Responses API", async () => {
await callThroughGateway("openai/gpt-5.4", {})
const step = firstStep(captured?.outerBody)
expect(step?.provider).toBe("openai")
expect(step?.endpoint).toBe("v1/responses")
const upstream = extractUpstreamQuery(captured?.outerBody)
expect(upstream?.model).toBe("gpt-5.4")
})
test("anthropic/* rides the native Anthropic passthrough on the Messages API", async () => {
await callThroughGateway("anthropic/claude-sonnet-4-6", {})
const step = firstStep(captured?.outerBody)
expect(step?.provider).toBe("anthropic")
expect(step?.endpoint).toBe("v1/messages")
const upstream = extractUpstreamQuery(captured?.outerBody)
expect(upstream?.model).toBe("claude-sonnet-4-6")
})
test("workers-ai models stay on the unified /compat route", async () => {
await callThroughGateway("workers-ai/@cf/moonshotai/kimi-k2.6", {})
const step = firstStep(captured?.outerBody)
expect(step?.provider).toBe("compat")
expect(step?.endpoint).toBe("chat/completions")
const upstream = extractUpstreamQuery(captured?.outerBody)
expect(upstream?.model).toBe("workers-ai/@cf/moonshotai/kimi-k2.6")
})
})
describe("cf-ai-gateway end-to-end (regression: #24432)", () => {
test("ProviderTransform.providerOptions output puts reasoning effort on the Responses wire", async () => {
// The full chain the runtime exercises for OpenAI models:
// transform.providerOptions() -> "openai" key (npm rewritten to @ai-sdk/openai)
// -> OpenAIResponsesLanguageModel emits body.reasoning.effort
test("ProviderTransform.providerOptions output puts reasoning_effort on the wire", async () => {
// The full chain the runtime exercises:
// transform.providerOptions() -> openaiCompatible key
// -> @ai-sdk/openai-compatible reads it as compatibleOptions
// -> emits body.reasoning_effort
// -> ai-gateway-provider wraps the body and forwards to gateway.ai.cloudflare.com
const opts = ProviderTransform.providerOptions(cfModel("openai/gpt-5.4"), { reasoningEffort: "xhigh" })
expect(Object.keys(opts)).toEqual(["openai"])
expect(opts.openai.reasoningEffort).toBe("xhigh")
expect(opts).toEqual({ openaiCompatible: { reasoningEffort: "xhigh" } })
const upstream = await callThroughGateway("openai/gpt-5.4", opts)
expect((upstream?.reasoning as Record<string, unknown> | undefined)?.effort).toBe("xhigh")
expect(upstream?.reasoning_effort).toBe("xhigh")
})
test("variants() output for openai/gpt-5.4 lands xhigh on the wire", async () => {
// fromModelsDevModel resolves the native npm before computing variants, so
// OpenAI models get full Responses-flavored payloads (summary + encrypted
// reasoning include for stateless multi-turn reasoning).
// The other half of the bug: workflow `variant: xhigh` flows through variants()
// and must reach the wire. variants() returns the providerOptions payload
// unwrapped; providerOptions() wraps it under the SDK key.
const variants = ProviderTransform.variants(cfModel("openai/gpt-5.4"))
expect(variants.xhigh).toEqual({
reasoningEffort: "xhigh",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
})
expect(variants.xhigh).toEqual({ reasoningEffort: "xhigh" })
const opts = ProviderTransform.providerOptions(cfModel("openai/gpt-5.4"), variants.xhigh)
const upstream = await callThroughGateway("openai/gpt-5.4", opts)
const reasoning = upstream?.reasoning as Record<string, unknown> | undefined
expect(reasoning?.effort).toBe("xhigh")
expect(reasoning?.summary).toBe("auto")
})
test("reasoning effort variants for anthropic models land as native adaptive thinking", async () => {
// Mirrors the runtime catalog path: models.dev reasoning_options -> reasoningVariants
// computed on the native @ai-sdk/anthropic npm -> adaptive thinking + output_config.effort.
const model = cfModel("anthropic/claude-sonnet-4-6")
const variants = ProviderTransform.reasoningVariants(
{ reasoning_options: [{ type: "effort", values: ["low", "medium", "high"] }] } as never,
model,
)
expect(variants?.high).toMatchObject({ effort: "high" })
const opts = ProviderTransform.providerOptions(model, variants!.high)
expect(Object.keys(opts)).toEqual(["anthropic"])
const upstream = await callThroughGateway("anthropic/claude-sonnet-4-6", opts)
expect((upstream?.thinking as Record<string, unknown> | undefined)?.type).toBe("adaptive")
expect((upstream?.output_config as Record<string, unknown> | undefined)?.effort).toBe("high")
})
test("reasoning_effort still reaches the /compat wire for workers-ai models", async () => {
const model = cfModel("workers-ai/@cf/moonshotai/kimi-k2.6")
const opts = ProviderTransform.providerOptions(model, { reasoningEffort: "high" })
expect(opts).toEqual({ openaiCompatible: { reasoningEffort: "high" } })
const upstream = await callThroughGateway("workers-ai/@cf/moonshotai/kimi-k2.6", opts)
expect(upstream?.reasoning_effort).toBe("high")
expect(upstream?.reasoning_effort).toBe("xhigh")
})
test("legacy buggy key 'cloudflare-ai-gateway' does NOT reach the wire (proves the bug)", async () => {
// Sanity: confirms the bug class. If a future change accidentally restores
// providerID-keyed providerOptions, this test fails before users notice.
const upstream = await callThroughGateway("workers-ai/@cf/moonshotai/kimi-k2.6", {
const upstream = await callThroughGateway("openai/gpt-5.4", {
"cloudflare-ai-gateway": { reasoningEffort: "high" },
})
expect(upstream?.reasoning_effort).toBeUndefined()
})
})
describe("cf-ai-gateway token scoping (regression: #32051/#32052)", () => {
test("openai passthrough does NOT forward the Cloudflare token upstream", async () => {
await callThroughGateway("openai/gpt-5.4", {}, "cf-gateway-secret")
expect(captured?.headers["cf-aig-authorization"]).toBe("Bearer cf-gateway-secret")
// Security invariant: the Cloudflare token must never become the upstream provider's Authorization.
expect(extractUpstreamHeaders(captured?.outerBody)?.["authorization"]).toBeUndefined()
expect(JSON.stringify(captured?.outerBody)).not.toContain("cf-gateway-secret")
})
test("anthropic passthrough does NOT forward the Cloudflare token upstream", async () => {
await callThroughGateway("anthropic/claude-sonnet-4-6", {}, "cf-gateway-secret")
expect(captured?.headers["cf-aig-authorization"]).toBe("Bearer cf-gateway-secret")
expect(extractUpstreamHeaders(captured?.outerBody)?.["x-api-key"]).toBeUndefined()
expect(JSON.stringify(captured?.outerBody)).not.toContain("cf-gateway-secret")
})
test("workers-ai models DO forward the Cloudflare token upstream", async () => {
await callThroughGateway("workers-ai/@cf/google/gemma-4-26b-a4b-it", {}, "cf-gateway-secret")
expect(captured?.headers["cf-aig-authorization"]).toBe("Bearer cf-gateway-secret")
expect(extractUpstreamHeaders(captured?.outerBody)?.["authorization"]).toBe("Bearer cf-gateway-secret")
})
test("bare @cf/ Workers AI models DO forward the Cloudflare token upstream", async () => {
await callThroughGateway("@cf/meta/llama-3.1-8b-instruct", {}, "cf-gateway-secret")
expect(captured?.headers["cf-aig-authorization"]).toBe("Bearer cf-gateway-secret")
expect(extractUpstreamHeaders(captured?.outerBody)?.["authorization"]).toBe("Bearer cf-gateway-secret")
})
})
@@ -222,6 +222,11 @@ function cfg(compaction?: ConfigV1.Info["compaction"]) {
return Layer.succeed(Config.Service, TestConfig.make({ get: () => Effect.succeed({ ...base, compaction }) }))
}
function cfgAgent(agent: NonNullable<ConfigV1.Info["agent"]>) {
const config = Schema.decodeUnknownSync(ConfigV1.Info)({ agent }) as ConfigV1.Info
return Layer.succeed(Config.Service, TestConfig.make({ get: () => Effect.succeed(config) }))
}
const defaultProvider = wide()
const compactionTestNode = LayerNode.group([
SessionCompaction.node,
@@ -812,6 +817,75 @@ describe("session.compaction.prune", () => {
})
describe("session.compaction.process", () => {
itCompaction.instance(
"inherits the last agent when compaction is not configured",
() => {
const stub = llm()
let captured: LLM.StreamInput | undefined
stub.push(reply("summary", (input) => (captured = input)))
return Effect.gen(function* () {
const ssn = yield* SessionNs.Service
const session = yield* ssn.create({})
const msg = yield* createUserMessage(session.id, "hello")
const msgs = yield* ssn.messages({ sessionID: session.id })
yield* SessionCompaction.use.process({
parentID: msg.id,
messages: msgs,
sessionID: session.id,
auto: false,
})
expect(captured?.agent.name).toBe("build")
expect(captured?.agent.prompt).toBe("custom build prompt")
expect(captured?.agent.temperature).toBe(0.37)
expect(String(captured?.model.providerID)).toBe("test")
expect(String(captured?.model.id)).toBe("test-model")
}).pipe(
withCompaction({
llm: stub.llmLayer,
config: cfgAgent({
build: { prompt: "custom build prompt", temperature: 0.37, model: "missing/missing" },
}),
}),
)
},
{ git: true },
)
itCompaction.instance(
"preserves an explicitly configured compaction agent",
() => {
const stub = llm()
let captured: LLM.StreamInput | undefined
stub.push(reply("summary", (input) => (captured = input)))
return Effect.gen(function* () {
const ssn = yield* SessionNs.Service
const session = yield* ssn.create({})
const msg = yield* createUserMessage(session.id, "hello")
const msgs = yield* ssn.messages({ sessionID: session.id })
yield* SessionCompaction.use.process({
parentID: msg.id,
messages: msgs,
sessionID: session.id,
auto: false,
})
expect(captured?.agent.name).toBe("compaction")
expect(captured?.agent.prompt).toBe("custom compaction prompt")
expect(captured?.agent.temperature).toBe(0.81)
expect(captured?.agent.hidden).toBe(true)
}).pipe(
withCompaction({
llm: stub.llmLayer,
config: cfgAgent({ compaction: { prompt: "custom compaction prompt", temperature: 0.81 } }),
}),
)
},
{ git: true },
)
it.instance(
"throws when parent is not a user message",
Effect.gen(function* () {
@@ -444,6 +444,34 @@ const boot = Effect.fn("test.boot")(function* (input?: { title?: string }) {
// Loop semantics
it.instance("default compaction reuses the normal system and tools", () =>
Effect.gen(function* () {
const { llm } = yield* useServerConfig(providerCfg)
const { prompt, chat } = yield* boot()
const compact = yield* SessionCompaction.Service
yield* user(chat.id, "cache prefix message")
yield* llm.text("normal response")
yield* prompt.loop({ sessionID: chat.id })
yield* compact.create({ sessionID: chat.id, agent: "build", model: ref, auto: false })
yield* llm.text("compacted summary")
yield* prompt.loop({ sessionID: chat.id })
const inputs = yield* llm.inputs
expect(inputs).toHaveLength(2)
expect(inputs[1]?.tools).toEqual(inputs[0]?.tools)
const normalMessages = Array.isArray(inputs[0]?.messages) ? inputs[0].messages : []
const compactMessages = Array.isArray(inputs[1]?.messages) ? inputs[1].messages : []
const system = (message: unknown) =>
typeof message === "object" && message !== null && "role" in message && message.role === "system"
expect(compactMessages.find(system)).toEqual(normalMessages.find(system))
expect(JSON.stringify(compactMessages)).toContain("cache prefix message")
expect(JSON.stringify(compactMessages)).toContain("normal response")
expect(JSON.stringify(compactMessages)).toContain("Create a new anchored summary")
expect(JSON.stringify(compactMessages)).not.toContain("[User]: cache prefix message")
}),
)
noLLMServer.instance(
"loop exits immediately when last assistant has stop finish",
() =>
-2
View File
@@ -85,7 +85,6 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -172,7 +171,6 @@ https://opencode.ai/zen/v1/models
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -90,7 +90,6 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -179,7 +178,6 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**.
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -90,7 +90,6 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -179,7 +178,6 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**.
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -81,7 +81,6 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -168,7 +167,6 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -90,7 +90,6 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -179,7 +178,6 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -81,7 +81,6 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -168,7 +167,6 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -90,7 +90,6 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -179,7 +178,6 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**.
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -81,7 +81,6 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -168,7 +167,6 @@ https://opencode.ai/zen/v1/models
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -81,7 +81,6 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -168,7 +167,6 @@ https://opencode.ai/zen/v1/models
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -90,7 +90,6 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -179,7 +178,6 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**.
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -90,7 +90,6 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -179,7 +178,6 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów*
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
@@ -81,7 +81,6 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -168,7 +167,6 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**.
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -90,7 +90,6 @@ OpenCode Zen работает как любой другой провайдер
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -179,7 +178,6 @@ https://opencode.ai/zen/v1/models
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -83,7 +83,6 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -170,7 +169,6 @@ https://opencode.ai/zen/v1/models
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -81,7 +81,6 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -168,7 +167,6 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
-2
View File
@@ -90,7 +90,6 @@ You can also access our models through the following API endpoints.
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -179,7 +178,6 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
@@ -81,7 +81,6 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -168,7 +167,6 @@ https://opencode.ai/zen/v1/models
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
@@ -85,7 +85,6 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
@@ -173,7 +172,6 @@ https://opencode.ai/zen/v1/models
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |