mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-05 18:22:56 +00:00
Refactor OpenAI provider identity so OpenAI remains the canonical provider for API-key and OAuth-backed flows while legacy openai-codex state is doctor/migration-only. Keeps OpenAI Codex Responses as an API/transport class rather than a provider identity, moves auth aliases through providerAuthAliases, updates doctor repair sequencing for old auth/profile state, and refreshes tests/docs around the canonical OpenAI behavior.
186 lines
5.4 KiB
TypeScript
186 lines
5.4 KiB
TypeScript
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const refreshOpenAICodexTokenMock = vi.hoisted(() => vi.fn());
|
|
const loginOpenAICodexDeviceCodeMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("./openai-codex-provider.runtime.js", () => ({
|
|
refreshOpenAICodexToken: refreshOpenAICodexTokenMock,
|
|
}));
|
|
|
|
vi.mock("./openai-codex-device-code.js", () => ({
|
|
loginOpenAICodexDeviceCode: loginOpenAICodexDeviceCodeMock,
|
|
}));
|
|
|
|
let buildOpenAIProvider: typeof import("./openai-provider.js").buildOpenAIProvider;
|
|
|
|
describe("OpenAI provider Codex transport hooks", () => {
|
|
beforeAll(async () => {
|
|
({ buildOpenAIProvider } = await import("./openai-provider.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
refreshOpenAICodexTokenMock.mockReset();
|
|
loginOpenAICodexDeviceCodeMock.mockReset();
|
|
});
|
|
|
|
it("exposes ChatGPT OAuth on the canonical OpenAI provider", () => {
|
|
const provider = buildOpenAIProvider();
|
|
|
|
expect(provider.id).toBe("openai");
|
|
expect(provider.aliases).toBeUndefined();
|
|
expect(provider.hookAliases).toEqual([
|
|
"openai-codex",
|
|
"azure-openai",
|
|
"azure-openai-responses",
|
|
]);
|
|
expect(provider.auth?.map((method) => method.id)).toEqual(["oauth", "device-code", "api-key"]);
|
|
expect(provider.auth?.map((method) => method.wizard?.choiceId)).toEqual([
|
|
"openai",
|
|
"openai-device-code",
|
|
"openai-api-key",
|
|
]);
|
|
expect(provider.oauthProfileIdRepairs).toEqual([
|
|
{
|
|
legacyProfileId: "openai-codex:default",
|
|
promptLabel: "OpenAI",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("stores device-code logins as OpenAI OAuth profiles", async () => {
|
|
const provider = buildOpenAIProvider();
|
|
const deviceCodeMethod = provider.auth?.find((method) => method.id === "device-code");
|
|
loginOpenAICodexDeviceCodeMock.mockResolvedValueOnce({
|
|
access: "access-token",
|
|
refresh: "refresh-token",
|
|
expires: 1_700_000_000_000,
|
|
});
|
|
|
|
const result = await deviceCodeMethod?.run({
|
|
isRemote: false,
|
|
openUrl: vi.fn(async () => {}),
|
|
prompter: {
|
|
note: vi.fn(async () => {}),
|
|
progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
|
|
},
|
|
runtime: { log: vi.fn(), error: vi.fn() },
|
|
config: {},
|
|
oauth: {},
|
|
} as never);
|
|
|
|
expect(result?.profiles?.[0]).toMatchObject({
|
|
profileId: "openai:default",
|
|
credential: {
|
|
type: "oauth",
|
|
provider: "openai",
|
|
access: "access-token",
|
|
refresh: "refresh-token",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("routes Codex-backed OpenAI models through the Codex Responses transport", () => {
|
|
const provider = buildOpenAIProvider();
|
|
|
|
const model = provider.resolveDynamicModel?.({
|
|
provider: "openai",
|
|
modelId: "gpt-5.4",
|
|
providerConfig: { api: "openai-codex-responses" },
|
|
modelRegistry: { find: () => null },
|
|
} as never);
|
|
|
|
expect(model).toMatchObject({
|
|
provider: "openai",
|
|
id: "gpt-5.4",
|
|
api: "openai-codex-responses",
|
|
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
});
|
|
});
|
|
|
|
it("keeps default Codex-backed OpenAI catalog models on the Codex Responses transport", () => {
|
|
const provider = buildOpenAIProvider();
|
|
|
|
const model = provider.resolveDynamicModel?.({
|
|
provider: "openai",
|
|
modelId: "gpt-5.5",
|
|
providerConfig: { api: "openai-codex-responses" },
|
|
modelRegistry: {
|
|
find: () => ({
|
|
provider: "openai",
|
|
id: "gpt-5.5",
|
|
name: "gpt-5.5",
|
|
api: "openai-responses",
|
|
baseUrl: "https://api.openai.com/v1",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
cost: { input: 1, output: 1, cacheRead: 1, cacheWrite: 1 },
|
|
contextWindow: 400_000,
|
|
maxTokens: 128_000,
|
|
}),
|
|
},
|
|
} as never);
|
|
|
|
expect(model).toMatchObject({
|
|
provider: "openai",
|
|
id: "gpt-5.5",
|
|
api: "openai-codex-responses",
|
|
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
});
|
|
});
|
|
|
|
it("keeps cloned Codex-backed OpenAI models on the Codex Responses transport", () => {
|
|
const provider = buildOpenAIProvider();
|
|
|
|
const model = provider.resolveDynamicModel?.({
|
|
provider: "openai",
|
|
modelId: "gpt-5.4",
|
|
providerConfig: { api: "openai-codex-responses" },
|
|
modelRegistry: {
|
|
find: () => ({
|
|
provider: "openai",
|
|
id: "gpt-5.4",
|
|
name: "gpt-5.4",
|
|
api: "openai-responses",
|
|
baseUrl: "https://api.openai.com/v1",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
cost: { input: 1, output: 1, cacheRead: 1, cacheWrite: 1 },
|
|
contextWindow: 128_000,
|
|
maxTokens: 16_384,
|
|
}),
|
|
},
|
|
} as never);
|
|
|
|
expect(model).toMatchObject({
|
|
provider: "openai",
|
|
id: "gpt-5.4",
|
|
api: "openai-codex-responses",
|
|
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
});
|
|
});
|
|
|
|
it("refreshes ChatGPT OAuth credentials under the OpenAI provider", async () => {
|
|
const provider = buildOpenAIProvider();
|
|
refreshOpenAICodexTokenMock.mockResolvedValueOnce({
|
|
access: "new-access",
|
|
refresh: "new-refresh",
|
|
expires: 1_700_000_000_000,
|
|
});
|
|
|
|
await expect(
|
|
provider.refreshOAuth?.({
|
|
type: "oauth",
|
|
provider: "openai",
|
|
access: "old-access",
|
|
refresh: "old-refresh",
|
|
expires: Date.now() - 60_000,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
type: "oauth",
|
|
provider: "openai",
|
|
access: "new-access",
|
|
refresh: "new-refresh",
|
|
});
|
|
});
|
|
});
|