diff --git a/sdk/packages/llms/src/providers/model-facts.ts b/sdk/packages/llms/src/providers/model-facts.ts index 2692c06270..d0c2717757 100644 --- a/sdk/packages/llms/src/providers/model-facts.ts +++ b/sdk/packages/llms/src/providers/model-facts.ts @@ -247,7 +247,8 @@ export function isQwenModel(options: { } // OpenAI reasoning-era chat models: the o-series (o1/o3/o4, including -mini, -// -pro, and dated variants) and the gpt-5 family (including gpt-5-chat). +// -pro, and dated variants) and gpt-5 and every later single-digit family +// (gpt-5-chat, gpt-5.6-sol, gpt-6-sol, gpt-6.1-sol, ...). // These models diverge from classic chat-completions parameter rules — most // importantly they reject `max_tokens` and require `max_completion_tokens`. // Detection is an id-pattern fallback (mirroring the legacy extension's @@ -257,11 +258,13 @@ export function isQwenModel(options: { // match, while namespaced ids like "openai/o3-mini" do. // // Maintenance: -// - If OpenAI ships a new family with the same parameter rules (e.g. gpt-6), -// add a pattern here and cases to the positive/negative lists in -// `vendors/openai-compatible.test.ts`. The failure mode until then is the -// loud, self-describing OpenAI 400 ("'max_tokens' is not supported with -// this model. Use 'max_completion_tokens' instead."), not silent breakage. +// - New gpt families up to gpt-9 match without changes. The version digit +// stays single on purpose: two digits would match Azure's `gpt-35-turbo`. +// For a new family outside this pattern, add one here and cases to the +// positive/negative lists in `vendors/openai-compatible.test.ts`. The +// failure mode until then is the loud, self-describing OpenAI 400 +// ("'max_tokens' is not supported with this model. Use +// 'max_completion_tokens' instead."), not silent breakage. // - Keep the boundary anchoring on BOTH sides of each pattern; loosening it // risks renaming the parameter for unrelated third-party models. // - Only `withMaxCompletionTokensForReasoningModels` in @@ -269,8 +272,8 @@ export function isQwenModel(options: { // `@ai-sdk/openai-compatible` maps `max_completion_tokens` itself (as // `@ai-sdk/openai` already does), delete that transform and this helper. const OPENAI_O_SERIES_MODEL_ID_PATTERN = /(^|[^a-z0-9])o[134](?=$|[^a-z0-9])/; -const OPENAI_GPT5_FAMILY_MODEL_ID_PATTERN = - /(^|[^a-z0-9])gpt-?5(?=$|[^a-z0-9])/; +const OPENAI_GPT5_AND_LATER_MODEL_ID_PATTERN = + /(^|[^a-z0-9])gpt-?[5-9](?=$|[^a-z0-9])/; export function isOpenAIReasoningEraModelId( modelId: string | undefined, @@ -282,7 +285,7 @@ export function isOpenAIReasoningEraModelId( return ( OPENAI_O_SERIES_MODEL_ID_PATTERN.test(normalized) || - OPENAI_GPT5_FAMILY_MODEL_ID_PATTERN.test(normalized) + OPENAI_GPT5_AND_LATER_MODEL_ID_PATTERN.test(normalized) ); } diff --git a/sdk/packages/llms/src/providers/vendors/openai-compatible.test.ts b/sdk/packages/llms/src/providers/vendors/openai-compatible.test.ts index f517c8f415..b8a71c66f6 100644 --- a/sdk/packages/llms/src/providers/vendors/openai-compatible.test.ts +++ b/sdk/packages/llms/src/providers/vendors/openai-compatible.test.ts @@ -11,7 +11,7 @@ import { } from "./openai-compatible"; describe("isOpenAIReasoningEraModelId", () => { - it("matches o-series and gpt-5-family model ids", () => { + it("matches o-series and gpt-5-and-later model ids", () => { for (const modelId of [ "o1", "o1-mini", @@ -32,6 +32,14 @@ describe("isOpenAIReasoningEraModelId", () => { "openai/gpt-5.2", "GPT-5-Mini", "azure-gpt-5-mini", + "gpt-5.6-sol", + "gpt-6", + "gpt-6-sol", + "gpt-6.1-sol", + "gpt-6-1-sol", + "openai/gpt-6-luna", + "azure/gpt-6-astra", + "gpt-7", ]) { expect(isOpenAIReasoningEraModelId(modelId), modelId).toBe(true); } @@ -57,6 +65,10 @@ describe("isOpenAIReasoningEraModelId", () => { "chatgpt-5", "somegpt-5-custom", "xgpt5", + "gpt-35-turbo-16k", + "gpt-55", + "gpt-6o", + "somegpt-6-custom", ]) { expect(isOpenAIReasoningEraModelId(modelId), modelId).toBe(false); } @@ -132,6 +144,17 @@ describe("createOpenAICompatibleProviderModule wire format", () => { expect(requestBody()).not.toHaveProperty("max_tokens"); }); + it("sends max_completion_tokens instead of max_tokens for gpt-6 streaming requests", async () => { + const { requestBody } = await stream({ modelId: "gpt-6.1-sol" }); + + expect(requestBody()).toMatchObject({ + model: "gpt-6.1-sol", + max_completion_tokens: 8_192, + stream: true, + }); + expect(requestBody()).not.toHaveProperty("max_tokens"); + }); + it("sends max_completion_tokens instead of max_tokens for o-series streaming requests", async () => { const { requestBody } = await stream({ modelId: "o3-mini" });