diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b9d2780d5c..7d2962dbaee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai - Discord/replies: run `message_sending` plugin hooks for Discord reply delivery, including DM targets, so plugins can transform or cancel outbound Discord replies consistently with other channels. Fixes #59350. (#71094) Thanks @wei840222. - Control UI/commands: carry provider-owned thinking option ids/labels in session rows and defaults so fresh sessions show and accept dynamic modes such as `adaptive`, `xhigh`, and `max`. Fixes #71269. Thanks @Young-Khalil. +- Image generation: make explicit `model=` overrides exact-only so failed `openai/gpt-image-2` requests no longer fall through to Gemini or other configured providers, and update `image_generate list` to mention OpenAI Codex OAuth as valid auth for `openai/gpt-image-2`. Fixes #71290 and #71231. Thanks @Young-Khalil and @steipete. - Providers/GitHub Copilot: keep the plugin stream wrapper from claiming transport selection before OpenClaw picks a boundary-aware stream path, avoiding Pi's stale fallback Copilot headers on normal model turns. Thanks @steipete. - Discord/subagents: pass runtime config into thread-bound native subagent binding and require it at the helper boundary so Discord channel resolution keeps account-aware config. Fixes #71054. (#70945) Thanks @jai. - Slack/Assistant: accept Slack Assistant DM `message_changed` events when their metadata identifies the human sender, while continuing to drop self-authored bot edits. Fixes #55445. Thanks @AlfredPros. diff --git a/docs/tools/image-generation.md b/docs/tools/image-generation.md index 7c3bcb310b3..d1f70df5660 100644 --- a/docs/tools/image-generation.md +++ b/docs/tools/image-generation.md @@ -172,10 +172,13 @@ When generating an image, OpenClaw tries providers in this order: - current default provider first - remaining registered image-generation providers in provider-id order -If a provider fails (auth error, rate limit, etc.), the next candidate is tried automatically. If all fail, the error includes details from each attempt. +If a provider fails (auth error, rate limit, etc.), the next configured candidate is tried automatically. If all fail, the error includes details from each attempt. Notes: +- A per-call `model` override is exact: OpenClaw tries only that provider/model + and does not continue to configured primary/fallback or auto-detected + providers. - Auto-detection is auth-aware. A provider default only enters the candidate list when OpenClaw can actually authenticate that provider. - Auto-detection is enabled by default. Set diff --git a/src/agents/tools/image-generate-tool.test.ts b/src/agents/tools/image-generate-tool.test.ts index 97a8f46dcdf..644bf6fc5cb 100644 --- a/src/agents/tools/image-generate-tool.test.ts +++ b/src/agents/tools/image-generate-tool.test.ts @@ -1070,7 +1070,9 @@ describe("createImageGenerateTool", () => { expect(text).toContain("gemini-3.1-flash-image-preview"); expect(text).toContain("gemini-3-pro-image-preview"); expect(text).toContain("auth: set GEMINI_API_KEY / GOOGLE_API_KEY to use google/*"); - expect(text).toContain("auth: set OPENAI_API_KEY to use openai/*"); + expect(text).toContain( + "auth: set OPENAI_API_KEY or configure OpenAI Codex OAuth for openai/gpt-image-2", + ); expect(text).toContain("editing up to 5 refs"); expect(text).toContain("aspect ratios 1:1, 16:9"); expect(result).toMatchObject({ diff --git a/src/agents/tools/image-generate-tool.ts b/src/agents/tools/image-generate-tool.ts index e9113052b97..7e92ed4268f 100644 --- a/src/agents/tools/image-generate-tool.ts +++ b/src/agents/tools/image-generate-tool.ts @@ -160,6 +160,19 @@ function getImageGenerationProviderAuthEnvVars(providerId: string): string[] { return getProviderEnvVars(providerId); } +function formatImageGenerationAuthHint(provider: { + id: string; + authEnvVars: readonly string[]; +}): string | undefined { + if (provider.id === "openai") { + return "set OPENAI_API_KEY or configure OpenAI Codex OAuth for openai/gpt-image-2"; + } + if (provider.authEnvVars.length === 0) { + return undefined; + } + return `set ${provider.authEnvVars.join(" / ")} to use ${provider.id}/*`; +} + export function resolveImageGenerationModelConfigForTool(params: { cfg?: OpenClawConfig; agentDir?: string; @@ -592,13 +605,12 @@ export function createImageGenerateTool(options?: { provider.models.length > 0 ? `models: ${provider.models.join(", ")}` : "models: unknown"; + const authHint = formatImageGenerationAuthHint(provider); return [ `${provider.id}${provider.defaultModel ? ` (default ${provider.defaultModel})` : ""}`, ` ${modelLine}`, ` configured: ${provider.configured ? "yes" : "no"}`, - ...(provider.authEnvVars.length > 0 - ? [` auth: set ${provider.authEnvVars.join(" / ")} to use ${provider.id}/*`] - : []), + ...(authHint ? [` auth: ${authHint}`] : []), ...(caps.length > 0 ? [` capabilities: ${caps.join("; ")}`] : []), ]; }); diff --git a/src/media-generation/runtime-shared.test.ts b/src/media-generation/runtime-shared.test.ts index 8d204850894..ec269e2151d 100644 --- a/src/media-generation/runtime-shared.test.ts +++ b/src/media-generation/runtime-shared.test.ts @@ -119,6 +119,33 @@ describe("media-generation runtime shared candidates", () => { expect(candidates).toEqual([{ provider: "google", model: "gemini-3.1-flash-image-preview" }]); }); + + it("treats an explicit model override as exact-only", () => { + const candidates = resolveCapabilityModelCandidates({ + cfg: { + agents: { + defaults: { + mediaGenerationAutoProviderFallback: false, + }, + }, + } as OpenClawConfig, + modelConfig: { + primary: "google/gemini-3.1-flash-image-preview", + fallbacks: ["fal/fal-ai/flux/dev"], + }, + modelOverride: "openai/gpt-image-2", + parseModelRef, + listProviders: () => [ + { + id: "google", + defaultModel: "gemini-3.1-flash-image-preview", + isConfigured: () => true, + }, + ], + }); + + expect(candidates).toEqual([{ provider: "openai", model: "gpt-image-2" }]); + }); }); describe("media-generation runtime shared normalization", () => { diff --git a/src/media-generation/runtime-shared.ts b/src/media-generation/runtime-shared.ts index df408be9055..0b27dc84baf 100644 --- a/src/media-generation/runtime-shared.ts +++ b/src/media-generation/runtime-shared.ts @@ -178,6 +178,11 @@ export function resolveCapabilityModelCandidates(params: { candidates.push(parsed); }; + const override = params.parseModelRef(params.modelOverride); + if (override) { + return [override]; + } + add(params.modelOverride); add(resolveAgentModelPrimaryValue(params.modelConfig)); for (const fallback of resolveAgentModelFallbackValues(params.modelConfig)) {