From 131ea15aa37f8a3f925a85bbf25cf8bada4c95b4 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Mon, 3 Aug 2026 22:11:50 +0000 Subject: [PATCH] fix(provider): centralize Azure request options --- packages/opencode/src/provider/transform.ts | 31 ++++++++-- packages/opencode/src/session/llm/request.ts | 13 ++--- .../opencode/test/provider/transform.test.ts | 57 ++++++++++++++++--- 3 files changed, 79 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 303ff11fdfd..53d24f3c997 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1270,11 +1270,6 @@ export function options(input: { result["gateway"] = { caching: "auto" } } - if (input.model.api.npm === "@ai-sdk/azure" && input.model.api.id.includes("gpt-5.5")) { - result["reasoningSummary"] = "auto" - return result - } - if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) { if (!input.model.api.id.includes("gpt-5-pro")) { result["reasoningEffort"] = "medium" @@ -1312,6 +1307,32 @@ export function options(input: { return result } +export function requestOptions(input: { + model: Provider.Model + providerOptions: Record + options: Record +}) { + if ( + input.model.api.npm !== "@ai-sdk/azure" || + !(input.model.options.useCompletionUrls ?? input.providerOptions.useCompletionUrls) + ) + return input.options + + const result = { ...input.options } + delete result.reasoningSummary + delete result.include + if (isGpt55OrNewer(input.model.api.id)) delete result.reasoningEffort + return result +} + +function isGpt55OrNewer(modelID: string) { + const match = /gpt-(\d)(?:[.-](\d+))?(?:[.-]|$)/i.exec(modelID) + if (!match) return false + const major = Number(match[1]) + const minor = Number(match[2] ?? 0) + return major > 5 || (major === 5 && minor >= 5) +} + export function smallOptions(model: Provider.Model) { const small = Object.values(model.variants ?? {})[0] ?? {} if ( diff --git a/packages/opencode/src/session/llm/request.ts b/packages/opencode/src/session/llm/request.ts index 4f93411107d..b52b33a9cfc 100644 --- a/packages/opencode/src/session/llm/request.ts +++ b/packages/opencode/src/session/llm/request.ts @@ -88,14 +88,11 @@ export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: Pre sessionID: input.sessionID, providerOptions: input.provider.options, }) - const options = mergeOptions(mergeOptions(mergeOptions(base, input.model.options), input.agent.options), variant) - if ( - input.model.api.npm === "@ai-sdk/azure" && - (input.provider.options.useCompletionUrls || input.model.options.useCompletionUrls || options.useCompletionUrls) - ) { - delete options.reasoningSummary - delete options.include - } + const options = ProviderTransform.requestOptions({ + model: input.model, + providerOptions: input.provider.options, + options: mergeOptions(mergeOptions(mergeOptions(base, input.model.options), input.agent.options), variant), + }) if (isOpenaiOauth) options.instructions = system.join("\n") const messages = diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 0b88fa59dff..a5c9c854d36 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -252,7 +252,7 @@ describe("ProviderTransform.options - setCacheKey", () => { expect(result.promptCacheKey).toBeUndefined() }) - test("should keep the Azure cache key for gpt-5.5 early return", () => { + test("should keep the Azure cache key for gpt-5.5 Responses defaults", () => { const result = ProviderTransform.options({ model: { ...mockModel, @@ -264,6 +264,7 @@ describe("ProviderTransform.options - setCacheKey", () => { }) expect(result.store).toBe(false) expect(result.reasoningSummary).toBe("auto") + expect(result.reasoningEffort).toBe("medium") expect(result.promptCacheKey).toBe(sessionID) }) @@ -525,13 +526,13 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => { expect(result.include).toBeUndefined() }) - test("azure chat completions omit Responses-only reasoning options after variants merge", async () => { + test("azure gpt-5.5 chat completions omit unsupported reasoning options after variants merge", async () => { const model = { - ...createGpt5Model("gpt-5.4"), - id: "azure/gpt-5.4", + ...createGpt5Model("gpt-5.5"), + id: "azure/gpt-5.5", providerID: "azure", api: { - id: "gpt-5.4", + id: "gpt-5.5", url: "https://azure.com", npm: "@ai-sdk/azure", }, @@ -551,7 +552,7 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => { role: "user", time: { created: Date.now() }, agent: "test", - model: { providerID: "azure", modelID: "gpt-5.4", variant: "high" }, + model: { providerID: "azure", modelID: "gpt-5.5", variant: "high" }, } as any, sessionID, model, @@ -580,7 +581,7 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => { isWorkflow: false, }), ) - expect(result.params.options.reasoningEffort).toBe("high") + expect(result.params.options.reasoningEffort).toBeUndefined() expect(result.params.options.reasoningSummary).toBeUndefined() expect(result.params.options.include).toBeUndefined() expect(result.tools.lookup.strict).toBe(false) @@ -681,14 +682,52 @@ describe("ProviderTransform.options - gpt-5 reasoningEffort", () => { expect(result.reasoningEffort).toBeUndefined() }) - test("gpt-5.5 should NOT set reasoningEffort", () => { + test("gpt-5.5 should set Responses reasoning options", () => { const result = ProviderTransform.options({ model: createModel("gpt-5.5"), sessionID, providerOptions: {}, }) - expect(result.reasoningEffort).toBeUndefined() + expect(result.reasoningEffort).toBe("medium") + expect(result.reasoningSummary).toBe("auto") + }) +}) + +describe("ProviderTransform.requestOptions - Azure endpoints", () => { + const options = { + reasoningEffort: "high", + reasoningSummary: "auto", + include: ["reasoning.encrypted_content"], + } + test.each([ + ["preserves Responses reasoning options", "gpt-5.5", {}, {}, options], + [ + "preserves reasoning effort for gpt-5.4 Chat Completions", + "gpt-5.4", + { useCompletionUrls: true }, + {}, + { reasoningEffort: "high" }, + ], + ["uses the model endpoint override", "gpt-5.5", { useCompletionUrls: true }, { useCompletionUrls: false }, options], + ["omits reasoning effort for gpt-5.5 Chat Completions", "gpt-5.5", { useCompletionUrls: true }, {}, {}], + ["omits reasoning effort for gpt-5.6 Chat Completions", "gpt-5.6", { useCompletionUrls: true }, {}, {}], + ["omits reasoning effort for gpt-6 Chat Completions", "gpt-6", { useCompletionUrls: true }, {}, {}], + [ + "does not treat gpt-50 as a versioned GPT model", + "gpt-50", + { useCompletionUrls: true }, + {}, + { reasoningEffort: "high" }, + ], + ])("%s", (_name, apiID, providerOptions, modelOptions, expected) => { + expect( + ProviderTransform.requestOptions({ + model: { api: { id: apiID, npm: "@ai-sdk/azure" }, options: modelOptions } as any, + providerOptions, + options, + }), + ).toEqual(expected) }) })