fix(core): support OpenAI pro mode (#36896)
This commit is contained in:
@@ -103,11 +103,12 @@ function wrapSSE(res: Response, ms: number, ctl: AbortController) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function prepareOptions(model: ModelV2.Info, pkg: string) {
|
function prepareOptions(model: ModelV2.Info, pkg: string) {
|
||||||
|
const projected = mapBodyToProviderOptions(model)
|
||||||
const options: Record<string, any> = {
|
const options: Record<string, any> = {
|
||||||
name: model.providerID,
|
name: model.providerID,
|
||||||
...(model.settings ?? {}),
|
...(model.settings ?? {}),
|
||||||
headers: model.headers,
|
headers: model.headers,
|
||||||
body: model.body,
|
body: projected.body,
|
||||||
}
|
}
|
||||||
|
|
||||||
const customFetch = options.fetch
|
const customFetch = options.fetch
|
||||||
@@ -300,8 +301,9 @@ export const locationLayer = Layer.effect(
|
|||||||
export const defaultLayer = locationLayer
|
export const defaultLayer = locationLayer
|
||||||
|
|
||||||
function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) {
|
function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) {
|
||||||
const settings = requestSettings(info.settings)
|
const packageName = ProviderV2.packageName(info.package)
|
||||||
const optionKey = providerOptionKey(ProviderV2.packageName(info.package), info.providerID)
|
const projected = mapBodyToProviderOptions(info)
|
||||||
|
const optionKey = providerOptionKey(packageName, info.providerID)
|
||||||
const route: AnyRoute = {
|
const route: AnyRoute = {
|
||||||
id: `ai-sdk:${ProviderV2.packageName(info.package) ?? "unknown"}`,
|
id: `ai-sdk:${ProviderV2.packageName(info.package) ?? "unknown"}`,
|
||||||
provider: ProviderID.make(info.providerID),
|
provider: ProviderID.make(info.providerID),
|
||||||
@@ -317,11 +319,14 @@ function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) {
|
|||||||
defaults: {
|
defaults: {
|
||||||
headers: info.headers,
|
headers: info.headers,
|
||||||
http:
|
http:
|
||||||
info.body === undefined && info.headers === undefined
|
projected.body === undefined && info.headers === undefined
|
||||||
? 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 },
|
limits: { context: info.limit.context, output: info.limit.output },
|
||||||
providerOptions: settings === undefined ? undefined : { [optionKey]: settings },
|
providerOptions: projected.settings === undefined ? undefined : { [optionKey]: projected.settings },
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
schema: Schema.Unknown,
|
schema: Schema.Unknown,
|
||||||
@@ -356,6 +361,18 @@ function requestSettings(settings: Readonly<Record<string, unknown>> | undefined
|
|||||||
return Object.keys(result).length === 0 ? undefined : result
|
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 {
|
function callOptions(request: LLMRequest): LanguageModelV3CallOptions {
|
||||||
return {
|
return {
|
||||||
prompt: prompt(request),
|
prompt: prompt(request),
|
||||||
|
|||||||
@@ -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 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. */
|
/** Which API model ids a ChatGPT subscription may call through the codex backend. */
|
||||||
export const eligible = (apiID: string) => {
|
export const eligible = (apiID: string) => {
|
||||||
|
|||||||
@@ -194,6 +194,10 @@ export const OpenAIPlugin = define({
|
|||||||
// ChatGPT-plan tokens only authorize codex-eligible models, and the
|
// ChatGPT-plan tokens only authorize codex-eligible models, and the
|
||||||
// subscription covers usage, so hide the rest and zero the cost.
|
// subscription covers usage, so hide the rest and zero the cost.
|
||||||
evt.model.update(item.provider.id, model.id, (draft) => {
|
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)) {
|
if (!OpenAICodex.eligible(draft.modelID ?? draft.id)) {
|
||||||
draft.enabled = false
|
draft.enabled = false
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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<LanguageModelV3CallOptions>(
|
||||||
|
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", () =>
|
it.effect("projects replay metadata onto AI SDK prompt parts", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const aisdk = yield* AISDK.Service
|
const aisdk = yield* AISDK.Service
|
||||||
|
|||||||
@@ -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.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"), () => {})
|
catalog.model.update(item.id, ModelV2.ID.make("gpt-4.1"), () => {})
|
||||||
})
|
})
|
||||||
yield* credentials.create({
|
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(
|
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.5-pro"))).enabled).toBe(
|
||||||
false,
|
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)
|
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-4.1"))).enabled).toBe(false)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user