diff --git a/packages/pi-ai/src/providers/azure-openai-responses.ts b/packages/pi-ai/src/providers/azure-openai-responses.ts index 4f474e1a5..6d8d7289d 100644 --- a/packages/pi-ai/src/providers/azure-openai-responses.ts +++ b/packages/pi-ai/src/providers/azure-openai-responses.ts @@ -15,6 +15,16 @@ import { AssistantMessageEventStream } from "../utils/event-stream.js"; import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js"; import { buildBaseOptions, clampReasoning } from "./simple-options.js"; +/** + * Clamp reasoning effort for models that don't support all levels. + * gpt-5.x models don't support "minimal" — map to "low". + */ +function clampReasoningForModel(modelName: string, effort: string): string { + const name = modelName.includes("/") ? modelName.split("/").pop()! : modelName; + if (name.startsWith("gpt-5") && effort === "minimal") return "low"; + return effort; +} + const DEFAULT_AZURE_API_VERSION = "v1"; const AZURE_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode", "azure-openai-responses"]); @@ -234,8 +244,9 @@ function buildParams( if (model.reasoning) { if (options?.reasoningEffort || options?.reasoningSummary) { + const effort = clampReasoningForModel(model.name, options?.reasoningEffort || "medium") as typeof options.reasoningEffort; params.reasoning = { - effort: options?.reasoningEffort || "medium", + effort: effort || "medium", summary: options?.reasoningSummary || "auto", }; params.include = ["reasoning.encrypted_content"]; diff --git a/packages/pi-ai/src/providers/openai-responses.ts b/packages/pi-ai/src/providers/openai-responses.ts index a2e56404e..931fb2711 100644 --- a/packages/pi-ai/src/providers/openai-responses.ts +++ b/packages/pi-ai/src/providers/openai-responses.ts @@ -18,6 +18,16 @@ import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copi import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js"; import { buildBaseOptions, clampReasoning } from "./simple-options.js"; +/** + * Clamp reasoning effort for models that don't support all levels. + * gpt-5.x models don't support "minimal" — map to "low". + */ +function clampReasoningForModel(modelName: string, effort: string): string { + const name = modelName.includes("/") ? modelName.split("/").pop()! : modelName; + if (name.startsWith("gpt-5") && effort === "minimal") return "low"; + return effort; +} + const OPENAI_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]); /** @@ -215,8 +225,9 @@ function buildParams(model: Model<"openai-responses">, context: Context, options if (model.reasoning) { if (options?.reasoningEffort || options?.reasoningSummary) { + const effort = clampReasoningForModel(model.name, options?.reasoningEffort || "medium") as typeof options.reasoningEffort; params.reasoning = { - effort: options?.reasoningEffort || "medium", + effort: effort || "medium", summary: options?.reasoningSummary || "auto", }; params.include = ["reasoning.encrypted_content"];