summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKobi Hudson <[email protected]>2026-04-16 13:01:21 -0700
committerGitHub <[email protected]>2026-04-16 15:01:21 -0500
commit5e650fd9e2c8ebaedb49af2ff771af9721782d98 (patch)
tree1c479d765b8d254d5790a5272a8c62c6a4922d70 /packages
parent76275fc3ab8f39cd02ae7eed87c47679e1f4c28e (diff)
downloadopencode-5e650fd9e2c8ebaedb49af2ff771af9721782d98.tar.gz
opencode-5e650fd9e2c8ebaedb49af2ff771af9721782d98.zip
fix(opencode): drop max_tokens for OpenAI reasoning models on Cloudflare AI Gateway (#22864)
Co-authored-by: Aiden Cline <[email protected]>
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/plugin/cloudflare.ts11
-rw-r--r--packages/opencode/test/plugin/cloudflare.test.ts68
2 files changed, 79 insertions, 0 deletions
diff --git a/packages/opencode/src/plugin/cloudflare.ts b/packages/opencode/src/plugin/cloudflare.ts
index 2ccf5168d..c4bf6bb8e 100644
--- a/packages/opencode/src/plugin/cloudflare.ts
+++ b/packages/opencode/src/plugin/cloudflare.ts
@@ -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
+ },
}
}
diff --git a/packages/opencode/test/plugin/cloudflare.test.ts b/packages/opencode/test/plugin/cloudflare.test.ts
new file mode 100644
index 000000000..5fa410683
--- /dev/null
+++ b/packages/opencode/test/plugin/cloudflare.test.ts
@@ -0,0 +1,68 @@
+import { expect, test } from "bun:test"
+import { CloudflareAIGatewayAuthPlugin } from "@/plugin/cloudflare"
+
+const pluginInput = {
+ client: {} as never,
+ project: {} as never,
+ directory: "",
+ worktree: "",
+ experimental_workspace: {
+ register() {},
+ },
+ serverUrl: new URL("https://example.com"),
+ $: {} as never,
+}
+
+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)
+ const out = makeHookOutput()
+ await hooks["chat.params"]!(makeHookInput({ apiId: "openai/gpt-5.2-codex", reasoning: true }), out)
+ expect(out.maxOutputTokens).toBeUndefined()
+})
+
+test("keeps maxOutputTokens for openai non-reasoning models", async () => {
+ const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
+ 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)
+})