diff --git a/.changeset/fix-gpt-56-responses.md b/.changeset/fix-gpt-56-responses.md new file mode 100644 index 0000000000..bc55888035 --- /dev/null +++ b/.changeset/fix-gpt-56-responses.md @@ -0,0 +1,5 @@ +--- +"workers-ai-provider": patch +--- + +Route OpenAI gpt-5.6 models through the Responses API. diff --git a/packages/workers-ai-provider/src/openai.ts b/packages/workers-ai-provider/src/openai.ts index 7312f64e68..14e326d99b 100644 --- a/packages/workers-ai-provider/src/openai.ts +++ b/packages/workers-ai-provider/src/openai.ts @@ -11,9 +11,9 @@ import type { ProviderPlugin } from "./gateway-delegate"; * * Requires `@ai-sdk/openai` (an optional peer dependency — install it yourself). * - * Uses `.chat()` (Chat Completions) deliberately: the bare `openai()` call - * (AI SDK v6+) defaults to the Responses API, which the AI Gateway run catalog - * does not serve. + * Uses `.chat()` (Chat Completions) for the catalog's chat-completions models. + * The gpt-5.6 class only accepts the Responses API, so those models use + * `.responses()` instead. */ export const openai: ProviderPlugin = { wireFormat: "openai", @@ -22,5 +22,7 @@ export const openai: ProviderPlugin = { // and the delegate strips the Authorization header on the gateway path. // baseURL (set by the registry for non-OpenAI openai-wire providers) makes // the generated URL host-strip to the right gateway-native endpoint. - createOpenAI({ apiKey: "unused", fetch, ...(baseURL ? { baseURL } : {}) }).chat(modelId), + createOpenAI({ apiKey: "unused", fetch, ...(baseURL ? { baseURL } : {}) })[ + /^gpt-5\.6(?:-|$)/.test(modelId) ? "responses" : "chat" + ](modelId), }; diff --git a/packages/workers-ai-provider/test/openai.test.ts b/packages/workers-ai-provider/test/openai.test.ts new file mode 100644 index 0000000000..9ee9c48367 --- /dev/null +++ b/packages/workers-ai-provider/test/openai.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it, vi } from "vitest"; +import { openai } from "../src/openai"; + +async function requestedUrl(modelId: string, baseURL?: string) { + const fetch = vi.fn(async (input: RequestInfo | URL) => { + throw new Error(String(input)); + }); + + const model = openai.create({ + modelId, + fetch: fetch as typeof globalThis.fetch, + ...(baseURL ? { baseURL } : {}), + }); + + await expect(model.doGenerate({ prompt: [] } as never)).rejects.toThrow(); + return String(fetch.mock.calls[0]?.[0]); +} + +describe("openai provider plugin", () => { + it("uses the Responses API for gpt-5.6 models", async () => { + for (const modelId of ["gpt-5.6", "gpt-5.6-luna", "gpt-5.6-mini"]) { + expect(await requestedUrl(modelId)).toContain("/responses"); + } + }); + + it("keeps Chat Completions for ordinary OpenAI models", async () => { + for (const modelId of ["gpt-5.4-mini", "gpt-5.5"]) { + expect(await requestedUrl(modelId)).toContain("/chat/completions"); + } + }); + + it("preserves the Responses endpoint when a Gateway base URL is configured", async () => { + expect(await requestedUrl("gpt-5.6-luna", "https://gateway.example/v1")).toBe( + "https://gateway.example/v1/responses", + ); + }); +});