From 976398715fcacc289181d1682ca4e128418c0a07 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 22 Apr 2026 20:25:52 +0100 Subject: [PATCH] fix(image): resolve custom provider model IDs --- src/agents/tools/image-tool.helpers.ts | 8 ++++- src/agents/tools/image-tool.test.ts | 29 +++++++++++++++++++ ...media-tool-shared.model-resolution.test.ts | 17 +++++++++++ src/agents/tools/media-tool-shared.ts | 8 ++++- 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/agents/tools/image-tool.helpers.ts b/src/agents/tools/image-tool.helpers.ts index ead4656b080..9bba2d18cbc 100644 --- a/src/agents/tools/image-tool.helpers.ts +++ b/src/agents/tools/image-tool.helpers.ts @@ -145,5 +145,11 @@ export function resolveProviderVisionModelFromConfig(params: { const models = providerCfg?.models ?? []; const picked = models.find((m) => Boolean((m?.id ?? "").trim()) && m.input?.includes("image")); const id = (picked?.id ?? "").trim(); - return id ? `${params.provider}/${id}` : null; + if (!id) { + return null; + } + const slash = id.indexOf("/"); + const idProvider = slash === -1 ? "" : normalizeLowercaseStringOrEmpty(id.slice(0, slash)); + const selectedProvider = normalizeLowercaseStringOrEmpty(params.provider); + return idProvider && idProvider === selectedProvider ? id : `${params.provider}/${id}`; } diff --git a/src/agents/tools/image-tool.test.ts b/src/agents/tools/image-tool.test.ts index 62aa6c48790..7841cb0cf04 100644 --- a/src/agents/tools/image-tool.test.ts +++ b/src/agents/tools/image-tool.test.ts @@ -708,6 +708,35 @@ describe("image tool implicit imageModel config", () => { }); }); + it("does not double-prefix custom provider model IDs that already include the provider", async () => { + await withTempAgentDir(async (agentDir) => { + await writeAuthProfiles(agentDir, { + version: 1, + profiles: { + "kimchi:default": { type: "api_key", provider: "kimchi", key: "sk-test" }, + }, + }); + const cfg: OpenClawConfig = { + agents: { defaults: { model: { primary: "kimchi/text-1" } } }, + models: { + providers: { + kimchi: { + baseUrl: "https://example.com", + models: [ + makeModelDefinition("kimchi/text-1", ["text"]), + makeModelDefinition("kimchi/vision-1", ["text", "image"]), + ], + }, + }, + }, + }; + + expect(resolveImageModelConfigForTool({ cfg, agentDir })).toEqual({ + primary: "kimchi/vision-1", + }); + }); + }); + it("pairs a provider when config uses an alias key", async () => { await withTempAgentDir(async (agentDir) => { await writeAuthProfiles(agentDir, { diff --git a/src/agents/tools/media-tool-shared.model-resolution.test.ts b/src/agents/tools/media-tool-shared.model-resolution.test.ts index 44688f047ad..c05a4fb0066 100644 --- a/src/agents/tools/media-tool-shared.model-resolution.test.ts +++ b/src/agents/tools/media-tool-shared.model-resolution.test.ts @@ -27,4 +27,21 @@ describe("resolveModelFromRegistry", () => { }), ).toThrow("Unknown model: ollama/qwen3.5:397b-cloud"); }); + + it("falls back to provider-prefixed custom model IDs", () => { + const foundModel = { provider: "kimchi", id: "kimchi/claude-opus-4-6" }; + const find = vi.fn().mockReturnValueOnce(null).mockReturnValueOnce(foundModel); + + const result = resolveModelFromRegistry({ + modelRegistry: { find }, + provider: "kimchi", + modelId: "claude-opus-4-6", + }); + + expect(find.mock.calls).toEqual([ + ["kimchi", "claude-opus-4-6"], + ["kimchi", "kimchi/claude-opus-4-6"], + ]); + expect(result).toBe(foundModel); + }); }); diff --git a/src/agents/tools/media-tool-shared.ts b/src/agents/tools/media-tool-shared.ts index 4d7d54191ea..62b41b00e89 100644 --- a/src/agents/tools/media-tool-shared.ts +++ b/src/agents/tools/media-tool-shared.ts @@ -402,10 +402,16 @@ export function resolveModelFromRegistry(params: { modelId: string; }): Model { const resolvedRef = normalizeModelRef(params.provider, params.modelId); - const model = params.modelRegistry.find( + let model = params.modelRegistry.find( resolvedRef.provider, resolvedRef.model, ) as Model | null; + if (!model && !resolvedRef.model.includes("/")) { + model = params.modelRegistry.find( + resolvedRef.provider, + `${resolvedRef.provider}/${resolvedRef.model}`, + ) as Model | null; + } if (!model) { throw new Error(`Unknown model: ${resolvedRef.provider}/${resolvedRef.model}`); }