diff --git a/packages/core/src/model-resolver.ts b/packages/core/src/model-resolver.ts index 024ab8b048..4b69d1c500 100644 --- a/packages/core/src/model-resolver.ts +++ b/packages/core/src/model-resolver.ts @@ -256,7 +256,6 @@ const codexModel = ( const account = OpenAICodex.accountID(credential) return withDefaults(model, OpenAIResponses.route) .with({ - endpoint: { baseURL: OpenAICodex.baseURL }, auth: (key === undefined ? Auth.none : Auth.bearer(key)).andThen( account === undefined ? Auth.none : Auth.headers({ "chatgpt-account-id": account }), ), diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index e133d9edc2..0dc25c64e2 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -193,6 +193,7 @@ export const OpenAIPlugin = define({ if (!chatgpt) return const item = evt.provider.get(Provider.ID.openai) if (!item) return + item.provider.settings = Provider.mergeOverlay(item.provider.settings, { baseURL: OpenAICodex.baseURL }) for (const model of item.models.values()) { // ChatGPT-plan tokens only authorize codex-eligible models, and the // subscription covers usage, so hide the rest and zero the cost. diff --git a/packages/core/test/model-resolver.test.ts b/packages/core/test/model-resolver.test.ts index 547893a088..47428dbd00 100644 --- a/packages/core/test/model-resolver.test.ts +++ b/packages/core/test/model-resolver.test.ts @@ -307,7 +307,7 @@ describe("ModelResolver", () => { }), ) - it.effect("routes ChatGPT OAuth credentials to the codex backend", () => + it.effect("keeps an explicit endpoint for ChatGPT OAuth credentials", () => Effect.gen(function* () { const resolved = yield* ModelResolver.fromCatalogModel( model(Provider.aisdk("@ai-sdk/openai"), { @@ -328,21 +328,21 @@ describe("ModelResolver", () => { const headers = yield* resolved.route.auth.apply({ request, method: "POST", - url: "https://chatgpt.com/backend-api/codex/responses", + url: "https://openai.example/v1/responses", body: "{}", headers: Headers.empty, }) expect(resolved.route).toMatchObject({ id: "openai-responses", - endpoint: { baseURL: "https://chatgpt.com/backend-api/codex" }, + endpoint: { baseURL: "https://openai.example/v1" }, }) expect(headers.authorization).toBe("Bearer chatgpt-token") expect(headers["chatgpt-account-id"]).toBe("acct_123") }), ) - it.effect("routes native OpenAI provider packages with ChatGPT credentials to the codex backend", () => + it.effect("keeps an explicit endpoint for native OpenAI packages with ChatGPT credentials", () => Effect.gen(function* () { const resolved = yield* ModelResolver.fromCatalogModel( model("@opencode-ai/ai/providers/openai", { @@ -360,12 +360,12 @@ describe("ModelResolver", () => { const headers = yield* resolved.route.auth.apply({ request: LLM.request({ model: resolved, prompt: "Hello" }), method: "POST", - url: "https://chatgpt.com/backend-api/codex/responses", + url: "https://openai.example/v1/responses", body: "{}", headers: Headers.empty, }) - expect(resolved.route.endpoint.baseURL).toBe("https://chatgpt.com/backend-api/codex") + expect(resolved.route.endpoint.baseURL).toBe("https://openai.example/v1") expect(headers.authorization).toBe("Bearer chatgpt-token") expect(headers["chatgpt-account-id"]).toBe("acct_123") }), @@ -407,7 +407,7 @@ describe("ModelResolver", () => { }), ) - it.effect("routes ChatGPT OAuth credentials without an account id to the codex backend", () => + it.effect("keeps an explicit endpoint for ChatGPT OAuth credentials without an account id", () => Effect.gen(function* () { const resolved = yield* ModelResolver.fromCatalogModel( model(Provider.aisdk("@ai-sdk/openai"), { @@ -427,12 +427,12 @@ describe("ModelResolver", () => { const headers = yield* resolved.route.auth.apply({ request, method: "POST", - url: "https://chatgpt.com/backend-api/codex/responses", + url: "https://openai.example/v1/responses", body: "{}", headers: Headers.empty, }) - expect(resolved.route.endpoint.baseURL).toBe("https://chatgpt.com/backend-api/codex") + expect(resolved.route.endpoint.baseURL).toBe("https://openai.example/v1") expect(headers.authorization).toBe("Bearer chatgpt-token") expect(headers["chatgpt-account-id"]).toBeUndefined() }), diff --git a/packages/core/test/plugin/provider-openai.test.ts b/packages/core/test/plugin/provider-openai.test.ts index 75b6385c02..d8cbf0925a 100644 --- a/packages/core/test/plugin/provider-openai.test.ts +++ b/packages/core/test/plugin/provider-openai.test.ts @@ -208,6 +208,7 @@ describe("OpenAIPlugin", () => { const eligible = required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.5"))) expect(eligible.cost).toEqual([]) expect(eligible.enabled).toBe(true) + expect(eligible.settings?.baseURL).toBe("https://chatgpt.com/backend-api/codex") expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.5-pro"))).enabled).toBe( false, ) @@ -219,6 +220,15 @@ describe("OpenAIPlugin", () => { true, ) expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-4.1"))).enabled).toBe(false) + + yield* catalog.transform((catalog) => { + catalog.provider.update(Provider.ID.openai, (provider) => { + provider.settings = Provider.mergeOverlay(provider.settings, { baseURL: "https://proxy.example/v1" }) + }) + }) + expect( + required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.5"))).settings?.baseURL, + ).toBe("https://proxy.example/v1") }), )