diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 4fd84d86..5fad8aaf 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -1370,11 +1370,12 @@ async function loadModelsDevData(): Promise[]> { const npm = m.provider?.npm; let api: Api; let baseUrl: string; - let compat: OpenAICompletionsCompat | undefined; + let compat: OpenAICompletionsCompat | OpenAIResponsesCompat | undefined; if (npm === "@ai-sdk/openai") { api = "openai-responses"; baseUrl = `${variant.basePath}/v1`; + compat = { sessionAffinityFormat: "openai-nosession" }; } else if (npm === "@ai-sdk/anthropic") { api = "anthropic-messages"; // Anthropic SDK appends /v1/messages to baseURL diff --git a/packages/ai/src/providers/opencode.models.ts b/packages/ai/src/providers/opencode.models.ts index ad18727a..cd2cfd34 100644 --- a/packages/ai/src/providers/opencode.models.ts +++ b/packages/ai/src/providers/opencode.models.ts @@ -392,6 +392,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null}, input: ["text", "image"], @@ -410,6 +411,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null}, input: ["text", "image"], @@ -428,6 +430,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null}, input: ["text", "image"], @@ -446,6 +449,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null}, input: ["text", "image"], @@ -464,6 +468,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null}, input: ["text", "image"], @@ -482,6 +487,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null}, input: ["text", "image"], @@ -500,6 +506,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null}, input: ["text", "image"], @@ -518,6 +525,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -536,6 +544,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -554,6 +563,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -572,6 +582,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -590,6 +601,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -608,6 +620,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -626,6 +639,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -644,6 +658,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, input: ["text", "image"], @@ -662,6 +677,7 @@ export const OPENCODE_MODELS = { api: "openai-responses", provider: "opencode", baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, reasoning: true, thinkingLevelMap: {"off":null,"xhigh":"xhigh","minimal":null,"low":null}, input: ["text", "image"], @@ -674,6 +690,63 @@ export const OPENCODE_MODELS = { contextWindow: 1050000, maxTokens: 128000, } satisfies Model<"openai-responses">, + "gpt-5.6-luna": { + id: "gpt-5.6-luna", + name: "GPT-5.6 Luna", + api: "openai-responses", + provider: "opencode", + baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, + reasoning: true, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"}, + input: ["text", "image"], + cost: { + input: 1, + output: 6, + cacheRead: 0.1, + cacheWrite: 1.25, + }, + contextWindow: 1050000, + maxTokens: 128000, + } satisfies Model<"openai-responses">, + "gpt-5.6-sol": { + id: "gpt-5.6-sol", + name: "GPT-5.6 Sol", + api: "openai-responses", + provider: "opencode", + baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, + reasoning: true, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"}, + input: ["text", "image"], + cost: { + input: 5, + output: 30, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1050000, + maxTokens: 128000, + } satisfies Model<"openai-responses">, + "gpt-5.6-terra": { + id: "gpt-5.6-terra", + name: "GPT-5.6 Terra", + api: "openai-responses", + provider: "opencode", + baseUrl: "https://opencode.ai/zen/v1", + compat: {"sessionAffinityFormat":"openai-nosession"}, + reasoning: true, + thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"}, + input: ["text", "image"], + cost: { + input: 2.5, + output: 15, + cacheRead: 0.25, + cacheWrite: 3.125, + }, + contextWindow: 1050000, + maxTokens: 128000, + } satisfies Model<"openai-responses">, "grok-4.5": { id: "grok-4.5", name: "Grok 4.5", @@ -726,7 +799,7 @@ export const OPENCODE_MODELS = { cacheRead: 0, cacheWrite: 0, }, - contextWindow: 256000, + contextWindow: 190000, maxTokens: 64000, } satisfies Model<"openai-completions">, "kimi-k2.5": { diff --git a/packages/ai/test/openai-responses-copilot-provider.test.ts b/packages/ai/test/openai-responses-compat.test.ts similarity index 94% rename from packages/ai/test/openai-responses-copilot-provider.test.ts rename to packages/ai/test/openai-responses-compat.test.ts index 8eeedea7..cabab825 100644 --- a/packages/ai/test/openai-responses-copilot-provider.test.ts +++ b/packages/ai/test/openai-responses-compat.test.ts @@ -360,6 +360,26 @@ describe("openai-responses provider defaults", () => { expect(capturedPayload?.prompt_cache_key).toBe("session-proxy"); }); + it("uses OpenAI no-session format for OpenCode Responses models", async () => { + const model = getModel("opencode", "gpt-5.4"); + let capturedPayload: CapturedResponsesPayload | undefined; + const captured = await captureOpenAIResponseHeaders( + { + sessionId: "session-opencode", + onPayload: (payload) => { + capturedPayload = payload as CapturedResponsesPayload; + }, + }, + model, + ); + + expect(model.compat?.sessionAffinityFormat).toBe("openai-nosession"); + expect(captured.sessionId).toBeNull(); + expect(captured.clientRequestId).toBe("session-opencode"); + expect(captured.xSessionId).toBeNull(); + expect(capturedPayload?.prompt_cache_key).toBe("session-opencode"); + }); + it("can omit OpenAI session_id header while preserving other affinity data", async () => { const proxyModel: Model<"openai-responses"> = { ...getModel("openai", "gpt-5.4"),