diff --git a/CHANGELOG.md b/CHANGELOG.md index d291bec6669..5324d742bd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Docs: https://docs.openclaw.ai - Agents/subagents: add optional forked context for native `sessions_spawn` runs so agents can let a child inherit the requester transcript when needed, while keeping clean isolated sessions as the default; includes prompt guidance, context-engine hook metadata, docs, and QA coverage. - Codex harness: add structured debug logging for embedded harness selection decisions so `/status` stays simple while gateway logs explain auto-selection and Pi fallback reasons. (#70760) Thanks @100yenadmin. - Providers/OpenAI: add forward-compatible `gpt-5.5` and `gpt-5.5-pro` support for OpenAI API keys, OpenAI Codex OAuth, and the Codex CLI default model. -- Providers/OpenAI Codex: add image generation and reference-image editing through Codex OAuth, so `openai-codex/gpt-image-2` works without an `OPENAI_API_KEY`. Fixes #70703. +- Providers/OpenAI: add image generation and reference-image editing through Codex OAuth, so `openai/gpt-image-2` works without an `OPENAI_API_KEY`. Fixes #70703. ### Fixes diff --git a/docs/providers/openai.md b/docs/providers/openai.md index a0099b1a267..196a678b0f6 100644 --- a/docs/providers/openai.md +++ b/docs/providers/openai.md @@ -184,16 +184,17 @@ Choose your preferred auth method and follow the setup steps. The bundled `openai` plugin registers image generation through the `image_generate` tool. It supports both OpenAI API-key image generation and Codex OAuth image -generation. +generation through the same `openai/gpt-image-2` model ref. -| Capability | OpenAI API key | Codex OAuth | -| ------------------------- | ---------------------------------- | ---------------------------------- | -| Model ref | `openai/gpt-image-2` | `openai-codex/gpt-image-2` | -| Auth | `OPENAI_API_KEY` | OpenAI Codex OAuth sign-in | -| Max images per request | 4 | 4 | -| Edit mode | Enabled (up to 5 reference images) | Enabled (up to 5 reference images) | -| Size overrides | Supported, including 2K/4K sizes | Supported, including 2K/4K sizes | -| Aspect ratio / resolution | Not forwarded to OpenAI Images API | Mapped to supported size when safe | +| Capability | OpenAI API key | Codex OAuth | +| ------------------------- | ---------------------------------- | ------------------------------------ | +| Model ref | `openai/gpt-image-2` | `openai/gpt-image-2` | +| Auth | `OPENAI_API_KEY` | OpenAI Codex OAuth sign-in | +| Transport | OpenAI Images API | Codex Responses backend | +| Max images per request | 4 | 4 | +| Edit mode | Enabled (up to 5 reference images) | Enabled (up to 5 reference images) | +| Size overrides | Supported, including 2K/4K sizes | Supported, including 2K/4K sizes | +| Aspect ratio / resolution | Not forwarded to OpenAI Images API | Mapped to a supported size when safe | ```json5 { @@ -205,18 +206,6 @@ generation. } ``` -Use Codex OAuth instead: - -```json5 -{ - agents: { - defaults: { - imageGenerationModel: { primary: "openai-codex/gpt-image-2" }, - }, - }, -} -``` - See [Image Generation](/tools/image-generation) for shared tool parameters, provider selection, and failover behavior. @@ -225,12 +214,10 @@ See [Image Generation](/tools/image-generation) for shared tool parameters, prov editing. `gpt-image-1` remains usable as an explicit model override, but new OpenAI image workflows should use `openai/gpt-image-2`. -The `openai-codex` provider also exposes `gpt-image-2` for image generation and -reference-image editing through OpenAI Codex OAuth. Use -`openai-codex/gpt-image-2` when the agent is signed in with Codex OAuth but does -not have an `OPENAI_API_KEY`. OpenClaw resolves the stored Codex OAuth access -token for `openai-codex` and sends image requests through the Codex Responses -backend, so this path works without the public OpenAI Images API key. +For Codex OAuth installs, keep the same `openai/gpt-image-2` ref. If no +`OPENAI_API_KEY` is available, OpenClaw resolves the stored OAuth access token +for the `openai-codex` auth profile and sends image requests through the Codex +Responses backend, so this path works without the public OpenAI Images API key. Generate: @@ -238,18 +225,6 @@ Generate: /tool image_generate model=openai/gpt-image-2 prompt="A polished launch poster for OpenClaw on macOS" size=3840x2160 count=1 ``` -Generate with Codex OAuth: - -``` -/tool image_generate model=openai-codex/gpt-image-2 prompt="A polished launch poster for OpenClaw on macOS" size=3840x2160 count=1 -``` - -Edit with Codex OAuth: - -``` -/tool image_generate model=openai-codex/gpt-image-2 prompt="Preserve the object shape, change the material to translucent glass" image=/path/to/reference.png size=1024x1536 -``` - Edit: ``` diff --git a/docs/tools/image-generation.md b/docs/tools/image-generation.md index dd192f72620..9b99a072fd4 100644 --- a/docs/tools/image-generation.md +++ b/docs/tools/image-generation.md @@ -30,36 +30,25 @@ The tool only appears when at least one image generation provider is available. } ``` -Use Codex OAuth instead of an OpenAI API key: +Codex OAuth uses the same `openai/gpt-image-2` model ref. If no `OPENAI_API_KEY` +is available, OpenClaw resolves the existing `openai-codex` OAuth profile and +sends the image request through the Codex Responses backend. -```json5 -{ - agents: { - defaults: { - imageGenerationModel: { - primary: "openai-codex/gpt-image-2", - }, - }, - }, -} -``` - -3. Ask the agent: _"Generate an image of a friendly lobster mascot."_ +3. Ask the agent: _"Generate an image of a friendly robot mascot."_ The agent calls `image_generate` automatically. No tool allow-listing needed — it's enabled by default when a provider is available. ## Supported providers -| Provider | Default model | Edit support | API key | -| ------------ | -------------------------------- | ---------------------------------- | ----------------------------------------------------- | -| OpenAI | `gpt-image-2` | Yes (up to 4 images) | `OPENAI_API_KEY` | -| OpenAI Codex | `gpt-image-2` | Yes (up to 4 images) | OpenAI Codex OAuth | -| Google | `gemini-3.1-flash-image-preview` | Yes | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | -| fal | `fal-ai/flux/dev` | Yes | `FAL_KEY` | -| MiniMax | `image-01` | Yes (subject reference) | `MINIMAX_API_KEY` or MiniMax OAuth (`minimax-portal`) | -| ComfyUI | `workflow` | Yes (1 image, workflow-configured) | `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY` for cloud | -| Vydra | `grok-imagine` | No | `VYDRA_API_KEY` | -| xAI | `grok-imagine-image` | Yes (up to 5 images) | `XAI_API_KEY` | +| Provider | Default model | Edit support | Auth | +| -------- | -------------------------------- | ---------------------------------- | ----------------------------------------------------- | +| OpenAI | `gpt-image-2` | Yes (up to 4 images) | `OPENAI_API_KEY` or OpenAI Codex OAuth | +| Google | `gemini-3.1-flash-image-preview` | Yes | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | +| fal | `fal-ai/flux/dev` | Yes | `FAL_KEY` | +| MiniMax | `image-01` | Yes (subject reference) | `MINIMAX_API_KEY` or MiniMax OAuth (`minimax-portal`) | +| ComfyUI | `workflow` | Yes (1 image, workflow-configured) | `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY` for cloud | +| Vydra | `grok-imagine` | No | `VYDRA_API_KEY` | +| xAI | `grok-imagine-image` | Yes (up to 5 images) | `XAI_API_KEY` | Use `action: "list"` to inspect available providers and models at runtime: @@ -73,7 +62,7 @@ Use `action: "list"` to inspect available providers and models at runtime: | ------------- | -------- | ------------------------------------------------------------------------------------- | | `prompt` | string | Image generation prompt (required for `action: "generate"`) | | `action` | string | `"generate"` (default) or `"list"` to inspect providers | -| `model` | string | Provider/model override, e.g. `openai/gpt-image-2` or `openai-codex/gpt-image-2` | +| `model` | string | Provider/model override, e.g. `openai/gpt-image-2` | | `image` | string | Single reference image path or URL for edit mode | | `images` | string[] | Multiple reference images for edit mode (up to 5) | | `size` | string | Size hint: `1024x1024`, `1536x1024`, `1024x1536`, `2048x2048`, `3840x2160` | @@ -139,11 +128,12 @@ OpenAI, Google, and xAI support up to 5 reference images via the `images` parame ### OpenAI `gpt-image-2` -OpenAI image generation defaults to `openai/gpt-image-2` with `OPENAI_API_KEY`. -Use `openai-codex/gpt-image-2` to generate or edit images with the same Codex -OAuth sign-in used by `openai-codex` chat models. The older `openai/gpt-image-1` -model can still be selected explicitly, but new OpenAI image-generation and -image-editing requests should use `gpt-image-2`. +OpenAI image generation defaults to `openai/gpt-image-2`. It uses +`OPENAI_API_KEY` when available. If no API key is configured, OpenClaw reuses the +same `openai-codex` OAuth profile used by Codex subscription chat models and +sends the image request through the Codex Responses backend. The older +`openai/gpt-image-1` model can still be selected explicitly, but new OpenAI +image-generation and image-editing requests should use `gpt-image-2`. `gpt-image-2` supports both text-to-image generation and reference-image editing through the same `image_generate` tool. OpenClaw forwards `prompt`, @@ -169,18 +159,6 @@ Edit one local reference image: /tool image_generate action=generate model=openai/gpt-image-2 prompt="Keep the subject, replace the background with a bright studio setup" image=/path/to/reference.png size=1024x1536 ``` -Generate with Codex OAuth: - -``` -/tool image_generate action=generate model=openai-codex/gpt-image-2 prompt="A clean editorial poster for OpenClaw image generation" size=3840x2160 count=1 -``` - -Edit one local reference image with Codex OAuth: - -``` -/tool image_generate action=generate model=openai-codex/gpt-image-2 prompt="Keep the subject, replace the background with a bright studio setup" image=/path/to/reference.png size=1024x1536 -``` - Edit with multiple references: ``` diff --git a/extensions/openai/image-generation-provider.test.ts b/extensions/openai/image-generation-provider.test.ts index 371ada818ed..6d31cdcdcfe 100644 --- a/extensions/openai/image-generation-provider.test.ts +++ b/extensions/openai/image-generation-provider.test.ts @@ -1,8 +1,5 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { - buildOpenAICodexImageGenerationProvider, - buildOpenAIImageGenerationProvider, -} from "./image-generation-provider.js"; +import { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js"; const { resolveApiKeyForProviderMock, @@ -11,7 +8,13 @@ const { assertOkOrThrowHttpErrorMock, resolveProviderHttpRequestConfigMock, } = vi.hoisted(() => ({ - resolveApiKeyForProviderMock: vi.fn(async () => ({ apiKey: "openai-key" })), + resolveApiKeyForProviderMock: vi.fn( + async (_params?: { + provider?: string; + }): Promise<{ apiKey?: string; source?: string; mode?: string }> => ({ + apiKey: "openai-key", + }), + ), postJsonRequestMock: vi.fn(), postMultipartRequestMock: vi.fn(), assertOkOrThrowHttpErrorMock: vi.fn(async () => {}), @@ -76,9 +79,19 @@ function mockCodexImageStream(params: { imageData?: string; revisedPrompt?: stri })); } +function mockCodexAuthOnly() { + resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => { + if (params?.provider === "openai-codex") { + return { apiKey: "codex-key", source: "profile:openai-codex:default", mode: "oauth" }; + } + return {}; + }); +} + describe("openai image generation provider", () => { afterEach(() => { - resolveApiKeyForProviderMock.mockClear(); + resolveApiKeyForProviderMock.mockReset(); + resolveApiKeyForProviderMock.mockResolvedValue({ apiKey: "openai-key" }); postJsonRequestMock.mockReset(); postMultipartRequestMock.mockReset(); assertOkOrThrowHttpErrorMock.mockClear(); @@ -281,13 +294,14 @@ describe("openai image generation provider", () => { expect(result.images).toHaveLength(1); }); - it("registers Codex OAuth image generation through Responses streaming", async () => { + it("falls back to Codex OAuth image generation through Responses streaming", async () => { + mockCodexAuthOnly(); mockCodexImageStream({ imageData: "codex-image", revisedPrompt: "revised codex prompt" }); - const provider = buildOpenAICodexImageGenerationProvider(); + const provider = buildOpenAIImageGenerationProvider(); const authStore = { version: 1, profiles: {} }; const result = await provider.generateImage({ - provider: "openai-codex", + provider: "openai", model: "gpt-image-2", prompt: "Draw a Codex lighthouse", cfg: {}, @@ -296,6 +310,12 @@ describe("openai image generation provider", () => { size: "1024x1536", }); + expect(resolveApiKeyForProviderMock).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "openai", + store: authStore, + }), + ); expect(resolveApiKeyForProviderMock).toHaveBeenCalledWith( expect.objectContaining({ provider: "openai-codex", @@ -306,7 +326,7 @@ describe("openai image generation provider", () => { expect.objectContaining({ defaultBaseUrl: "https://chatgpt.com/backend-api/codex", defaultHeaders: expect.objectContaining({ - Authorization: "Bearer openai-key", + Authorization: "Bearer codex-key", Accept: "text/event-stream", }), provider: "openai-codex", @@ -353,11 +373,12 @@ describe("openai image generation provider", () => { }); it("sends Codex reference images as Responses input images", async () => { + mockCodexAuthOnly(); mockCodexImageStream(); - const provider = buildOpenAICodexImageGenerationProvider(); + const provider = buildOpenAIImageGenerationProvider(); await provider.generateImage({ - provider: "openai-codex", + provider: "openai", model: "gpt-image-2", prompt: "Use the reference image", cfg: {}, @@ -384,11 +405,12 @@ describe("openai image generation provider", () => { }); it("satisfies Codex count by issuing one Responses request per image", async () => { + mockCodexAuthOnly(); mockCodexImageStream({ imageData: "codex-image" }); - const provider = buildOpenAICodexImageGenerationProvider(); + const provider = buildOpenAIImageGenerationProvider(); const result = await provider.generateImage({ - provider: "openai-codex", + provider: "openai", model: "gpt-image-2", prompt: "Draw two Codex icons", cfg: {}, diff --git a/extensions/openai/image-generation-provider.ts b/extensions/openai/image-generation-provider.ts index 6e3df1af7ed..d01702a77e2 100644 --- a/extensions/openai/image-generation-provider.ts +++ b/extensions/openai/image-generation-provider.ts @@ -221,7 +221,7 @@ function extractCodexImageGenerationResult(params: { } function createOpenAIImageGenerationProviderBase(params: { - id: "openai" | "openai-codex"; + id: "openai"; label: string; isConfigured: ImageGenerationProvider["isConfigured"]; generateImage: ImageGenerationProvider["generateImage"]; @@ -255,6 +255,104 @@ function createOpenAIImageGenerationProviderBase(params: { }; } +async function resolveOptionalApiKeyForProvider( + params: Parameters[0], +) { + try { + return await resolveApiKeyForProvider(params); + } catch { + return null; + } +} + +async function generateOpenAICodexImage(params: { + req: Parameters[0]; + apiKey: string; +}): Promise { + const { req, apiKey } = params; + const inputImages = req.inputImages ?? []; + const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = + resolveProviderHttpRequestConfig({ + defaultBaseUrl: DEFAULT_OPENAI_CODEX_IMAGE_BASE_URL, + defaultHeaders: { + Authorization: `Bearer ${apiKey}`, + Accept: "text/event-stream", + }, + provider: "openai-codex", + api: "openai-codex-responses", + capability: "image", + transport: "http", + }); + + const model = req.model || DEFAULT_OPENAI_IMAGE_MODEL; + const count = req.count ?? 1; + const size = req.size ?? DEFAULT_SIZE; + headers.set("Content-Type", "application/json"); + const content: Array> = [ + { type: "input_text", text: req.prompt }, + ...inputImages.map((image) => ({ + type: "input_image", + image_url: toOpenAIDataUrl(image), + detail: "auto", + })), + ]; + const results: ImageGenerationResult[] = []; + for (let index = 0; index < count; index += 1) { + const requestResult = await postJsonRequest({ + url: `${baseUrl}/responses`, + headers, + body: { + model: "gpt-5.4", + input: [ + { + role: "user", + content, + }, + ], + instructions: OPENAI_CODEX_IMAGE_INSTRUCTIONS, + tools: [ + { + type: "image_generation", + model, + size, + }, + ], + tool_choice: { type: "image_generation" }, + stream: true, + store: false, + }, + timeoutMs: req.timeoutMs, + fetchFn: fetch, + allowPrivateNetwork, + dispatcherPolicy, + }); + const { response, release } = requestResult; + try { + await assertOkOrThrowHttpError(response, "OpenAI Codex image generation failed"); + results.push( + extractCodexImageGenerationResult({ + body: await readResponseBodyText(response), + model, + }), + ); + } finally { + await release(); + } + } + const images = results.flatMap((result) => result.images); + return { + images: images.map((image, index) => + Object.assign({}, image, { + fileName: `image-${index + 1}.png`, + }), + ), + model, + metadata: { + responses: results.map((result) => result.metadata).filter(Boolean), + }, + }; +} + export function buildOpenAIImageGenerationProvider(): ImageGenerationProvider { return createOpenAIImageGenerationProviderBase({ id: "openai", @@ -263,18 +361,31 @@ export function buildOpenAIImageGenerationProvider(): ImageGenerationProvider { isProviderApiKeyConfigured({ provider: "openai", agentDir, + }) || + isProviderApiKeyConfigured({ + provider: "openai-codex", + agentDir, }), async generateImage(req) { const inputImages = req.inputImages ?? []; const isEdit = inputImages.length > 0; - const auth = await resolveApiKeyForProvider({ + const auth = await resolveOptionalApiKeyForProvider({ provider: "openai", cfg: req.cfg, agentDir: req.agentDir, store: req.authStore, }); - if (!auth.apiKey) { - throw new Error("OpenAI API key missing"); + if (!auth?.apiKey) { + const codexAuth = await resolveOptionalApiKeyForProvider({ + provider: "openai-codex", + cfg: req.cfg, + agentDir: req.agentDir, + store: req.authStore, + }); + if (codexAuth?.apiKey) { + return generateOpenAICodexImage({ req, apiKey: codexAuth.apiKey }); + } + throw new Error("OpenAI API key or Codex OAuth missing"); } const rawBaseUrl = resolveConfiguredOpenAIBaseUrl(req.cfg); const isAzure = isAzureOpenAIBaseUrl(rawBaseUrl); @@ -382,108 +493,3 @@ export function buildOpenAIImageGenerationProvider(): ImageGenerationProvider { }, }); } - -export function buildOpenAICodexImageGenerationProvider(): ImageGenerationProvider { - return createOpenAIImageGenerationProviderBase({ - id: "openai-codex", - label: "OpenAI Codex", - isConfigured: ({ agentDir }) => - isProviderApiKeyConfigured({ - provider: "openai-codex", - agentDir, - }), - async generateImage(req) { - const inputImages = req.inputImages ?? []; - const auth = await resolveApiKeyForProvider({ - provider: "openai-codex", - cfg: req.cfg, - agentDir: req.agentDir, - store: req.authStore, - }); - if (!auth.apiKey) { - throw new Error("OpenAI Codex OAuth missing"); - } - - const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = - resolveProviderHttpRequestConfig({ - defaultBaseUrl: DEFAULT_OPENAI_CODEX_IMAGE_BASE_URL, - defaultHeaders: { - Authorization: `Bearer ${auth.apiKey}`, - Accept: "text/event-stream", - }, - provider: "openai-codex", - api: "openai-codex-responses", - capability: "image", - transport: "http", - }); - - const model = req.model || DEFAULT_OPENAI_IMAGE_MODEL; - const count = req.count ?? 1; - const size = req.size ?? DEFAULT_SIZE; - headers.set("Content-Type", "application/json"); - const content: Array> = [ - { type: "input_text", text: req.prompt }, - ...inputImages.map((image) => ({ - type: "input_image", - image_url: toOpenAIDataUrl(image), - detail: "auto", - })), - ]; - const results: ImageGenerationResult[] = []; - for (let index = 0; index < count; index += 1) { - const requestResult = await postJsonRequest({ - url: `${baseUrl}/responses`, - headers, - body: { - model: "gpt-5.4", - input: [ - { - role: "user", - content, - }, - ], - instructions: OPENAI_CODEX_IMAGE_INSTRUCTIONS, - tools: [ - { - type: "image_generation", - model, - size, - }, - ], - tool_choice: { type: "image_generation" }, - stream: true, - store: false, - }, - timeoutMs: req.timeoutMs, - fetchFn: fetch, - allowPrivateNetwork, - dispatcherPolicy, - }); - const { response, release } = requestResult; - try { - await assertOkOrThrowHttpError(response, "OpenAI Codex image generation failed"); - results.push( - extractCodexImageGenerationResult({ - body: await readResponseBodyText(response), - model, - }), - ); - } finally { - await release(); - } - } - const images = results.flatMap((result) => result.images); - return { - images: images.map((image, index) => - Object.assign({}, image, { - fileName: `image-${index + 1}.png`, - }), - ), - model, - metadata: { - responses: results.map((result) => result.metadata).filter(Boolean), - }, - }; - }, - }); -} diff --git a/extensions/openai/index.ts b/extensions/openai/index.ts index 8540217daf0..c7f4b9d5642 100644 --- a/extensions/openai/index.ts +++ b/extensions/openai/index.ts @@ -2,10 +2,7 @@ import { resolvePluginConfigObject } from "openclaw/plugin-sdk/config-runtime"; import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools"; import { buildOpenAICodexCliBackend } from "./cli-backend.js"; -import { - buildOpenAICodexImageGenerationProvider, - buildOpenAIImageGenerationProvider, -} from "./image-generation-provider.js"; +import { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js"; import { openaiCodexMediaUnderstandingProvider, openaiMediaUnderstandingProvider, @@ -52,7 +49,6 @@ export default definePluginEntry({ api.registerProvider(buildProviderWithPromptContribution(buildOpenAICodexProviderPlugin())); api.registerMemoryEmbeddingProvider(openAiMemoryEmbeddingProviderAdapter); api.registerImageGenerationProvider(buildOpenAIImageGenerationProvider()); - api.registerImageGenerationProvider(buildOpenAICodexImageGenerationProvider()); api.registerRealtimeTranscriptionProvider(buildOpenAIRealtimeTranscriptionProvider()); api.registerRealtimeVoiceProvider(buildOpenAIRealtimeVoiceProvider()); api.registerSpeechProvider(buildOpenAISpeechProvider()); diff --git a/extensions/openai/openclaw.plugin.json b/extensions/openai/openclaw.plugin.json index e5148bac7ec..100fed03b2b 100644 --- a/extensions/openai/openclaw.plugin.json +++ b/extensions/openai/openclaw.plugin.json @@ -54,7 +54,7 @@ "realtimeVoiceProviders": ["openai"], "memoryEmbeddingProviders": ["openai"], "mediaUnderstandingProviders": ["openai", "openai-codex"], - "imageGenerationProviders": ["openai", "openai-codex"], + "imageGenerationProviders": ["openai"], "videoGenerationProviders": ["openai"] }, "mediaUnderstandingProviderMetadata": { diff --git a/test/helpers/plugins/plugin-registration-contract-cases.ts b/test/helpers/plugins/plugin-registration-contract-cases.ts index cfff7e38f56..2eef2e7ff34 100644 --- a/test/helpers/plugins/plugin-registration-contract-cases.ts +++ b/test/helpers/plugins/plugin-registration-contract-cases.ts @@ -104,7 +104,7 @@ export const pluginRegistrationContractCases = { realtimeTranscriptionProviderIds: ["openai"], realtimeVoiceProviderIds: ["openai"], mediaUnderstandingProviderIds: ["openai", "openai-codex"], - imageGenerationProviderIds: ["openai", "openai-codex"], + imageGenerationProviderIds: ["openai"], requireSpeechVoices: true, requireDescribeImages: true, requireGenerateImage: true,