From 4604d252b27db83a2be78a8f183ea553d4208993 Mon Sep 17 00:00:00 2001 From: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:59:09 -0400 Subject: [PATCH] Media: allow active OpenRouter image models --- extensions/openrouter/index.test.ts | 2 +- extensions/openrouter/index.ts | 2 ++ .../media-understanding-provider.ts | 12 +++++++++ extensions/openrouter/openclaw.plugin.json | 3 +++ .../runner.vision-skip.test.ts | 26 +++++++++++++++++++ 5 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 extensions/openrouter/media-understanding-provider.ts diff --git a/extensions/openrouter/index.test.ts b/extensions/openrouter/index.test.ts index a7bc542241a..248f728bf87 100644 --- a/extensions/openrouter/index.test.ts +++ b/extensions/openrouter/index.test.ts @@ -28,7 +28,7 @@ describe("openrouter plugin", () => { expect(providers).toHaveLength(1); expect(providers.map((provider) => provider.id)).toEqual(["openrouter"]); expect(speechProviders).toHaveLength(0); - expect(mediaProviders).toHaveLength(0); + expect(mediaProviders.map((provider) => provider.id)).toEqual(["openrouter"]); expect(imageProviders).toHaveLength(0); }); }); diff --git a/extensions/openrouter/index.ts b/extensions/openrouter/index.ts index c33a4a6eb95..abddf31a945 100644 --- a/extensions/openrouter/index.ts +++ b/extensions/openrouter/index.ts @@ -13,6 +13,7 @@ import { createOpenRouterWrapper, isProxyReasoningUnsupported, } from "openclaw/plugin-sdk/provider-stream"; +import { openrouterMediaUnderstandingProvider } from "./media-understanding-provider.js"; import { applyOpenrouterConfig, OPENROUTER_DEFAULT_MODEL_REF } from "./onboard.js"; import { buildOpenrouterProvider } from "./provider-catalog.js"; @@ -154,5 +155,6 @@ export default definePluginEntry({ }, isCacheTtlEligible: (ctx) => isOpenRouterCacheTtlModel(ctx.modelId), }); + api.registerMediaUnderstandingProvider(openrouterMediaUnderstandingProvider); }, }); diff --git a/extensions/openrouter/media-understanding-provider.ts b/extensions/openrouter/media-understanding-provider.ts new file mode 100644 index 00000000000..0a40be25120 --- /dev/null +++ b/extensions/openrouter/media-understanding-provider.ts @@ -0,0 +1,12 @@ +import { + describeImageWithModel, + describeImagesWithModel, + type MediaUnderstandingProvider, +} from "openclaw/plugin-sdk/media-understanding"; + +export const openrouterMediaUnderstandingProvider: MediaUnderstandingProvider = { + id: "openrouter", + capabilities: ["image"], + describeImage: describeImageWithModel, + describeImages: describeImagesWithModel, +}; diff --git a/extensions/openrouter/openclaw.plugin.json b/extensions/openrouter/openclaw.plugin.json index 8151f24e677..2a153632343 100644 --- a/extensions/openrouter/openclaw.plugin.json +++ b/extensions/openrouter/openclaw.plugin.json @@ -19,6 +19,9 @@ "cliDescription": "OpenRouter API key" } ], + "contracts": { + "mediaUnderstandingProviders": ["openrouter"] + }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/src/media-understanding/runner.vision-skip.test.ts b/src/media-understanding/runner.vision-skip.test.ts index df380571139..7f5bfae7966 100644 --- a/src/media-understanding/runner.vision-skip.test.ts +++ b/src/media-understanding/runner.vision-skip.test.ts @@ -1,6 +1,13 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { MsgContext } from "../auto-reply/templating.js"; import type { OpenClawConfig } from "../config/config.js"; +import { + buildProviderRegistry, + createMediaAttachmentCache, + normalizeMediaAttachments, + resolveAutoImageModel, + runCapability, +} from "./runner.js"; const catalog = [ { @@ -26,6 +33,7 @@ vi.mock("../agents/model-catalog.js", async () => { let buildProviderRegistry: typeof import("./runner.js").buildProviderRegistry; let createMediaAttachmentCache: typeof import("./runner.js").createMediaAttachmentCache; let normalizeMediaAttachments: typeof import("./runner.js").normalizeMediaAttachments; +let resolveAutoImageModel: typeof import("./runner.js").resolveAutoImageModel; let runCapability: typeof import("./runner.js").runCapability; describe("runCapability image skip", () => { @@ -44,6 +52,7 @@ describe("runCapability image skip", () => { buildProviderRegistry, createMediaAttachmentCache, normalizeMediaAttachments, + resolveAutoImageModel, runCapability, } = await import("./runner.js")); loadModelCatalog.mockClear(); @@ -78,4 +87,21 @@ describe("runCapability image skip", () => { await cache.cleanup(); } }); + + it("uses active OpenRouter image models for auto image resolution", async () => { + vi.stubEnv("OPENROUTER_API_KEY", "test-openrouter-key"); + try { + await expect( + resolveAutoImageModel({ + cfg: {} as OpenClawConfig, + activeModel: { provider: "openrouter", model: "google/gemini-2.5-flash" }, + }), + ).resolves.toEqual({ + provider: "openrouter", + model: "google/gemini-2.5-flash", + }); + } finally { + vi.unstubAllEnvs(); + } + }); });