Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion core/llm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1021,9 +1021,18 @@ export abstract class BaseLLM implements ILLM {
}

private canUseOpenAIResponses(options: CompletionOptions): boolean {
const isOfficialOpenAIAPI =
!this.apiBase ||
this.apiBase.trim() === "" ||
this.apiBase.replace(/\/$/, "") === "https://api.openai.com/v1";

const shouldUseResponses =
this._llmOptions.useResponsesApi === true ||
(this._llmOptions.useResponsesApi !== false && isOfficialOpenAIAPI);

return (
this.providerName === "openai" &&
this._llmOptions.useResponsesApi !== false &&
shouldUseResponses &&
typeof (this as any)._streamResponses === "function" &&
(this as any).isOSeriesOrGpt5PlusModel(options.model)
);
Expand Down
47 changes: 47 additions & 0 deletions core/llm/llms/OpenAI.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,51 @@ describe("OpenAI", () => {
expect(openai.isOSeriesOrGpt5PlusModel("ao31")).toBeFalsy();
expect(openai.isOSeriesOrGpt5PlusModel("1os")).toBeFalsy();
});

describe("canUseOpenAIResponses", () => {
test("should use responses on official OpenAI apiBase for o-series model", () => {
const openai = new OpenAI({
model: "o3-mini",
apiKey: "test",
});
expect((openai as any).canUseOpenAIResponses({ model: "o3-mini" })).toBe(
true,
);
});

test("should not use responses on custom apiBase by default", () => {
const openai = new OpenAI({
model: "o3-mini",
apiKey: "test",
apiBase: "https://custom.openai-proxy.com/v1/",
});
expect((openai as any).canUseOpenAIResponses({ model: "o3-mini" })).toBe(
false,
);
});

test("should allow forcing responses on custom apiBase when useResponsesApi is true", () => {
const openai = new OpenAI({
model: "o3-mini",
apiKey: "test",
apiBase: "https://custom.openai-proxy.com/v1/",
useResponsesApi: true,
});
expect((openai as any).canUseOpenAIResponses({ model: "o3-mini" })).toBe(
true,
);
});

test("should disable responses when useResponsesApi is false", () => {
const openai = new OpenAI({
model: "o3-mini",
apiKey: "test",
apiBase: "https://api.openai.com/v1",
useResponsesApi: false,
});
expect((openai as any).canUseOpenAIResponses({ model: "o3-mini" })).toBe(
false,
);
});
});
});
13 changes: 11 additions & 2 deletions packages/openai-adapters/src/apis/OpenAI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,17 @@ export class OpenAIApi implements BaseLlmApi {
if (this.config.useResponsesApi === false) {
return false;
}
const isOfficialOpenAIAPI = this.apiBase === "https://api.openai.com/v1/";
return isOfficialOpenAIAPI && isResponsesModel(model);
if (!isResponsesModel(model)) {
return false;
}
if (this.config.useResponsesApi === true) {
return true;
}
const isOfficialOpenAIAPI =
!this.apiBase ||
this.apiBase.trim() === "" ||
this.apiBase.replace(/\/$/, "") === "https://api.openai.com/v1";
return isOfficialOpenAIAPI;
}

modifyCompletionBody<
Expand Down
48 changes: 47 additions & 1 deletion packages/openai-adapters/src/test/openai-adapter.vitest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, vi } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { OpenAIApi } from "../apis/OpenAI.js";
import { createAdapterTests } from "./adapter-test-utils.js";

// Mock the fetch package (not needed for OpenAI but required by the shared test utils)
Expand All @@ -25,4 +26,49 @@ describe("OpenAI Adapter Tests", () => {
accept: "application/json",
},
});

describe("shouldUseResponsesEndpoint", () => {
it("should use responses endpoint on official apiBase for responses models", () => {
const api = new OpenAIApi({
provider: "openai",
apiKey: "test",
apiBase: "https://api.openai.com/v1/",
});
expect((api as any).shouldUseResponsesEndpoint("gpt-5")).toBe(true);
expect((api as any).shouldUseResponsesEndpoint("o3-mini")).toBe(true);
expect((api as any).shouldUseResponsesEndpoint("gpt-4o")).toBe(false);
});

it("should not use responses endpoint on custom apiBase by default", () => {
const api = new OpenAIApi({
provider: "openai",
apiKey: "test",
apiBase: "https://custom-openai-proxy.com/v1/",
});
expect((api as any).shouldUseResponsesEndpoint("gpt-5")).toBe(false);
expect((api as any).shouldUseResponsesEndpoint("o3-mini")).toBe(false);
});

it("should use responses endpoint on custom apiBase if useResponsesApi is true", () => {
const api = new OpenAIApi({
provider: "openai",
apiKey: "test",
apiBase: "https://custom-openai-proxy.com/v1/",
useResponsesApi: true,
});
expect((api as any).shouldUseResponsesEndpoint("gpt-5")).toBe(true);
expect((api as any).shouldUseResponsesEndpoint("o3-mini")).toBe(true);
expect((api as any).shouldUseResponsesEndpoint("gpt-4o")).toBe(false);
});

it("should not use responses endpoint when useResponsesApi is false", () => {
const api = new OpenAIApi({
provider: "openai",
apiKey: "test",
apiBase: "https://api.openai.com/v1/",
useResponsesApi: false,
});
expect((api as any).shouldUseResponsesEndpoint("gpt-5")).toBe(false);
});
});
});
Loading