Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
218
extensions/openrouter/openrouter.live.test.ts
Normal file
218
extensions/openrouter/openrouter.live.test.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
// Openrouter tests cover openrouter plugin behavior.
|
||||
import OpenAI from "openai";
|
||||
import { AuthStorage, ModelRegistry } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import {
|
||||
registerProviderPlugin,
|
||||
requireRegisteredProvider,
|
||||
} from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import plugin from "./index.js";
|
||||
import { normalizeOpenRouterApiModelId } from "./models.js";
|
||||
|
||||
const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
||||
const OPENROUTER_MISTRAL_PROVIDER_PREFIX = "mistralai/";
|
||||
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY ?? "";
|
||||
const LIVE_MODEL_REF =
|
||||
process.env.OPENCLAW_LIVE_OPENROUTER_PLUGIN_MODEL?.trim() ||
|
||||
"openrouter/anthropic/claude-sonnet-4.6";
|
||||
const LIVE_MODEL_ID = LIVE_MODEL_REF.startsWith("openrouter/")
|
||||
? LIVE_MODEL_REF
|
||||
: `openrouter/${LIVE_MODEL_REF}`;
|
||||
const LIVE_CACHE_MODEL_ID =
|
||||
process.env.OPENCLAW_LIVE_OPENROUTER_CACHE_MODEL?.trim() || "deepseek/deepseek-v3.2";
|
||||
const liveEnabled = OPENROUTER_API_KEY.trim().length > 0 && process.env.OPENCLAW_LIVE_TEST === "1";
|
||||
const liveCatalogEnabled = process.env.OPENCLAW_LIVE_TEST === "1";
|
||||
const describeLive = liveEnabled ? describe : describe.skip;
|
||||
const describeCatalogLive = liveCatalogEnabled ? describe : describe.skip;
|
||||
const describeCacheLive =
|
||||
liveEnabled && process.env.OPENCLAW_LIVE_CACHE_TEST === "1" ? describe : describe.skip;
|
||||
const ModelRegistryCtor = ModelRegistry as unknown as {
|
||||
new (authStorage: AuthStorage, modelsJsonPath?: string): ModelRegistry;
|
||||
};
|
||||
type OpenRouterModelsResponse = {
|
||||
data?: Array<{ id?: unknown }>;
|
||||
};
|
||||
|
||||
const registerOpenRouterPlugin = async () =>
|
||||
registerProviderPlugin({
|
||||
plugin,
|
||||
id: "openrouter",
|
||||
name: "OpenRouter Provider",
|
||||
});
|
||||
|
||||
function buildStableCachePrefix(): string {
|
||||
return Array.from(
|
||||
{ length: 700 },
|
||||
(_, index) =>
|
||||
`Stable OpenRouter cache probe sentence ${
|
||||
index % 20
|
||||
}: this prefix must stay byte-identical across repeated requests.`,
|
||||
).join("\n");
|
||||
}
|
||||
|
||||
async function completeOpenRouterChat(params: {
|
||||
client: OpenAI;
|
||||
messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[];
|
||||
model: string;
|
||||
}) {
|
||||
return params.client.chat.completions.create({
|
||||
model: params.model,
|
||||
messages: params.messages,
|
||||
max_tokens: 8,
|
||||
});
|
||||
}
|
||||
|
||||
async function expectWeatherToolCall(client: OpenAI, model: string): Promise<void> {
|
||||
const response = await client.chat.completions.create({
|
||||
model,
|
||||
messages: [{ role: "user", content: "Call get_weather for Paris." }],
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_weather",
|
||||
description: "Get the weather for a city.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: { city: { type: "string" } },
|
||||
required: ["city"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
tool_choice: {
|
||||
type: "function",
|
||||
function: { name: "get_weather" },
|
||||
},
|
||||
max_tokens: 64,
|
||||
});
|
||||
|
||||
const toolCall = response.choices[0]?.message?.tool_calls?.find(
|
||||
(call) => call.type === "function",
|
||||
);
|
||||
expect(toolCall?.type).toBe("function");
|
||||
expect(toolCall?.function.name).toBe("get_weather");
|
||||
expect(JSON.parse(toolCall?.function.arguments ?? "{}")).toMatchObject({ city: "Paris" });
|
||||
}
|
||||
|
||||
async function fetchOpenRouterModelIds(): Promise<string[]> {
|
||||
const response = await fetch(OPENROUTER_MODELS_URL, {
|
||||
headers: { "accept-encoding": "identity" },
|
||||
});
|
||||
expect(response.ok).toBe(true);
|
||||
const json = (await response.json()) as OpenRouterModelsResponse;
|
||||
return (json.data ?? [])
|
||||
.map((model) => model.id)
|
||||
.filter((id): id is string => typeof id === "string" && id.length > 0);
|
||||
}
|
||||
|
||||
describeLive("openrouter plugin live", () => {
|
||||
it("normalizes a prefixed OpenRouter model and completes a live tool call", async () => {
|
||||
const { providers } = await registerOpenRouterPlugin();
|
||||
const provider = requireRegisteredProvider(providers, "openrouter");
|
||||
|
||||
const resolved = provider.resolveDynamicModel?.({
|
||||
provider: "openrouter",
|
||||
modelId: LIVE_MODEL_ID,
|
||||
modelRegistry: new ModelRegistryCtor(AuthStorage.inMemory()),
|
||||
});
|
||||
if (!resolved) {
|
||||
throw new Error(`openrouter provider did not resolve ${LIVE_MODEL_ID}`);
|
||||
}
|
||||
|
||||
expect(resolved.provider).toBe("openrouter");
|
||||
expect(resolved.id).toBe(LIVE_MODEL_ID);
|
||||
expect(resolved.api).toBe("openai-completions");
|
||||
expect(resolved.baseUrl).toBe("https://openrouter.ai/api/v1");
|
||||
|
||||
const normalized =
|
||||
provider.normalizeResolvedModel?.({
|
||||
provider: "openrouter",
|
||||
modelId: resolved.id,
|
||||
model: resolved,
|
||||
}) ?? resolved;
|
||||
expect(normalized.id).toBe(normalizeOpenRouterApiModelId(LIVE_MODEL_ID));
|
||||
|
||||
const client = new OpenAI({
|
||||
apiKey: OPENROUTER_API_KEY,
|
||||
baseURL: normalized.baseUrl,
|
||||
});
|
||||
const autoResolved = provider.resolveDynamicModel?.({
|
||||
provider: "openrouter",
|
||||
modelId: "openrouter/auto",
|
||||
modelRegistry: new ModelRegistryCtor(AuthStorage.inMemory()),
|
||||
});
|
||||
if (!autoResolved) {
|
||||
throw new Error("openrouter provider did not resolve openrouter/auto");
|
||||
}
|
||||
const autoModel =
|
||||
provider.normalizeResolvedModel?.({
|
||||
provider: "openrouter",
|
||||
modelId: autoResolved.id,
|
||||
model: autoResolved,
|
||||
}) ?? autoResolved;
|
||||
expect(autoModel.id).toBe("openrouter/auto");
|
||||
await expectWeatherToolCall(client, autoModel.id);
|
||||
await expectWeatherToolCall(client, normalized.id);
|
||||
}, 30_000);
|
||||
});
|
||||
|
||||
describeCatalogLive("openrouter plugin live model catalog", () => {
|
||||
it("applies strict9 replay policy to current OpenRouter Mistral-family routes", async () => {
|
||||
const liveMistralModelIds = (await fetchOpenRouterModelIds()).filter((id) =>
|
||||
id.startsWith(OPENROUTER_MISTRAL_PROVIDER_PREFIX),
|
||||
);
|
||||
expect(liveMistralModelIds.length).toBeGreaterThan(0);
|
||||
|
||||
const { providers } = await registerOpenRouterPlugin();
|
||||
const provider = requireRegisteredProvider(providers, "openrouter");
|
||||
|
||||
for (const modelId of liveMistralModelIds) {
|
||||
const policy = provider.buildReplayPolicy?.({
|
||||
provider: "openrouter",
|
||||
modelApi: "openai-completions",
|
||||
modelId,
|
||||
} as never);
|
||||
expect.soft(policy?.sanitizeToolCallIds, modelId).toBe(true);
|
||||
expect.soft(policy?.toolCallIdMode, modelId).toBe("strict9");
|
||||
}
|
||||
}, 30_000);
|
||||
});
|
||||
|
||||
describeCacheLive("openrouter plugin live cache", () => {
|
||||
it("observes automatic cache reads for DeepSeek model refs after cache construction", async () => {
|
||||
const { providers } = await registerOpenRouterPlugin();
|
||||
const provider = requireRegisteredProvider(providers, "openrouter");
|
||||
const resolved = provider.resolveDynamicModel?.({
|
||||
provider: "openrouter",
|
||||
modelId: LIVE_CACHE_MODEL_ID,
|
||||
modelRegistry: new ModelRegistryCtor(AuthStorage.inMemory()),
|
||||
});
|
||||
if (!resolved) {
|
||||
throw new Error(`openrouter provider did not resolve ${LIVE_CACHE_MODEL_ID}`);
|
||||
}
|
||||
|
||||
const client = new OpenAI({
|
||||
apiKey: OPENROUTER_API_KEY,
|
||||
baseURL: resolved.baseUrl,
|
||||
});
|
||||
const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
|
||||
{
|
||||
role: "system",
|
||||
content: `You are testing prompt caching.\n${buildStableCachePrefix()}`,
|
||||
},
|
||||
{ role: "user", content: "Reply with exactly OK." },
|
||||
];
|
||||
|
||||
await completeOpenRouterChat({ client, model: resolved.id, messages });
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 2_000);
|
||||
});
|
||||
const cached = await completeOpenRouterChat({ client, model: resolved.id, messages });
|
||||
|
||||
const cachedTokens = cached.usage?.prompt_tokens_details?.cached_tokens ?? 0;
|
||||
expect(cached.choices[0]?.message?.content?.trim()).toMatch(/^OK[.!]?$/);
|
||||
expect(cachedTokens).toBeGreaterThan(1024);
|
||||
}, 60_000);
|
||||
});
|
||||
Reference in New Issue
Block a user