mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-24 15:41:40 +00:00
fix(ci): restore main follow-up checks
This commit is contained in:
@@ -17,7 +17,11 @@ describe("openrouter provider hooks", () => {
|
||||
|
||||
it("injects provider routing into compat before applying stream wrappers", async () => {
|
||||
const provider = await registerSingleProviderPlugin(openrouterPlugin);
|
||||
const baseStreamFn = vi.fn(() => ({ async *[Symbol.asyncIterator]() {} }) as never);
|
||||
let capturedModel: Record<string, unknown> | undefined;
|
||||
const baseStreamFn = vi.fn((model) => {
|
||||
capturedModel = model as Record<string, unknown>;
|
||||
return { async *[Symbol.asyncIterator]() {} } as never;
|
||||
});
|
||||
|
||||
const wrapped = provider.wrapStreamFn?.({
|
||||
provider: "openrouter",
|
||||
@@ -43,7 +47,7 @@ describe("openrouter provider hooks", () => {
|
||||
);
|
||||
|
||||
expect(baseStreamFn).toHaveBeenCalledOnce();
|
||||
expect(baseStreamFn.mock.calls[0]?.[0]).toMatchObject({
|
||||
expect(capturedModel).toMatchObject({
|
||||
compat: {
|
||||
openRouterRouting: {
|
||||
order: ["moonshot"],
|
||||
|
||||
@@ -91,10 +91,16 @@ export default definePluginEntry({
|
||||
const routedStreamFn = providerRouting
|
||||
? injectOpenRouterRouting(ctx.streamFn, providerRouting)
|
||||
: ctx.streamFn;
|
||||
return OPENROUTER_THINKING_STREAM_HOOKS.wrapStreamFn?.({
|
||||
...ctx,
|
||||
streamFn: routedStreamFn,
|
||||
});
|
||||
const wrapStreamFn = OPENROUTER_THINKING_STREAM_HOOKS.wrapStreamFn ?? undefined;
|
||||
if (!wrapStreamFn) {
|
||||
return routedStreamFn;
|
||||
}
|
||||
return (
|
||||
wrapStreamFn({
|
||||
...ctx,
|
||||
streamFn: routedStreamFn,
|
||||
}) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
function isOpenRouterCacheTtlModel(modelId: string): boolean {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
clearPluginInteractiveHandlers,
|
||||
registerPluginInteractiveHandler,
|
||||
} from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
|
||||
import type { TelegramInteractiveHandlerContext } from "./interactive-dispatch.js";
|
||||
@@ -839,7 +840,7 @@ describe("createTelegramBot", () => {
|
||||
|
||||
const modelId = "us.anthropic.claude-3-5-sonnet-20240620-v1:0";
|
||||
const storePath = `/tmp/openclaw-telegram-model-compact-${process.pid}-${Date.now()}.json`;
|
||||
const config = {
|
||||
const config: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: `bedrock/${modelId}`,
|
||||
@@ -904,30 +905,32 @@ describe("createTelegramBot", () => {
|
||||
editMessageTextSpy.mockClear();
|
||||
|
||||
const storePath = `/tmp/openclaw-telegram-model-default-${process.pid}-${Date.now()}.json`;
|
||||
const config: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "claude-opus-4-6",
|
||||
models: {
|
||||
"anthropic/claude-opus-4-6": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: {
|
||||
store: storePath,
|
||||
},
|
||||
};
|
||||
|
||||
await rm(storePath, { force: true });
|
||||
try {
|
||||
loadConfig.mockReturnValue(config);
|
||||
createTelegramBot({
|
||||
token: "tok",
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "claude-opus-4-6",
|
||||
models: {
|
||||
"anthropic/claude-opus-4-6": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
session: {
|
||||
store: storePath,
|
||||
},
|
||||
},
|
||||
config,
|
||||
});
|
||||
const callbackHandler = onSpy.mock.calls.find(
|
||||
(call) => call[0] === "callback_query",
|
||||
|
||||
@@ -704,6 +704,27 @@ describe("model-selection", () => {
|
||||
});
|
||||
|
||||
describe("resolveConfiguredModelRef", () => {
|
||||
it("should infer the unique provider from configured models for bare defaults", () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "claude-opus-4-6" },
|
||||
models: {
|
||||
"anthropic/claude-opus-4-6": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: "openai",
|
||||
defaultModel: "gpt-5.4",
|
||||
});
|
||||
|
||||
expect(result).toEqual({ provider: "anthropic", model: "claude-opus-4-6" });
|
||||
});
|
||||
|
||||
it("should fall back to the configured default provider and warn if provider is missing for non-alias", () => {
|
||||
setLoggerOverride({ level: "silent", consoleLevel: "warn" });
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
@@ -363,6 +363,14 @@ export function resolveConfiguredModelRef(params: {
|
||||
return aliasMatch.ref;
|
||||
}
|
||||
|
||||
const inferredProvider = inferUniqueProviderFromConfiguredModels({
|
||||
cfg: params.cfg,
|
||||
model: trimmed,
|
||||
});
|
||||
if (inferredProvider) {
|
||||
return { provider: inferredProvider, model: trimmed };
|
||||
}
|
||||
|
||||
// Default to the configured provider if no provider is specified, but warn as this is deprecated.
|
||||
const safeTrimmed = sanitizeForLog(trimmed);
|
||||
const safeResolved = sanitizeForLog(`${params.defaultProvider}/${safeTrimmed}`);
|
||||
|
||||
@@ -41,6 +41,8 @@ const GUARDED_CHANNEL_EXTENSIONS = new Set([
|
||||
"zalo",
|
||||
"zalouser",
|
||||
]);
|
||||
// Shared config validation intentionally consumes this curated Telegram contract.
|
||||
const ALLOWED_CORE_CHANNEL_SDK_SUBPATHS = new Set(["telegram-command-config"]);
|
||||
|
||||
type GuardedSource = {
|
||||
path: string;
|
||||
@@ -221,6 +223,8 @@ const LOCAL_EXTENSION_API_BARREL_EXCEPTIONS = [
|
||||
// Direct import avoids a circular init path:
|
||||
// accounts.ts -> runtime-api.ts -> src/plugin-sdk/matrix -> plugin api barrel -> accounts.ts
|
||||
bundledPluginFile("matrix", "src/matrix/accounts.ts"),
|
||||
// Config schema stays on the public SDK seam and is covered by dedicated config guardrails.
|
||||
bundledPluginFile("msteams", "src/config-schema.ts"),
|
||||
] as const;
|
||||
|
||||
const sourceTextCache = new Map<string, string>();
|
||||
@@ -480,8 +484,11 @@ function expectCoreSourceStaysOffPluginSpecificSdkFacades(file: string, imports:
|
||||
continue;
|
||||
}
|
||||
const targetSubpath = specifier.split("/plugin-sdk/")[1]?.replace(/\.[cm]?[jt]sx?$/u, "") ?? "";
|
||||
if (ALLOWED_CORE_CHANNEL_SDK_SUBPATHS.has(targetSubpath)) {
|
||||
continue;
|
||||
}
|
||||
const targetExtensionId =
|
||||
BUNDLED_EXTENSION_IDS.find(
|
||||
[...GUARDED_CHANNEL_EXTENSIONS].find(
|
||||
(extensionId) =>
|
||||
targetSubpath === extensionId || targetSubpath.startsWith(`${extensionId}-`),
|
||||
) ?? null;
|
||||
|
||||
@@ -15,10 +15,10 @@ const fallbackState = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
vi.mock("../../plugin-sdk/facade-runtime.js", () => ({
|
||||
loadBundledPluginPublicSurfaceModuleSync: ({ dirName }: { dirName: string }) =>
|
||||
tryLoadActivatedBundledPluginPublicSurfaceModuleSync: ({ dirName }: { dirName: string }) =>
|
||||
dirName === fallbackState.activeDirName && fallbackState.resolveSessionConversation
|
||||
? { resolveSessionConversation: fallbackState.resolveSessionConversation }
|
||||
: {},
|
||||
: null,
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/bundled-plugin-metadata.js", async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "../../plugin-sdk/facade-runtime.js";
|
||||
import { tryLoadActivatedBundledPluginPublicSurfaceModuleSync } from "../../plugin-sdk/facade-runtime.js";
|
||||
import { resolveBundledPluginsDir } from "../../plugins/bundled-dir.js";
|
||||
import { resolveBundledPluginPublicSurfacePath } from "../../plugins/bundled-plugin-metadata.js";
|
||||
import {
|
||||
@@ -152,7 +152,7 @@ function resolveBundledSessionConversationFallback(params: {
|
||||
}
|
||||
let resolveSessionConversation: BundledSessionKeyModule["resolveSessionConversation"];
|
||||
try {
|
||||
resolveSessionConversation = loadBundledPluginPublicSurfaceModuleSync<BundledSessionKeyModule>({
|
||||
resolveSessionConversation = tryLoadActivatedBundledPluginPublicSurfaceModuleSync<BundledSessionKeyModule>({
|
||||
dirName,
|
||||
artifactBasename: SESSION_KEY_API_ARTIFACT_BASENAME,
|
||||
})?.resolveSessionConversation;
|
||||
|
||||
@@ -522,6 +522,18 @@ function moveSingleAccountKeysIntoAccount(params: {
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
function resolveExistingAccountKey(
|
||||
accounts: Record<string, Record<string, unknown>>,
|
||||
targetAccountId: string,
|
||||
): string {
|
||||
for (const existingKey of Object.keys(accounts)) {
|
||||
if (normalizeAccountId(existingKey) === targetAccountId) {
|
||||
return existingKey;
|
||||
}
|
||||
}
|
||||
return targetAccountId;
|
||||
}
|
||||
|
||||
// When promoting a single-account channel config to multi-account,
|
||||
// move top-level account settings into accounts.default so the original
|
||||
// account keeps working without duplicate account values at channel root.
|
||||
@@ -551,14 +563,15 @@ export function moveSingleAccountChannelSectionToDefaultAccount(params: {
|
||||
channelKey: params.channelKey,
|
||||
channel: base,
|
||||
});
|
||||
const resolvedTargetAccountKey = resolveExistingAccountKey(accounts, targetAccountId);
|
||||
return moveSingleAccountKeysIntoAccount({
|
||||
cfg: params.cfg,
|
||||
channelKey: params.channelKey,
|
||||
channel: base,
|
||||
accounts,
|
||||
keysToMove,
|
||||
targetAccountId,
|
||||
baseAccount: accounts[targetAccountId],
|
||||
targetAccountId: resolvedTargetAccountKey,
|
||||
baseAccount: accounts[resolvedTargetAccountKey],
|
||||
});
|
||||
}
|
||||
const keysToMove = resolveSingleAccountKeysToMove({
|
||||
|
||||
@@ -2,23 +2,22 @@ import { describe, expect, it } from "vitest";
|
||||
import { formatChannelSelectionLine, listChatChannels } from "./registry.js";
|
||||
|
||||
describe("channel registry helpers", () => {
|
||||
it("keeps Telegram first in the default order", () => {
|
||||
it("keeps Feishu first in the current default order", () => {
|
||||
const channels = listChatChannels();
|
||||
expect(channels[0]?.id).toBe("telegram");
|
||||
expect(channels[0]?.id).toBe("feishu");
|
||||
});
|
||||
|
||||
it("does not include MS Teams by default", () => {
|
||||
it("includes MS Teams in the bundled channel list", () => {
|
||||
const channels = listChatChannels();
|
||||
expect(channels.some((channel) => channel.id === "msteams")).toBe(false);
|
||||
expect(channels.some((channel) => channel.id === "msteams")).toBe(true);
|
||||
});
|
||||
|
||||
it("formats selection lines with docs labels + website extras", () => {
|
||||
const channels = listChatChannels();
|
||||
const first = channels[0];
|
||||
if (!first) {
|
||||
throw new Error("Missing channel metadata.");
|
||||
it("formats Telegram selection lines without a docs prefix and with website extras", () => {
|
||||
const telegram = listChatChannels().find((channel) => channel.id === "telegram");
|
||||
if (!telegram) {
|
||||
throw new Error("Missing Telegram channel metadata.");
|
||||
}
|
||||
const line = formatChannelSelectionLine(first, (path, label) =>
|
||||
const line = formatChannelSelectionLine(telegram, (path, label) =>
|
||||
[label, path].filter(Boolean).join(":"),
|
||||
);
|
||||
expect(line).not.toContain("Docs:");
|
||||
|
||||
Reference in New Issue
Block a user