diff --git a/packages/core/src/aisdk.ts b/packages/core/src/aisdk.ts index 3330c77f4a8..517f226d134 100644 --- a/packages/core/src/aisdk.ts +++ b/packages/core/src/aisdk.ts @@ -103,11 +103,12 @@ function wrapSSE(res: Response, ms: number, ctl: AbortController) { } function prepareOptions(model: ModelV2.Info, pkg: string) { + const projected = mapBodyToProviderOptions(model) const options: Record = { name: model.providerID, ...(model.settings ?? {}), headers: model.headers, - body: model.body, + body: projected.body, } const customFetch = options.fetch @@ -300,8 +301,9 @@ export const locationLayer = Layer.effect( export const defaultLayer = locationLayer function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) { - const settings = requestSettings(info.settings) - const optionKey = providerOptionKey(ProviderV2.packageName(info.package), info.providerID) + const packageName = ProviderV2.packageName(info.package) + const projected = mapBodyToProviderOptions(info) + const optionKey = providerOptionKey(packageName, info.providerID) const route: AnyRoute = { id: `ai-sdk:${ProviderV2.packageName(info.package) ?? "unknown"}`, provider: ProviderID.make(info.providerID), @@ -317,11 +319,14 @@ function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) { defaults: { headers: info.headers, http: - info.body === undefined && info.headers === undefined + projected.body === undefined && info.headers === undefined ? undefined - : { body: info.body === undefined ? undefined : { ...info.body }, headers: info.headers }, + : { + body: projected.body === undefined ? undefined : { ...projected.body }, + headers: info.headers, + }, limits: { context: info.limit.context, output: info.limit.output }, - providerOptions: settings === undefined ? undefined : { [optionKey]: settings }, + providerOptions: projected.settings === undefined ? undefined : { [optionKey]: projected.settings }, }, body: { schema: Schema.Unknown, @@ -356,6 +361,18 @@ function requestSettings(settings: Readonly> | undefined return Object.keys(result).length === 0 ? undefined : result } +function mapBodyToProviderOptions(model: ModelV2.Info) { + const settings = requestSettings(model.settings) + if (!Schema.is(Schema.Struct({ mode: Schema.Literal("pro") }))(model.body?.reasoning)) + return { settings, body: model.body } + const body = { ...model.body } + delete body.reasoning + return { + settings: ProviderV2.mergeOverlay(settings, { reasoningMode: "pro" }), + body: Object.keys(body).length === 0 ? undefined : body, + } +} + function callOptions(request: LLMRequest): LanguageModelV3CallOptions { return { prompt: prompt(request), diff --git a/packages/core/src/plugin/provider/openai-codex.ts b/packages/core/src/plugin/provider/openai-codex.ts index de31d39b394..8d4389d969e 100644 --- a/packages/core/src/plugin/provider/openai-codex.ts +++ b/packages/core/src/plugin/provider/openai-codex.ts @@ -31,7 +31,7 @@ export const accountID = (credential: CredentialLike | undefined) => { } const allowed = new Set(["gpt-5.5", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini"]) -const disallowed = new Set(["gpt-5.5-pro"]) +const disallowed = new Set(["gpt-5.5-pro", "gpt-5.6"]) /** Which API model ids a ChatGPT subscription may call through the codex backend. */ export const eligible = (apiID: string) => { diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index 5e4a1b08ef0..111f66105a2 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -194,6 +194,10 @@ export const OpenAIPlugin = define({ // ChatGPT-plan tokens only authorize codex-eligible models, and the // subscription covers usage, so hide the rest and zero the cost. evt.model.update(item.provider.id, model.id, (draft) => { + if (Schema.is(Schema.Struct({ mode: Schema.Literal("pro") }))(draft.body?.reasoning)) { + draft.enabled = false + return + } if (!OpenAICodex.eligible(draft.modelID ?? draft.id)) { draft.enabled = false return diff --git a/packages/core/test/aisdk.test.ts b/packages/core/test/aisdk.test.ts index 0a4b7dadce4..7efd04a4f60 100644 --- a/packages/core/test/aisdk.test.ts +++ b/packages/core/test/aisdk.test.ts @@ -68,6 +68,28 @@ it.effect("projects request settings, headers, and body overlays", () => }), ) +it.effect("maps pro reasoning bodies to AI SDK provider options", () => + Effect.gen(function* () { + const aisdk = yield* AISDK.Service + let body: unknown + yield* aisdk.hook.sdk((event) => { + body = event.options.body + event.sdk = { languageModel: () => ({ provider: event.model.providerID }) } + }) + + const resolved = yield* aisdk.model({ + ...model("@ai-sdk/openai"), + body: { reasoning: { mode: "pro" } }, + }) + const prepared = yield* LLMClient.prepare( + LLM.request({ model: resolved, prompt: "Hello" }), + ) + + expect(body).toBeUndefined() + expect(prepared.body.providerOptions).toEqual({ openai: { reasoningMode: "pro" } }) + }), +) + it.effect("projects replay metadata onto AI SDK prompt parts", () => Effect.gen(function* () { const aisdk = yield* AISDK.Service diff --git a/packages/core/test/plugin/provider-openai.test.ts b/packages/core/test/plugin/provider-openai.test.ts index 13109550c7f..ca4772a584a 100644 --- a/packages/core/test/plugin/provider-openai.test.ts +++ b/packages/core/test/plugin/provider-openai.test.ts @@ -198,6 +198,12 @@ describe("OpenAIPlugin", () => { ] }) catalog.model.update(item.id, ModelV2.ID.make("gpt-5.5-pro"), () => {}) + catalog.model.update(item.id, ModelV2.ID.make("gpt-5.4-pro"), (model) => { + model.modelID = ModelV2.ID.make("gpt-5.4") + model.body = { reasoning: { mode: "pro" } } + }) + catalog.model.update(item.id, ModelV2.ID.make("gpt-5.6"), () => {}) + catalog.model.update(item.id, ModelV2.ID.make("gpt-5.6-sol"), () => {}) catalog.model.update(item.id, ModelV2.ID.make("gpt-4.1"), () => {}) }) yield* credentials.create({ @@ -223,6 +229,13 @@ describe("OpenAIPlugin", () => { expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.5-pro"))).enabled).toBe( false, ) + expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.4-pro"))).enabled).toBe( + false, + ) + expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6"))).enabled).toBe(false) + expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6-sol"))).enabled).toBe( + true, + ) expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-4.1"))).enabled).toBe(false) }), )