mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-21 13:44:03 +00:00
test: scope implicit provider discovery harness
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, vi } from "vitest";
|
||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js";
|
||||
import { resetPluginLoaderTestStateForTest } from "../plugins/loader.test-fixtures.js";
|
||||
import { resetProviderRuntimeHookCacheForTest } from "../plugins/provider-runtime.js";
|
||||
import { resolveOwningPluginIdsForProvider } from "../plugins/providers.js";
|
||||
import type { MockFn } from "../test-utils/vitest-mock-fn.js";
|
||||
import { resetModelsJsonReadyCacheForTest } from "./models-config.js";
|
||||
import { resolveImplicitProviders } from "./models-config.providers.implicit.js";
|
||||
@@ -94,6 +97,7 @@ export async function withCopilotGithubToken<T>(
|
||||
}
|
||||
|
||||
export const MODELS_CONFIG_IMPLICIT_ENV_VARS = [
|
||||
"OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS",
|
||||
"VITEST",
|
||||
"NODE_ENV",
|
||||
"AI_GATEWAY_API_KEY",
|
||||
@@ -147,6 +151,53 @@ export const MODELS_CONFIG_IMPLICIT_ENV_VARS = [
|
||||
"AWS_SHARED_CREDENTIALS_FILE",
|
||||
];
|
||||
|
||||
const TEST_PROVIDER_ENV_TO_PROVIDER_IDS: Record<string, string[]> = {
|
||||
AI_GATEWAY_API_KEY: ["vercel-ai-gateway"],
|
||||
ANTHROPIC_VERTEX_PROJECT_ID: ["anthropic-vertex"],
|
||||
ANTHROPIC_VERTEX_USE_GCP_METADATA: ["anthropic-vertex"],
|
||||
AWS_ACCESS_KEY_ID: ["amazon-bedrock"],
|
||||
AWS_BEARER_TOKEN_BEDROCK: ["amazon-bedrock"],
|
||||
AWS_CONFIG_FILE: ["amazon-bedrock"],
|
||||
AWS_DEFAULT_REGION: ["amazon-bedrock"],
|
||||
AWS_PROFILE: ["amazon-bedrock"],
|
||||
AWS_REGION: ["amazon-bedrock"],
|
||||
AWS_SECRET_ACCESS_KEY: ["amazon-bedrock"],
|
||||
AWS_SESSION_TOKEN: ["amazon-bedrock"],
|
||||
AWS_SHARED_CREDENTIALS_FILE: ["amazon-bedrock"],
|
||||
BYTEPLUS_API_KEY: ["byteplus"],
|
||||
CLOUD_ML_REGION: ["anthropic-vertex"],
|
||||
CLOUDFLARE_AI_GATEWAY_API_KEY: ["cloudflare-ai-gateway"],
|
||||
COPILOT_GITHUB_TOKEN: ["github-copilot"],
|
||||
GEMINI_API_KEY: ["google"],
|
||||
GITHUB_TOKEN: ["github-copilot"],
|
||||
GH_TOKEN: ["github-copilot"],
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ["anthropic-vertex"],
|
||||
GOOGLE_CLOUD_LOCATION: ["anthropic-vertex"],
|
||||
GOOGLE_CLOUD_PROJECT: ["anthropic-vertex"],
|
||||
GOOGLE_CLOUD_PROJECT_ID: ["anthropic-vertex"],
|
||||
HF_TOKEN: ["huggingface"],
|
||||
HUGGINGFACE_HUB_TOKEN: ["huggingface"],
|
||||
KILOCODE_API_KEY: ["kilocode"],
|
||||
KIMI_API_KEY: ["moonshot"],
|
||||
KIMICODE_API_KEY: ["kimi-coding"],
|
||||
MINIMAX_API_KEY: ["minimax"],
|
||||
MINIMAX_OAUTH_TOKEN: ["minimax"],
|
||||
MODELSTUDIO_API_KEY: ["chutes"],
|
||||
MOONSHOT_API_KEY: ["moonshot"],
|
||||
NVIDIA_API_KEY: ["nvidia"],
|
||||
OLLAMA_API_KEY: ["ollama"],
|
||||
OPENAI_API_KEY: ["openai"],
|
||||
OPENROUTER_API_KEY: ["openrouter"],
|
||||
QIANFAN_API_KEY: ["qianfan"],
|
||||
STEPFUN_API_KEY: ["stepfun"],
|
||||
SYNTHETIC_API_KEY: ["custom-proxy"],
|
||||
TOGETHER_API_KEY: ["together"],
|
||||
VENICE_API_KEY: ["venice"],
|
||||
VLLM_API_KEY: ["vllm"],
|
||||
VOLCANO_ENGINE_API_KEY: ["volcengine"],
|
||||
XIAOMI_API_KEY: ["xiaomi"],
|
||||
};
|
||||
|
||||
export function snapshotImplicitProviderEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||
const source = env ?? process.env;
|
||||
const snapshot: NodeJS.ProcessEnv = {};
|
||||
@@ -167,12 +218,103 @@ export function snapshotImplicitProviderEnv(env?: NodeJS.ProcessEnv): NodeJS.Pro
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
export function resolveImplicitProvidersForTest(
|
||||
async function inferAuthProfileProviderIds(agentDir?: string): Promise<string[]> {
|
||||
if (!agentDir) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const raw = await fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
profiles?: Record<string, { provider?: string }>;
|
||||
order?: Record<string, unknown>;
|
||||
};
|
||||
const providers = new Set<string>();
|
||||
for (const providerId of Object.keys(parsed.order ?? {})) {
|
||||
if (providerId.trim()) {
|
||||
providers.add(providerId.trim());
|
||||
}
|
||||
}
|
||||
for (const profile of Object.values(parsed.profiles ?? {})) {
|
||||
const providerId = profile?.provider?.trim();
|
||||
if (providerId) {
|
||||
providers.add(providerId);
|
||||
}
|
||||
}
|
||||
return [...providers];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function inferImplicitProviderTestPluginIds(params: {
|
||||
agentDir?: string;
|
||||
config?: OpenClawConfig;
|
||||
explicitProviders?: Record<string, unknown> | null;
|
||||
env: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
}): Promise<string[]> {
|
||||
const providerIds = new Set<string>();
|
||||
for (const providerId of Object.keys(params.config?.models?.providers ?? {})) {
|
||||
if (providerId.trim()) {
|
||||
providerIds.add(providerId.trim());
|
||||
}
|
||||
}
|
||||
for (const providerId of Object.keys(params.explicitProviders ?? {})) {
|
||||
if (providerId.trim()) {
|
||||
providerIds.add(providerId.trim());
|
||||
}
|
||||
}
|
||||
for (const [envVar, mappedProviderIds] of Object.entries(TEST_PROVIDER_ENV_TO_PROVIDER_IDS)) {
|
||||
if (!params.env[envVar]?.trim()) {
|
||||
continue;
|
||||
}
|
||||
for (const providerId of mappedProviderIds) {
|
||||
providerIds.add(providerId);
|
||||
}
|
||||
}
|
||||
for (const providerId of await inferAuthProfileProviderIds(params.agentDir)) {
|
||||
providerIds.add(providerId);
|
||||
}
|
||||
|
||||
if (providerIds.size === 0) {
|
||||
// No config/env/auth hints: keep ambient local auto-discovery focused on the
|
||||
// one provider that is expected to probe localhost in tests.
|
||||
return ["ollama"];
|
||||
}
|
||||
|
||||
const pluginIds = new Set<string>();
|
||||
for (const providerId of providerIds) {
|
||||
const owningPluginIds =
|
||||
resolveOwningPluginIdsForProvider({
|
||||
provider: providerId,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
}) ?? [];
|
||||
for (const pluginId of owningPluginIds) {
|
||||
pluginIds.add(pluginId);
|
||||
}
|
||||
}
|
||||
return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export async function resolveImplicitProvidersForTest(
|
||||
params: Parameters<typeof resolveImplicitProviders>[0],
|
||||
) {
|
||||
const env = snapshotImplicitProviderEnv(params.env);
|
||||
const inferredPluginIds = await inferImplicitProviderTestPluginIds({
|
||||
agentDir: params.agentDir,
|
||||
config: params.config,
|
||||
explicitProviders: params.explicitProviders,
|
||||
env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
});
|
||||
if (inferredPluginIds.length > 0) {
|
||||
env.OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS = inferredPluginIds.join(",");
|
||||
}
|
||||
return resolveImplicitProviders({
|
||||
...params,
|
||||
env: snapshotImplicitProviderEnv(params.env),
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,15 @@ function resolveLiveProviderCatalogTimeoutMs(env: NodeJS.ProcessEnv): number | n
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : 15_000;
|
||||
}
|
||||
|
||||
function resolveLiveProviderDiscoveryFilter(env: NodeJS.ProcessEnv): string[] | undefined {
|
||||
function resolveProviderDiscoveryFilter(env: NodeJS.ProcessEnv): string[] | undefined {
|
||||
const testRaw = env.OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS?.trim();
|
||||
if (testRaw) {
|
||||
const ids = testRaw
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
return ids.length > 0 ? [...new Set(ids)] : undefined;
|
||||
}
|
||||
const live =
|
||||
env.OPENCLAW_LIVE_TEST === "1" || env.OPENCLAW_LIVE_GATEWAY === "1" || env.LIVE === "1";
|
||||
if (!live) {
|
||||
@@ -150,15 +158,9 @@ function resolveExistingImplicitProviderFromContext(params: {
|
||||
|
||||
async function resolvePluginImplicitProviders(
|
||||
ctx: ImplicitProviderContext,
|
||||
providers: import("../plugins/types.js").ProviderPlugin[],
|
||||
order: import("../plugins/types.js").ProviderDiscoveryOrder,
|
||||
): Promise<Record<string, ProviderConfig> | undefined> {
|
||||
const onlyPluginIds = resolveLiveProviderDiscoveryFilter(ctx.env);
|
||||
const providers = await resolvePluginDiscoveryProviders({
|
||||
config: ctx.config,
|
||||
workspaceDir: ctx.workspaceDir,
|
||||
env: ctx.env,
|
||||
onlyPluginIds,
|
||||
});
|
||||
const byOrder = groupPluginDiscoveryProvidersByOrder(providers);
|
||||
const discovered: Record<string, ProviderConfig> = {};
|
||||
const catalogConfig = buildPluginCatalogConfig(ctx);
|
||||
@@ -309,9 +311,18 @@ export async function resolveImplicitProviders(
|
||||
resolveProviderApiKey: createProviderApiKeyResolver(env, authStore, params.config),
|
||||
resolveProviderAuth: createProviderAuthResolver(env, authStore, params.config),
|
||||
};
|
||||
const discoveryProviders = await resolvePluginDiscoveryProviders({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: resolveProviderDiscoveryFilter(env),
|
||||
});
|
||||
|
||||
for (const order of PLUGIN_DISCOVERY_ORDERS) {
|
||||
mergeImplicitProviderSet(providers, await resolvePluginImplicitProviders(context, order));
|
||||
mergeImplicitProviderSet(
|
||||
providers,
|
||||
await resolvePluginImplicitProviders(context, discoveryProviders, order),
|
||||
);
|
||||
}
|
||||
|
||||
return providers;
|
||||
|
||||
@@ -55,7 +55,7 @@ describe("Ollama auto-discovery", () => {
|
||||
}) as unknown as typeof fetch;
|
||||
|
||||
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
|
||||
const providers = await resolveImplicitProvidersForTest({ agentDir });
|
||||
const providers = await resolveImplicitProvidersForTest({ agentDir, env: {} });
|
||||
|
||||
expect(providers?.ollama).toBeDefined();
|
||||
expect(providers?.ollama?.apiKey).toBe("ollama-local");
|
||||
@@ -73,7 +73,7 @@ describe("Ollama auto-discovery", () => {
|
||||
mockOllamaUnreachable();
|
||||
|
||||
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
|
||||
const providers = await resolveImplicitProvidersForTest({ agentDir });
|
||||
const providers = await resolveImplicitProvidersForTest({ agentDir, env: {} });
|
||||
|
||||
expect(providers?.ollama).toBeUndefined();
|
||||
const ollamaWarnings = warnSpy.mock.calls.filter(
|
||||
@@ -91,6 +91,7 @@ describe("Ollama auto-discovery", () => {
|
||||
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
|
||||
await resolveImplicitProvidersForTest({
|
||||
agentDir,
|
||||
env: {},
|
||||
explicitProviders: {
|
||||
ollama: {
|
||||
baseUrl: "http://127.0.0.1:11434/v1",
|
||||
|
||||
@@ -61,7 +61,7 @@ describe("openai-codex implicit provider", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps generated openai-codex rows on the generic OpenAI responses transport", async () => {
|
||||
it("normalizes generated openai-codex rows back to the Codex transport when oauth exists", async () => {
|
||||
await withModelsTempHome(async () => {
|
||||
await withTempEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS, async () => {
|
||||
unsetEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS);
|
||||
@@ -99,8 +99,8 @@ describe("openai-codex implicit provider", () => {
|
||||
providers: Record<string, { baseUrl?: string; api?: string }>;
|
||||
}>();
|
||||
expect(parsed.providers["openai-codex"]).toMatchObject({
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
api: "openai-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
api: "openai-codex-responses",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user