mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:50:43 +00:00
test: speed up import-heavy suites
This commit is contained in:
@@ -44,6 +44,18 @@ async function expectVertexAdcEnvApiKey(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testModelDefinition(id: string): Model<Api> {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: id,
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 128_000,
|
||||||
|
maxTokens: 8192,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
vi.mock("../plugins/setup-registry.js", async () => {
|
vi.mock("../plugins/setup-registry.js", async () => {
|
||||||
const { readFileSync } = await import("node:fs");
|
const { readFileSync } = await import("node:fs");
|
||||||
return {
|
return {
|
||||||
@@ -627,6 +639,51 @@ describe("getApiKeyForModel", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reuses runtime auth availability for provider auth checks", () => {
|
||||||
|
const store = { version: 1 as const, profiles: {} };
|
||||||
|
const localNoKeyConfig = {
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
vllm: {
|
||||||
|
api: "openai-completions",
|
||||||
|
baseUrl: "http://127.0.0.1:8000/v1",
|
||||||
|
models: [testModelDefinition("meta-llama/Meta-Llama-3-8B-Instruct")],
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
api: "openai-completions",
|
||||||
|
baseUrl: "https://remote.example.com/v1",
|
||||||
|
models: [testModelDefinition("remote-model")],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as OpenClawConfig;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
hasAuthForModelProvider({
|
||||||
|
provider: "amazon-bedrock",
|
||||||
|
cfg: {} as OpenClawConfig,
|
||||||
|
env: {},
|
||||||
|
store,
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
hasAuthForModelProvider({
|
||||||
|
provider: "vllm",
|
||||||
|
cfg: localNoKeyConfig,
|
||||||
|
env: {},
|
||||||
|
store,
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
hasAuthForModelProvider({
|
||||||
|
provider: "remote",
|
||||||
|
cfg: localNoKeyConfig,
|
||||||
|
env: {},
|
||||||
|
store,
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("hasAvailableAuthForProvider('google') accepts GOOGLE_API_KEY fallback", async () => {
|
it("hasAvailableAuthForProvider('google') accepts GOOGLE_API_KEY fallback", async () => {
|
||||||
await withEnvAsync(
|
await withEnvAsync(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
|
||||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
||||||
import type { AuthProfileStore } from "./auth-profiles.js";
|
|
||||||
import { hasAuthForModelProvider } from "./model-provider-auth.js";
|
|
||||||
|
|
||||||
const emptyStore: AuthProfileStore = {
|
|
||||||
version: 1,
|
|
||||||
profiles: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
function modelDefinition(id: string) {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: id,
|
|
||||||
reasoning: false,
|
|
||||||
input: ["text" as const],
|
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
||||||
contextWindow: 128_000,
|
|
||||||
maxTokens: 8192,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("model provider auth availability", () => {
|
|
||||||
it("accepts implicit Bedrock AWS SDK auth without an API key", () => {
|
|
||||||
expect(
|
|
||||||
hasAuthForModelProvider({
|
|
||||||
provider: "amazon-bedrock",
|
|
||||||
cfg: {} as OpenClawConfig,
|
|
||||||
env: {},
|
|
||||||
store: emptyStore,
|
|
||||||
}),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("accepts local no-key custom providers", () => {
|
|
||||||
const cfg = {
|
|
||||||
models: {
|
|
||||||
providers: {
|
|
||||||
vllm: {
|
|
||||||
api: "openai-completions",
|
|
||||||
baseUrl: "http://127.0.0.1:8000/v1",
|
|
||||||
models: [modelDefinition("meta-llama/Meta-Llama-3-8B-Instruct")],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as OpenClawConfig;
|
|
||||||
|
|
||||||
expect(
|
|
||||||
hasAuthForModelProvider({
|
|
||||||
provider: "vllm",
|
|
||||||
cfg,
|
|
||||||
env: {},
|
|
||||||
store: emptyStore,
|
|
||||||
}),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps remote no-key custom providers unavailable", () => {
|
|
||||||
const cfg = {
|
|
||||||
models: {
|
|
||||||
providers: {
|
|
||||||
remote: {
|
|
||||||
api: "openai-completions",
|
|
||||||
baseUrl: "https://remote.example.com/v1",
|
|
||||||
models: [modelDefinition("remote-model")],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as OpenClawConfig;
|
|
||||||
|
|
||||||
expect(
|
|
||||||
hasAuthForModelProvider({
|
|
||||||
provider: "remote",
|
|
||||||
cfg,
|
|
||||||
env: {},
|
|
||||||
store: emptyStore,
|
|
||||||
}),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -11,11 +11,16 @@ const baseProviderTransform = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const transformProviderSystemPrompt: Parameters<
|
||||||
|
typeof buildAttemptSystemPrompt
|
||||||
|
>[0]["transformProviderSystemPrompt"] = ({ context }) => context.systemPrompt;
|
||||||
|
|
||||||
describe("buildAttemptSystemPrompt", () => {
|
describe("buildAttemptSystemPrompt", () => {
|
||||||
it("preserves bootstrap Project Context when a system prompt override is configured", () => {
|
it("preserves bootstrap Project Context when a system prompt override is configured", () => {
|
||||||
const result = buildAttemptSystemPrompt({
|
const result = buildAttemptSystemPrompt({
|
||||||
isRawModelRun: false,
|
isRawModelRun: false,
|
||||||
systemPromptOverrideText: "Custom override prompt.",
|
systemPromptOverrideText: "Custom override prompt.",
|
||||||
|
transformProviderSystemPrompt,
|
||||||
embeddedSystemPrompt: {
|
embeddedSystemPrompt: {
|
||||||
workspaceDir: "/tmp/openclaw",
|
workspaceDir: "/tmp/openclaw",
|
||||||
reasoningTagHint: false,
|
reasoningTagHint: false,
|
||||||
@@ -59,6 +64,7 @@ describe("buildAttemptSystemPrompt", () => {
|
|||||||
it("omits system prompts for raw model probes", () => {
|
it("omits system prompts for raw model probes", () => {
|
||||||
const result = buildAttemptSystemPrompt({
|
const result = buildAttemptSystemPrompt({
|
||||||
isRawModelRun: true,
|
isRawModelRun: true,
|
||||||
|
transformProviderSystemPrompt,
|
||||||
embeddedSystemPrompt: {
|
embeddedSystemPrompt: {
|
||||||
workspaceDir: "/tmp/openclaw",
|
workspaceDir: "/tmp/openclaw",
|
||||||
reasoningTagHint: false,
|
reasoningTagHint: false,
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
|
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
|
||||||
import { transformProviderSystemPrompt } from "../../../plugins/provider-runtime.js";
|
|
||||||
import type { ProviderTransformSystemPromptContext } from "../../../plugins/types.js";
|
import type { ProviderTransformSystemPromptContext } from "../../../plugins/types.js";
|
||||||
import { appendAgentBootstrapSystemPromptSupplement } from "../../system-prompt.js";
|
import { appendAgentBootstrapSystemPromptSupplement } from "../../system-prompt.js";
|
||||||
import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "../system-prompt.js";
|
import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "../system-prompt.js";
|
||||||
|
|
||||||
type EmbeddedSystemPromptParams = Parameters<typeof buildEmbeddedSystemPrompt>[0];
|
type EmbeddedSystemPromptParams = Parameters<typeof buildEmbeddedSystemPrompt>[0];
|
||||||
|
type ProviderSystemPromptTransform = (params: {
|
||||||
|
provider: string;
|
||||||
|
config?: OpenClawConfig;
|
||||||
|
workspaceDir: string;
|
||||||
|
context: ProviderTransformSystemPromptContext;
|
||||||
|
}) => string;
|
||||||
|
|
||||||
export type BuildAttemptSystemPromptParams = {
|
export type BuildAttemptSystemPromptParams = {
|
||||||
isRawModelRun: boolean;
|
isRawModelRun: boolean;
|
||||||
systemPromptOverrideText?: string;
|
systemPromptOverrideText?: string;
|
||||||
embeddedSystemPrompt: EmbeddedSystemPromptParams;
|
embeddedSystemPrompt: EmbeddedSystemPromptParams;
|
||||||
|
transformProviderSystemPrompt: ProviderSystemPromptTransform;
|
||||||
providerTransform: {
|
providerTransform: {
|
||||||
provider: string;
|
provider: string;
|
||||||
config?: OpenClawConfig;
|
config?: OpenClawConfig;
|
||||||
@@ -38,7 +44,7 @@ export function buildAttemptSystemPrompt(
|
|||||||
|
|
||||||
const systemPrompt = params.isRawModelRun
|
const systemPrompt = params.isRawModelRun
|
||||||
? ""
|
? ""
|
||||||
: transformProviderSystemPrompt({
|
: params.transformProviderSystemPrompt({
|
||||||
provider: params.providerTransform.provider,
|
provider: params.providerTransform.provider,
|
||||||
config: params.providerTransform.config,
|
config: params.providerTransform.config,
|
||||||
workspaceDir: params.providerTransform.workspaceDir,
|
workspaceDir: params.providerTransform.workspaceDir,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
resolveEmbeddedAgentStreamFn,
|
resolveEmbeddedAgentStreamFn,
|
||||||
resolveUnknownToolGuardThreshold,
|
resolveUnknownToolGuardThreshold,
|
||||||
shouldCreateBundleMcpRuntimeForAttempt,
|
shouldCreateBundleMcpRuntimeForAttempt,
|
||||||
|
shouldBuildCoreCodingToolsForAllowlist,
|
||||||
resolveAttemptToolPolicyMessageProvider,
|
resolveAttemptToolPolicyMessageProvider,
|
||||||
resolvePromptBuildHookResult,
|
resolvePromptBuildHookResult,
|
||||||
resolvePromptModeForSession,
|
resolvePromptModeForSession,
|
||||||
@@ -81,6 +82,15 @@ describe("applyEmbeddedAttemptToolsAllow", () => {
|
|||||||
applyEmbeddedAttemptToolsAllow(tools, [" cron ", "READ"]).map((tool) => tool.name),
|
applyEmbeddedAttemptToolsAllow(tools, [" cron ", "READ"]).map((tool) => tool.name),
|
||||||
).toEqual(["cron", "read"]);
|
).toEqual(["cron", "read"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps plugin-only allowlists on the shared tool policy path", () => {
|
||||||
|
const tools = [{ name: "memory_search" }, { name: "plugin_extra" }];
|
||||||
|
|
||||||
|
expect(shouldBuildCoreCodingToolsForAllowlist(["memory_search"])).toBe(false);
|
||||||
|
expect(
|
||||||
|
applyEmbeddedAttemptToolsAllow(tools, ["memory_search"]).map((tool) => tool.name),
|
||||||
|
).toEqual(["memory_search"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildEmbeddedAttemptToolRunContext", () => {
|
describe("buildEmbeddedAttemptToolRunContext", () => {
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
||||||
import {
|
|
||||||
cleanupTempPaths,
|
|
||||||
createContextEngineAttemptRunner,
|
|
||||||
getHoisted,
|
|
||||||
resetEmbeddedAttemptHarness,
|
|
||||||
} from "./attempt.spawn-workspace.test-support.js";
|
|
||||||
|
|
||||||
describe("runEmbeddedAttempt toolsAllow startup cost", () => {
|
|
||||||
const tempPaths: string[] = [];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
resetEmbeddedAttemptHarness();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await cleanupTempPaths(tempPaths);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps plugin-only allowlists on the shared tool policy path", async () => {
|
|
||||||
const hoisted = getHoisted();
|
|
||||||
hoisted.createOpenClawCodingToolsMock.mockReturnValue([
|
|
||||||
{
|
|
||||||
name: "memory_search",
|
|
||||||
description: "search memory",
|
|
||||||
parameters: { type: "object", properties: {} },
|
|
||||||
execute: async () => "ok",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "plugin_extra",
|
|
||||||
description: "extra plugin tool",
|
|
||||||
parameters: { type: "object", properties: {} },
|
|
||||||
execute: async () => "ok",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
await createContextEngineAttemptRunner({
|
|
||||||
contextEngine: {
|
|
||||||
assemble: async ({ messages }) => ({ messages, estimatedTokens: 1 }),
|
|
||||||
},
|
|
||||||
attemptOverrides: {
|
|
||||||
toolsAllow: ["memory_search"],
|
|
||||||
},
|
|
||||||
sessionKey: "agent:main:main",
|
|
||||||
tempPaths,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(hoisted.createOpenClawCodingToolsMock).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
includeCoreTools: false,
|
|
||||||
runtimeToolAllowlist: ["memory_search"],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const createSessionOptions = hoisted.createAgentSessionMock.mock.calls[0]?.[0] as
|
|
||||||
| { customTools?: { name: string }[] }
|
|
||||||
| undefined;
|
|
||||||
expect(createSessionOptions?.customTools?.map((tool) => tool.name)).toEqual(["memory_search"]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
resolveProviderSystemPromptContribution,
|
resolveProviderSystemPromptContribution,
|
||||||
resolveProviderTextTransforms,
|
resolveProviderTextTransforms,
|
||||||
|
transformProviderSystemPrompt,
|
||||||
} from "../../../plugins/provider-runtime.js";
|
} from "../../../plugins/provider-runtime.js";
|
||||||
import { getPluginToolMeta } from "../../../plugins/tools.js";
|
import { getPluginToolMeta } from "../../../plugins/tools.js";
|
||||||
import { isAcpSessionKey, isSubagentSessionKey } from "../../../routing/session-key.js";
|
import { isAcpSessionKey, isSubagentSessionKey } from "../../../routing/session-key.js";
|
||||||
@@ -525,7 +526,7 @@ const CORE_CODING_TOOL_ALLOWLIST_NAMES = new Set([
|
|||||||
"write",
|
"write",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function shouldBuildCoreCodingToolsForAllowlist(toolsAllow?: string[]): boolean {
|
export function shouldBuildCoreCodingToolsForAllowlist(toolsAllow?: string[]): boolean {
|
||||||
if (!toolsAllow || toolsAllow.length === 0) {
|
if (!toolsAllow || toolsAllow.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1291,6 +1292,7 @@ export async function runEmbeddedAttempt(
|
|||||||
const attemptSystemPrompt = buildAttemptSystemPrompt({
|
const attemptSystemPrompt = buildAttemptSystemPrompt({
|
||||||
isRawModelRun,
|
isRawModelRun,
|
||||||
systemPromptOverrideText,
|
systemPromptOverrideText,
|
||||||
|
transformProviderSystemPrompt,
|
||||||
embeddedSystemPrompt: {
|
embeddedSystemPrompt: {
|
||||||
workspaceDir: effectiveWorkspace,
|
workspaceDir: effectiveWorkspace,
|
||||||
defaultThinkLevel: params.thinkLevel,
|
defaultThinkLevel: params.thinkLevel,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { OpenClawConfig } from "../../../config/types.js";
|
import type { OpenClawConfig } from "../../../config/types.js";
|
||||||
import { migrateLegacyConfig } from "./legacy-config-migrate.js";
|
|
||||||
import { LEGACY_CONFIG_MIGRATIONS } from "./legacy-config-migrations.js";
|
import { LEGACY_CONFIG_MIGRATIONS } from "./legacy-config-migrations.js";
|
||||||
|
|
||||||
function migrateLegacyConfigForTest(raw: unknown): {
|
function migrateLegacyConfigForTest(raw: unknown): {
|
||||||
@@ -211,38 +210,6 @@ describe("legacy migrate mention routing", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("legacy migrate sandbox scope aliases", () => {
|
describe("legacy migrate sandbox scope aliases", () => {
|
||||||
it("returns migrated config when unrelated plugin validation issues remain (#76798)", () => {
|
|
||||||
const res = migrateLegacyConfig({
|
|
||||||
agents: {
|
|
||||||
defaults: {
|
|
||||||
model: { primary: "openai/gpt-5.5" },
|
|
||||||
llm: { idleTimeoutSeconds: 120 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
entries: {
|
|
||||||
brave: {
|
|
||||||
enabled: true,
|
|
||||||
config: { webSearch: { mode: "definitely-invalid" } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tools: { web: { search: { provider: "brave" } } },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.partiallyValid).toBe(true);
|
|
||||||
expect(res.changes).toContain(
|
|
||||||
"Removed agents.defaults.llm; model idle timeout now follows models.providers.<id>.timeoutSeconds.",
|
|
||||||
);
|
|
||||||
expect(res.changes).toContain(
|
|
||||||
"Migration applied; other validation issues remain — run doctor to review.",
|
|
||||||
);
|
|
||||||
expect(res.config?.agents?.defaults).toEqual({
|
|
||||||
model: { primary: "openai/gpt-5.5" },
|
|
||||||
});
|
|
||||||
expect(res.config?.tools?.web?.search?.provider).toBe("brave");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes legacy agents.defaults.llm timeout config", () => {
|
it("removes legacy agents.defaults.llm timeout config", () => {
|
||||||
const res = migrateLegacyConfigForTest({
|
const res = migrateLegacyConfigForTest({
|
||||||
agents: {
|
agents: {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { migrateLegacyConfig } from "./legacy-config-migrate.js";
|
||||||
|
|
||||||
|
describe("legacy config migrate validation", () => {
|
||||||
|
it("returns migrated config when unrelated plugin validation issues remain (#76798)", () => {
|
||||||
|
const res = migrateLegacyConfig({
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
model: { primary: "openai/gpt-5.5" },
|
||||||
|
llm: { idleTimeoutSeconds: 120 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
entries: {
|
||||||
|
brave: {
|
||||||
|
enabled: true,
|
||||||
|
config: { webSearch: { mode: "definitely-invalid" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tools: { web: { search: { provider: "brave" } } },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.partiallyValid).toBe(true);
|
||||||
|
expect(res.changes).toContain(
|
||||||
|
"Removed agents.defaults.llm; model idle timeout now follows models.providers.<id>.timeoutSeconds.",
|
||||||
|
);
|
||||||
|
expect(res.changes).toContain(
|
||||||
|
"Migration applied; other validation issues remain — run doctor to review.",
|
||||||
|
);
|
||||||
|
expect(res.config?.agents?.defaults).toEqual({
|
||||||
|
model: { primary: "openai/gpt-5.5" },
|
||||||
|
});
|
||||||
|
expect(res.config?.tools?.web?.search?.provider).toBe("brave");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,28 +1,5 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { OpenClawConfig } from "../../../config/config.js";
|
import type { OpenClawConfig } from "../../../config/config.js";
|
||||||
|
|
||||||
vi.mock("../../../plugins/plugin-metadata-snapshot.js", () => ({
|
|
||||||
loadPluginMetadataSnapshot: () => ({
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
id: "brave",
|
|
||||||
origin: "bundled",
|
|
||||||
contracts: { webSearchProviders: ["brave"] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "xai",
|
|
||||||
origin: "bundled",
|
|
||||||
contracts: { webSearchProviders: ["grok"] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "moonshot",
|
|
||||||
origin: "bundled",
|
|
||||||
contracts: { webSearchProviders: ["kimi"] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
listLegacyWebSearchConfigPaths,
|
listLegacyWebSearchConfigPaths,
|
||||||
migrateLegacyWebSearchConfig,
|
migrateLegacyWebSearchConfig,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { mergeMissing } from "../../../config/legacy.shared.js";
|
import { mergeMissing } from "../../../config/legacy.shared.js";
|
||||||
import { loadManifestMetadataSnapshot } from "../../../plugins/manifest-contract-eligibility.js";
|
|
||||||
import {
|
import {
|
||||||
cloneRecord,
|
cloneRecord,
|
||||||
ensureRecord,
|
ensureRecord,
|
||||||
@@ -10,24 +9,28 @@ import {
|
|||||||
|
|
||||||
const MODERN_SCOPED_WEB_SEARCH_KEYS = new Set(["openaiCodex"]);
|
const MODERN_SCOPED_WEB_SEARCH_KEYS = new Set(["openaiCodex"]);
|
||||||
|
|
||||||
|
const BUNDLED_LEGACY_WEB_SEARCH_OWNERS = new Map<string, string>([
|
||||||
|
["brave", "brave"],
|
||||||
|
["duckduckgo", "duckduckgo"],
|
||||||
|
["exa", "exa"],
|
||||||
|
["firecrawl", "firecrawl"],
|
||||||
|
["gemini", "google"],
|
||||||
|
["grok", "xai"],
|
||||||
|
["kimi", "moonshot"],
|
||||||
|
["minimax", "minimax"],
|
||||||
|
["ollama", "ollama"],
|
||||||
|
["perplexity", "perplexity"],
|
||||||
|
["searxng", "searxng"],
|
||||||
|
["tavily", "tavily"],
|
||||||
|
]);
|
||||||
|
|
||||||
// Tavily only ever used the plugin-owned config path, so there is no legacy
|
// Tavily only ever used the plugin-owned config path, so there is no legacy
|
||||||
// `tools.web.search.tavily.*` shape to migrate.
|
// `tools.web.search.tavily.*` shape to migrate.
|
||||||
const NON_MIGRATED_LEGACY_WEB_SEARCH_PROVIDER_IDS = new Set(["tavily"]);
|
const NON_MIGRATED_LEGACY_WEB_SEARCH_PROVIDER_IDS = new Set(["tavily"]);
|
||||||
const LEGACY_GLOBAL_WEB_SEARCH_PROVIDER_ID = "brave";
|
const LEGACY_GLOBAL_WEB_SEARCH_PROVIDER_ID = "brave";
|
||||||
|
|
||||||
function getBundledLegacyWebSearchOwners(): ReadonlyMap<string, string> {
|
function getBundledLegacyWebSearchOwners(): ReadonlyMap<string, string> {
|
||||||
const owners = new Map<string, string>();
|
return BUNDLED_LEGACY_WEB_SEARCH_OWNERS;
|
||||||
for (const plugin of loadManifestMetadataSnapshot({ config: {}, env: process.env }).plugins) {
|
|
||||||
if (plugin.origin !== "bundled") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const providerId of plugin.contracts?.webSearchProviders ?? []) {
|
|
||||||
if (!owners.has(providerId)) {
|
|
||||||
owners.set(providerId, plugin.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return owners;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLegacyWebSearchProviderIds(
|
function getLegacyWebSearchProviderIds(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { OpenClawConfig } from "../config/types.js";
|
import type { OpenClawConfig } from "../config/types.js";
|
||||||
import type { ImageGenerationProviderPlugin } from "../plugins/types.js";
|
import type { ImageGenerationProviderPlugin } from "../plugins/types.js";
|
||||||
import type * as ProviderRegistry from "./provider-registry.js";
|
import { getImageGenerationProvider, listImageGenerationProviders } from "./provider-registry.js";
|
||||||
|
|
||||||
const { resolvePluginCapabilityProvidersMock } = vi.hoisted(() => ({
|
const { resolvePluginCapabilityProvidersMock } = vi.hoisted(() => ({
|
||||||
resolvePluginCapabilityProvidersMock: vi.fn<() => ImageGenerationProviderPlugin[]>(() => []),
|
resolvePluginCapabilityProvidersMock: vi.fn<() => ImageGenerationProviderPlugin[]>(() => []),
|
||||||
@@ -11,9 +11,6 @@ vi.mock("../plugins/capability-provider-runtime.js", () => ({
|
|||||||
resolvePluginCapabilityProviders: resolvePluginCapabilityProvidersMock,
|
resolvePluginCapabilityProviders: resolvePluginCapabilityProvidersMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let getImageGenerationProvider: typeof ProviderRegistry.getImageGenerationProvider;
|
|
||||||
let listImageGenerationProviders: typeof ProviderRegistry.listImageGenerationProviders;
|
|
||||||
|
|
||||||
function createProvider(
|
function createProvider(
|
||||||
params: Pick<ImageGenerationProviderPlugin, "id"> & Partial<ImageGenerationProviderPlugin>,
|
params: Pick<ImageGenerationProviderPlugin, "id"> & Partial<ImageGenerationProviderPlugin>,
|
||||||
): ImageGenerationProviderPlugin {
|
): ImageGenerationProviderPlugin {
|
||||||
@@ -31,12 +28,9 @@ function createProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("image-generation provider registry", () => {
|
describe("image-generation provider registry", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(() => {
|
||||||
vi.resetModules();
|
|
||||||
resolvePluginCapabilityProvidersMock.mockReset();
|
resolvePluginCapabilityProvidersMock.mockReset();
|
||||||
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
|
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
|
||||||
({ getImageGenerationProvider, listImageGenerationProviders } =
|
|
||||||
await import("./provider-registry.js"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("delegates provider resolution to the capability provider boundary", () => {
|
it("delegates provider resolution to the capability provider boundary", () => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { VideoGenerationProviderPlugin } from "../plugins/types.js";
|
import type { VideoGenerationProviderPlugin } from "../plugins/types.js";
|
||||||
|
import { getVideoGenerationProvider, listVideoGenerationProviders } from "./provider-registry.js";
|
||||||
|
|
||||||
const { resolvePluginCapabilityProvidersMock } = vi.hoisted(() => ({
|
const { resolvePluginCapabilityProvidersMock } = vi.hoisted(() => ({
|
||||||
resolvePluginCapabilityProvidersMock: vi.fn<() => VideoGenerationProviderPlugin[]>(() => []),
|
resolvePluginCapabilityProvidersMock: vi.fn<() => VideoGenerationProviderPlugin[]>(() => []),
|
||||||
@@ -22,20 +23,13 @@ function createProvider(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProviderRegistry() {
|
|
||||||
vi.resetModules();
|
|
||||||
return await import("./provider-registry.js");
|
|
||||||
}
|
|
||||||
describe("video-generation provider registry", () => {
|
describe("video-generation provider registry", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetModules();
|
|
||||||
resolvePluginCapabilityProvidersMock.mockReset();
|
resolvePluginCapabilityProvidersMock.mockReset();
|
||||||
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
|
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("delegates provider resolution to the capability provider boundary", async () => {
|
it("delegates provider resolution to the capability provider boundary", () => {
|
||||||
const { listVideoGenerationProviders } = await loadProviderRegistry();
|
|
||||||
|
|
||||||
expect(listVideoGenerationProviders()).toEqual([]);
|
expect(listVideoGenerationProviders()).toEqual([]);
|
||||||
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
|
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
|
||||||
key: "videoGenerationProviders",
|
key: "videoGenerationProviders",
|
||||||
@@ -43,9 +37,8 @@ describe("video-generation provider registry", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses active plugin providers without loading from disk", async () => {
|
it("uses active plugin providers without loading from disk", () => {
|
||||||
resolvePluginCapabilityProvidersMock.mockReturnValue([createProvider({ id: "custom-video" })]);
|
resolvePluginCapabilityProvidersMock.mockReturnValue([createProvider({ id: "custom-video" })]);
|
||||||
const { getVideoGenerationProvider } = await loadProviderRegistry();
|
|
||||||
|
|
||||||
const provider = getVideoGenerationProvider("custom-video");
|
const provider = getVideoGenerationProvider("custom-video");
|
||||||
|
|
||||||
@@ -56,13 +49,11 @@ describe("video-generation provider registry", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores prototype-like provider ids and aliases", async () => {
|
it("ignores prototype-like provider ids and aliases", () => {
|
||||||
resolvePluginCapabilityProvidersMock.mockReturnValue([
|
resolvePluginCapabilityProvidersMock.mockReturnValue([
|
||||||
createProvider({ id: "__proto__", aliases: ["constructor", "prototype"] }),
|
createProvider({ id: "__proto__", aliases: ["constructor", "prototype"] }),
|
||||||
createProvider({ id: "safe-video", aliases: ["safe-alias", "constructor"] }),
|
createProvider({ id: "safe-video", aliases: ["safe-alias", "constructor"] }),
|
||||||
]);
|
]);
|
||||||
const { getVideoGenerationProvider, listVideoGenerationProviders } =
|
|
||||||
await loadProviderRegistry();
|
|
||||||
|
|
||||||
expect(listVideoGenerationProviders().map((provider) => provider.id)).toEqual(["safe-video"]);
|
expect(listVideoGenerationProviders().map((provider) => provider.id)).toEqual(["safe-video"]);
|
||||||
expect(getVideoGenerationProvider("__proto__")).toBeUndefined();
|
expect(getVideoGenerationProvider("__proto__")).toBeUndefined();
|
||||||
|
|||||||
Reference in New Issue
Block a user