diff --git a/extensions/openrouter/index.test.ts b/extensions/openrouter/index.test.ts new file mode 100644 index 00000000000..fa4cbda6cd2 --- /dev/null +++ b/extensions/openrouter/index.test.ts @@ -0,0 +1,101 @@ +import OpenAI from "openai"; +import { describe, expect, it } from "vitest"; +import { createTestPluginApi } from "../../test/helpers/extensions/plugin-api.js"; +import plugin from "./index.js"; + +const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY ?? ""; +const LIVE_MODEL_ID = + process.env.OPENCLAW_LIVE_OPENROUTER_PLUGIN_MODEL?.trim() || "openai/gpt-5.4-nano"; +const liveEnabled = OPENROUTER_API_KEY.trim().length > 0 && process.env.OPENCLAW_LIVE_TEST === "1"; +const describeLive = liveEnabled ? describe : describe.skip; + +function registerOpenRouterPlugin() { + const providers: unknown[] = []; + const speechProviders: unknown[] = []; + const mediaProviders: unknown[] = []; + const imageProviders: unknown[] = []; + + plugin.register( + createTestPluginApi({ + id: "openrouter", + name: "OpenRouter Provider", + source: "test", + config: {}, + runtime: {} as never, + registerProvider: (provider) => { + providers.push(provider); + }, + registerSpeechProvider: (provider) => { + speechProviders.push(provider); + }, + registerMediaUnderstandingProvider: (provider) => { + mediaProviders.push(provider); + }, + registerImageGenerationProvider: (provider) => { + imageProviders.push(provider); + }, + }), + ); + + return { providers, speechProviders, mediaProviders, imageProviders }; +} + +describe("openrouter plugin", () => { + it("registers the expected provider surfaces", () => { + const { providers, speechProviders, mediaProviders, imageProviders } = + registerOpenRouterPlugin(); + + expect(providers).toHaveLength(1); + expect( + providers.map( + (provider) => + // oxlint-disable-next-line typescript/no-explicit-any + (provider as any).id, + ), + ).toEqual(["openrouter"]); + expect(speechProviders).toHaveLength(0); + expect(mediaProviders).toHaveLength(0); + expect(imageProviders).toHaveLength(0); + }); +}); + +describeLive("openrouter plugin live", () => { + it("registers an OpenRouter provider that can complete a live request", async () => { + const { providers } = registerOpenRouterPlugin(); + const provider = + // oxlint-disable-next-line typescript/no-explicit-any + providers.find((entry) => (entry as any).id === "openrouter"); + + expect(provider).toBeDefined(); + + // oxlint-disable-next-line typescript/no-explicit-any + const resolved = (provider as any).resolveDynamicModel?.({ + provider: "openrouter", + modelId: LIVE_MODEL_ID, + modelRegistry: { + find() { + return null; + }, + }, + }); + + expect(resolved).toMatchObject({ + provider: "openrouter", + id: LIVE_MODEL_ID, + api: "openai-completions", + baseUrl: "https://openrouter.ai/api/v1", + }); + + const client = new OpenAI({ + apiKey: OPENROUTER_API_KEY, + baseURL: resolved?.baseUrl, + }); + const response = await client.chat.completions.create({ + model: resolved?.id ?? LIVE_MODEL_ID, + messages: [{ role: "user", content: "Reply with exactly OK." }], + max_tokens: 16, + }); + + expect(response.choices[0]?.message?.content?.trim()).toMatch(/^OK[.!]?$/); + }, 30_000); +});