























@@ -2,16 +2,25 @@ import { afterEach, describe, expect, it, vi } from "vitest";
22import { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js";
3344const {
5+ ensureAuthProfileStoreMock,
56 isProviderApiKeyConfiguredMock,
7+ listProfilesForProviderMock,
68 resolveApiKeyForProviderMock,
79 postJsonRequestMock,
810 postMultipartRequestMock,
911 assertOkOrThrowHttpErrorMock,
1012 resolveProviderHttpRequestConfigMock,
1113} = vi.hoisted(() => ({
14+ensureAuthProfileStoreMock: vi.fn(() => ({ version: 1, profiles: {} })),
1215isProviderApiKeyConfiguredMock: vi.fn<
1316(params: { provider: string; agentDir?: string }) => boolean
1417>(() => false),
18+listProfilesForProviderMock: vi.fn(
19+(store: { profiles?: Record<string, { provider?: string }> }, provider: string) =>
20+Object.entries(store.profiles ?? {})
21+.filter(([, profile]) => profile.provider === provider)
22+.map(([profileId]) => profileId),
23+),
1524resolveApiKeyForProviderMock: vi.fn(
1625async (_params?: {
1726provider?: string;
@@ -31,7 +40,9 @@ const {
3140}));
32413342vi.mock("openclaw/plugin-sdk/provider-auth", () => ({
43+ensureAuthProfileStore: ensureAuthProfileStoreMock,
3444isProviderApiKeyConfigured: isProviderApiKeyConfiguredMock,
45+listProfilesForProvider: listProfilesForProviderMock,
3546}));
36473748vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => ({
@@ -96,10 +107,28 @@ function mockCodexAuthOnly() {
96107});
97108}
98109110+function createCodexOAuthAuthStore() {
111+return {
112+version: 1 as const,
113+profiles: {
114+"openai-codex:default": {
115+type: "oauth" as const,
116+provider: "openai-codex",
117+access: "codex-access",
118+refresh: "codex-refresh",
119+expires: Date.now() + 60_000,
120+},
121+},
122+};
123+}
124+99125describe("openai image generation provider", () => {
100126afterEach(() => {
127+ensureAuthProfileStoreMock.mockReset();
128+ensureAuthProfileStoreMock.mockReturnValue({ version: 1, profiles: {} });
101129isProviderApiKeyConfiguredMock.mockReset();
102130isProviderApiKeyConfiguredMock.mockReturnValue(false);
131+listProfilesForProviderMock.mockClear();
103132resolveApiKeyForProviderMock.mockReset();
104133resolveApiKeyForProviderMock.mockResolvedValue({ apiKey: "openai-key" });
105134postJsonRequestMock.mockReset();
@@ -412,6 +441,99 @@ describe("openai image generation provider", () => {
412441});
413442});
414443444+it("uses configured Codex OAuth directly instead of probing an available OpenAI API key", async () => {
445+resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
446+if (params?.provider === "openai") {
447+return { apiKey: "openai-key", source: "OPENAI_API_KEY", mode: "api-key" };
448+}
449+if (params?.provider === "openai-codex") {
450+return { apiKey: "codex-key", source: "profile:openai-codex:default", mode: "oauth" };
451+}
452+return {};
453+});
454+mockCodexImageStream({ imageData: "codex-image" });
455+456+const provider = buildOpenAIImageGenerationProvider();
457+const authStore = createCodexOAuthAuthStore();
458+const result = await provider.generateImage({
459+provider: "openai",
460+model: "gpt-image-2",
461+prompt: "Draw using configured Codex auth",
462+cfg: {},
463+ authStore,
464+});
465+466+expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
467+expect(resolveApiKeyForProviderMock).toHaveBeenCalledWith(
468+expect.objectContaining({
469+provider: "openai-codex",
470+store: authStore,
471+}),
472+);
473+expect(resolveApiKeyForProviderMock).not.toHaveBeenCalledWith(
474+expect.objectContaining({
475+provider: "openai",
476+}),
477+);
478+expect(postJsonRequestMock).toHaveBeenCalledWith(
479+expect.objectContaining({
480+url: "https://chatgpt.com/backend-api/codex/responses",
481+}),
482+);
483+expect(result.images[0]?.buffer).toEqual(Buffer.from("codex-image"));
484+});
485+486+it("uses direct OpenAI auth when custom OpenAI image config is explicit", async () => {
487+mockGeneratedPngResponse();
488+resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
489+if (params?.provider === "openai") {
490+return { apiKey: "openai-key", source: "models.json", mode: "api-key" };
491+}
492+if (params?.provider === "openai-codex") {
493+return { apiKey: "codex-key", source: "profile:openai-codex:default", mode: "oauth" };
494+}
495+return {};
496+});
497+498+const provider = buildOpenAIImageGenerationProvider();
499+const authStore = createCodexOAuthAuthStore();
500+await provider.generateImage({
501+provider: "openai",
502+model: "gpt-image-2",
503+prompt: "Draw using explicit direct config",
504+cfg: {
505+models: {
506+providers: {
507+openai: {
508+baseUrl: "https://api.openai.com/v1",
509+apiKey: "OPENAI_API_KEY",
510+models: [],
511+},
512+},
513+},
514+},
515+ authStore,
516+});
517+518+expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
519+expect(resolveApiKeyForProviderMock).toHaveBeenCalledWith(
520+expect.objectContaining({
521+provider: "openai",
522+store: authStore,
523+}),
524+);
525+expect(resolveApiKeyForProviderMock).not.toHaveBeenCalledWith(
526+expect.objectContaining({
527+provider: "openai-codex",
528+}),
529+);
530+expect(postJsonRequestMock).toHaveBeenCalledWith(
531+expect.objectContaining({
532+url: "https://api.openai.com/v1/images/generations",
533+}),
534+);
535+});
536+415537it("sends Codex reference images as Responses input images", async () => {
416538mockCodexAuthOnly();
417539mockCodexImageStream();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。