import type { StreamFn } from "openclaw/plugin-sdk/agent-core"; import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime"; import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const providerAuthRuntimeMocks = vi.hoisted(() => ({ resolveApiKeyForProvider: vi.fn(), })); vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => providerAuthRuntimeMocks); import plugin from "./index.js"; import { wrapClawRouterProviderStream } from "./stream.js"; const LIVE_CATALOG = { providers: [ { id: "openai", displayName: "OpenAI", openaiCompatible: true, nativeBaseUrl: "/v1/native/openai", routes: [], models: [ { id: "openai/gpt-5.5", upstream: "gpt-5.5", capabilities: ["llm.responses"], }, ], }, ], }; describe("ClawRouter plugin", () => { beforeEach(() => { clearLiveCatalogCacheForTests(); providerAuthRuntimeMocks.resolveApiKeyForProvider.mockReset(); }); afterEach(() => { vi.unstubAllGlobals(); }); it("registers catalog, transport compatibility, and quota hooks", async () => { const provider = await registerSingleProviderPlugin(plugin); expect(provider).toMatchObject({ id: "clawrouter", label: "ClawRouter", docsPath: "/providers/clawrouter", envVars: ["CLAWROUTER_API_KEY"], buildReplayPolicy: expect.any(Function), fetchUsageSnapshot: expect.any(Function), inspectToolSchemas: expect.any(Function), normalizeResolvedModel: expect.any(Function), normalizeToolSchemas: expect.any(Function), prepareDynamicModel: expect.any(Function), resolveDynamicModel: expect.any(Function), resolveUsageAuth: expect.any(Function), sanitizeReplayHistory: expect.any(Function), wrapSimpleCompletionStreamFn: expect.any(Function), wrapStreamFn: expect.any(Function), }); expect(provider?.auth[0]).toMatchObject({ id: "api-key", label: "ClawRouter proxy key", kind: "api_key", }); expect(provider?.wrapSimpleCompletionStreamFn).toBe(provider?.wrapStreamFn); }); it("attaches the proxy key and native upstream id only at request dispatch", async () => { const provider = await registerSingleProviderPlugin(plugin); const calls: Array[0]> = []; const baseStreamFn: StreamFn = (model) => { calls.push(model); return {} as ReturnType; }; const wrapped = provider?.wrapStreamFn?.({ provider: "clawrouter", modelId: "anthropic/claude-sonnet-4-6", streamFn: baseStreamFn, } as never); void wrapped?.( { provider: "clawrouter", api: "anthropic-messages", id: "anthropic/claude-sonnet-4-6", headers: { "x-request-id": "request-1" }, params: { clawrouterRoute: { api: "anthropic-messages", baseUrl: "https://clawrouter.example/v1/native/anthropic", upstreamModel: "claude-sonnet-4-6", }, }, } as never, {} as never, { apiKey: "runtime-proxy-key", requestId: "automatic-call-id" } as never, ); expect(calls[0]?.headers).toEqual({ "x-request-id": "request-1", "X-ClawRouter-Client": "openclaw", Authorization: "Bearer runtime-proxy-key", }); expect(calls[0]?.id).toBe("claude-sonnet-4-6"); expect(calls[0]?.params).toBeUndefined(); }); it("attaches bounded attribution without overriding configured metadata", () => { const calls: Array[0]> = []; const baseStreamFn: StreamFn = (model) => { calls.push(model); return {} as ReturnType; }; const wrapped = wrapClawRouterProviderStream({ provider: "clawrouter", modelId: "openai/gpt-5.5", agentId: "main", streamFn: baseStreamFn, } as never); const longRunId = `run-${"r".repeat(300)}`; void wrapped?.( { provider: "clawrouter", api: "openai-responses", id: "openai/gpt-5.5", headers: { "x-clawrouter-client": "managed-openclaw", "X-ClawRouter-Project-Id": "fakeco", }, } as never, {} as never, { apiKey: "runtime-proxy-key", requestId: `${longRunId}:model:1`, sessionId: `session-${"x".repeat(300)}`, } as never, ); void wrapped?.( { provider: "clawrouter", api: "openai-responses", id: "openai/gpt-5.5", } as never, {} as never, { requestId: `${longRunId}:model:2`, } as never, ); void wrapped?.( { provider: "clawrouter", api: "openai-responses", id: "openai/gpt-5.5", } as never, {} as never, { requestId: "turn-😀:model:3", } as never, ); expect(calls[0]?.headers).toMatchObject({ "x-clawrouter-client": "managed-openclaw", "X-ClawRouter-Agent-Id": "main", "X-ClawRouter-Project-Id": "fakeco", Authorization: "Bearer runtime-proxy-key", }); expect(calls[0]?.headers?.["X-ClawRouter-Session-Id"]).toHaveLength(256); expect(calls[0]?.headers?.["X-Request-ID"]).toHaveLength(128); expect(calls[0]?.headers?.["X-Request-ID"]).toMatch(/~[a-f0-9]{16}:model:1$/u); expect(calls[1]?.headers?.["X-Request-ID"]).toMatch(/~[a-f0-9]{16}:model:2$/u); expect(calls[1]?.headers?.["X-Request-ID"]).not.toBe(calls[0]?.headers?.["X-Request-ID"]); expect(calls[2]?.headers?.["X-Request-ID"]).toMatch(/^turn-_~[a-f0-9]{16}:model:3$/u); }); it("keeps an explicit per-request header ahead of the automatic model-call id", () => { const calls: Array<{ model: Parameters[0]; options: Parameters[2]; }> = []; const baseStreamFn: StreamFn = (model, _context, options) => { calls.push({ model, options }); return {} as ReturnType; }; const wrapped = wrapClawRouterProviderStream({ provider: "clawrouter", modelId: "openai/gpt-5.5", streamFn: baseStreamFn, } as never); void wrapped?.( { provider: "clawrouter", api: "openai-responses", id: "openai/gpt-5.5", } as never, {} as never, { headers: { "x-request-id": "operator-request" }, requestId: "automatic-call-id", } as never, ); expect(calls[0]?.model.headers).not.toHaveProperty("X-Request-ID"); expect(calls[0]?.options?.headers).toEqual({ "x-request-id": "operator-request" }); }); it("omits unsafe attribution header values", () => { const calls: Array[0]> = []; const baseStreamFn: StreamFn = (model) => { calls.push(model); return {} as ReturnType; }; const wrapped = wrapClawRouterProviderStream({ provider: "clawrouter", modelId: "openai/gpt-5.5", agentId: "bad\nagent", streamFn: baseStreamFn, } as never); void wrapped?.( { provider: "clawrouter", api: "openai-responses", id: "openai/gpt-5.5", } as never, {} as never, { apiKey: "runtime-proxy-key", requestId: "bad\nrequest", sessionId: "bad\rsession", } as never, ); expect(calls[0]?.headers).toEqual({ "X-ClawRouter-Client": "openclaw", Authorization: "Bearer runtime-proxy-key", }); }); it("resolves managed secret refs before scoped discovery", async () => { providerAuthRuntimeMocks.resolveApiKeyForProvider.mockResolvedValue({ apiKey: "resolved-proxy-key", mode: "api-key", source: "models.json secretref", }); const fetchMock = vi.fn(async () => Response.json(LIVE_CATALOG)); vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch); const provider = await registerSingleProviderPlugin(plugin); const result = await provider?.catalog?.run({ config: { models: {} }, agentDir: "/agent", workspaceDir: "/workspace", env: {}, resolveProviderAuth: () => ({ apiKey: "secretref-managed", discoveryApiKey: undefined, mode: "api_key", source: "profile", profileId: "clawrouter-profile", }), resolveProviderApiKey: () => ({ apiKey: "secretref-managed", discoveryApiKey: undefined, }), }); if (!result || !("provider" in result)) { throw new Error("expected ClawRouter catalog provider result"); } expect(result.provider.apiKey).toBe("secretref-managed"); expect(result.provider.models.map((model) => model.id)).toEqual(["openai/gpt-5.5"]); expect(providerAuthRuntimeMocks.resolveApiKeyForProvider).toHaveBeenCalledWith({ provider: "clawrouter", cfg: { models: {} }, agentDir: "/agent", workspaceDir: "/workspace", profileId: "clawrouter-profile", lockedProfile: true, }); const fetchCall = fetchMock.mock.calls[0] as unknown as [string, RequestInit] | undefined; expect(new Headers(fetchCall?.[1]?.headers).get("Authorization")).toBe( "Bearer resolved-proxy-key", ); }); it("surfaces catalog authentication failures", async () => { vi.stubGlobal( "fetch", vi.fn(async () => new Response("Unauthorized", { status: 401 })), ); const provider = await registerSingleProviderPlugin(plugin); await expect( provider?.catalog?.run({ config: { models: {} }, env: { CLAWROUTER_API_KEY: "invalid-proxy-key" }, resolveProviderAuth: () => ({ apiKey: "invalid-proxy-key", discoveryApiKey: "invalid-proxy-key", mode: "api_key", source: "env", }), resolveProviderApiKey: () => ({ apiKey: "invalid-proxy-key", discoveryApiKey: "invalid-proxy-key", }), }), ).rejects.toThrow(/401/u); }); it("resolves configured catalog models through a stored auth profile", async () => { providerAuthRuntimeMocks.resolveApiKeyForProvider.mockResolvedValue({ apiKey: "resolved-proxy-key", mode: "api-key", source: "auth profile", }); vi.stubGlobal( "fetch", vi.fn(async () => Response.json(LIVE_CATALOG)), ); const provider = await registerSingleProviderPlugin(plugin); const context = { config: { models: {} }, agentDir: "/agent", workspaceDir: "/workspace", provider: "clawrouter", modelId: "openai/gpt-5.5", modelRegistry: { find: vi.fn(() => null) }, authProfileId: "clawrouter-profile", authProfileMode: "api_key", }; expect(provider?.resolveDynamicModel?.(context as never)).toBeUndefined(); await provider?.prepareDynamicModel?.(context as never); expect(provider?.resolveDynamicModel?.(context as never)).toMatchObject({ id: "openai/gpt-5.5", provider: "clawrouter", api: "openai-responses", baseUrl: "https://clawrouter.openclaw.ai/v1", params: { clawrouterRoute: { api: "openai-responses", baseUrl: "https://clawrouter.openclaw.ai/v1", }, }, }); expect(providerAuthRuntimeMocks.resolveApiKeyForProvider).toHaveBeenCalledWith({ provider: "clawrouter", cfg: { models: {} }, agentDir: "/agent", workspaceDir: "/workspace", profileId: "clawrouter-profile", lockedProfile: true, }); expect( provider?.resolveDynamicModel?.({ ...context, authProfileId: "another-profile", } as never), ).toBeUndefined(); providerAuthRuntimeMocks.resolveApiKeyForProvider.mockResolvedValue(undefined); await provider?.prepareDynamicModel?.(context as never); expect(provider?.resolveDynamicModel?.(context as never)).toBeUndefined(); }); it("dispatches replay and tool policies by upstream protocol family", async () => { const provider = await registerSingleProviderPlugin(plugin); expect( provider?.buildReplayPolicy?.({ provider: "clawrouter", modelApi: "anthropic-messages", modelId: "anthropic/claude-sonnet-4-6", } as never), ).toMatchObject({ preserveNativeAnthropicToolUseIds: true, validateAnthropicTurns: true }); expect( provider?.buildReplayPolicy?.({ provider: "clawrouter", modelApi: "google-generative-ai", modelId: "google/gemini-3.5-flash", } as never), ).toMatchObject({ validateGeminiTurns: true }); expect( provider?.buildReplayPolicy?.({ provider: "clawrouter", modelApi: "openai-completions", modelId: "deepseek/deepseek-v4-flash", } as never), ).toMatchObject({ sanitizeToolCallIds: true }); }); });