Skip to content

Commit 3221d43

Browse files
authored
fix(openai): image generation unavailable when openai provider set via config apiKey + custom baseUrl (#100745)
* fix(openai): treat a config apiKey as configured for image generation The openai image provider's isConfigured only consulted env vars / auth profiles (isProviderApiKeyConfigured) and considered a config apiKey only inside that env/profile-gated branch. A provider apiKey supplied directly in config (models.providers.openai.apiKey) — e.g. an AI-gateway token alongside a custom baseUrl — was reported as not-configured, even though the generate path resolves exactly that credential via resolveApiKeyForProvider and honors the config baseUrl. This made image generation behave differently from chat models, which authenticate purely from provider config. Recognize a config apiKey as configured so image generation works purely from config, like chat, with no OPENAI_API_KEY env var or auth profile. Env/profile and Codex/ChatGPT-OAuth branches are unchanged. Formatting verified with oxfmt --check (no node_modules in this worktree); full tests run in CI/Testbox. * fix(openai): require a non-empty config apiKey for image readiness * fix(openai): normalize config apiKey readiness via hasConfiguredSecretInput --------- Co-authored-by: Alex Knight <[email protected]>
1 parent 7634c81 commit 3221d43

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

extensions/openai/image-generation-provider.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ const {
5050
logInfoMock: vi.fn(),
5151
}));
5252

53-
vi.mock("openclaw/plugin-sdk/provider-auth", () => ({
53+
vi.mock("openclaw/plugin-sdk/provider-auth", async (importOriginal) => ({
54+
...(await importOriginal<typeof import("openclaw/plugin-sdk/provider-auth")>()),
5455
ensureAuthProfileStore: ensureAuthProfileStoreMock,
5556
isProviderApiKeyConfigured: isProviderApiKeyConfiguredMock,
5657
listProfilesForProvider: listProfilesForProviderMock,
@@ -409,6 +410,61 @@ describe("openai image generation provider", () => {
409410
).toBe(false);
410411
});
411412

413+
it("reports configured from a config apiKey (gateway-routed openai) with no env/profile creds", () => {
414+
const provider = buildOpenAIImageGenerationProvider();
415+
416+
// Config-only auth: a provider apiKey in config, with no env var and no
417+
// auth profile.
418+
isProviderApiKeyConfiguredMock.mockReturnValue(false);
419+
ensureAuthProfileStoreMock.mockReturnValue({ version: 1, profiles: {} });
420+
421+
expect(
422+
provider.isConfigured?.({
423+
agentDir: "/tmp/agent",
424+
cfg: {
425+
models: {
426+
providers: {
427+
openai: {
428+
baseUrl: "https://gateway.example.test/openai/v1",
429+
apiKey: "gateway-token",
430+
models: [],
431+
},
432+
},
433+
},
434+
},
435+
}),
436+
).toBe(true);
437+
});
438+
439+
it.each([
440+
["empty", ""],
441+
["whitespace-only", " "],
442+
])("treats a %s config apiKey as not configured", (_label, apiKey) => {
443+
const provider = buildOpenAIImageGenerationProvider();
444+
445+
// Blank placeholders resolve to no usable credential in the generate
446+
// path, so readiness must not count them either.
447+
isProviderApiKeyConfiguredMock.mockReturnValue(false);
448+
ensureAuthProfileStoreMock.mockReturnValue({ version: 1, profiles: {} });
449+
450+
expect(
451+
provider.isConfigured?.({
452+
agentDir: "/tmp/agent",
453+
cfg: {
454+
models: {
455+
providers: {
456+
openai: {
457+
baseUrl: "https://gateway.example.test/openai/v1",
458+
apiKey,
459+
models: [],
460+
},
461+
},
462+
},
463+
},
464+
}),
465+
).toBe(false);
466+
});
467+
412468
it("reports ChatGPT OAuth image auth as configured for ChatGPT routes", () => {
413469
const provider = buildOpenAIImageGenerationProvider();
414470

extensions/openai/image-generation-provider.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
1717
import { MAX_IMAGE_BYTES } from "openclaw/plugin-sdk/media-runtime";
1818
import {
1919
ensureAuthProfileStore,
20+
hasConfiguredSecretInput,
2021
isProviderApiKeyConfigured,
2122
listProfilesForProvider,
2223
type AuthProfileStore,
@@ -829,6 +830,12 @@ export function buildOpenAIImageGenerationProvider(): ImageGenerationProvider {
829830
id: "openai",
830831
label: "OpenAI",
831832
isConfigured: ({ cfg, agentDir }) => {
833+
// generateImage already authenticates from a config apiKey; count a
834+
// usable one (non-blank literal or secret ref) as configured here too,
835+
// so image gen works from config alone, like chat.
836+
if (hasConfiguredSecretInput(cfg?.models?.providers?.openai?.apiKey)) {
837+
return true;
838+
}
832839
const configuredBaseUrl = resolveConfiguredOpenAIBaseUrl(cfg);
833840
const hasPublicOpenAIBaseUrl = isPublicOpenAIImageBaseUrl(configuredBaseUrl);
834841
const hasChatGPTRouteConfig = hasChatGPTImageRouteConfig(cfg);

0 commit comments

Comments
 (0)