






















@@ -1,15 +1,9 @@
1-import fs from "node:fs/promises";
2-import path from "node:path";
3-import { afterEach, beforeEach, vi } from "vitest";
1+import { afterEach, beforeEach } from "vitest";
42import { clearConfigCache, clearRuntimeConfigSnapshot } from "../config/config.js";
53import type { OpenClawConfig } from "../config/types.openclaw.js";
64import { withTempHome as withTempHomeBase } from "../plugin-sdk/test-helpers/temp-home.js";
7-import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js";
85import { resetPluginLoaderTestStateForTest } from "../plugins/loader.test-fixtures.js";
9-import { resolveOwningPluginIdsForProvider } from "../plugins/providers.js";
10-import type { MockFn } from "../test-utils/vitest-mock-fn.js";
116import { resetModelsJsonReadyCacheForTest } from "./models-config-state.js";
12-import { resolveImplicitProviders } from "./models-config.providers.implicit.js";
137148export function withModelsTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
159// Models-config tests do not exercise session persistence; skip draining
@@ -94,47 +88,6 @@ export function unsetEnv(vars: string[]) {
9488}
9589}
969097-export const COPILOT_TOKEN_ENV_VARS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
98-const COPILOT_DISCOVERY_ENV_VARS = [
99- ...COPILOT_TOKEN_ENV_VARS,
100-"OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS",
101-];
102-103-export async function withUnsetCopilotTokenEnv<T>(fn: () => Promise<T>): Promise<T> {
104-return withTempEnv(COPILOT_DISCOVERY_ENV_VARS, async () => {
105-unsetEnv(COPILOT_TOKEN_ENV_VARS);
106-process.env.OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS = "github-copilot";
107-return fn();
108-});
109-}
110-111-export function mockCopilotTokenExchangeSuccess(): MockFn {
112-const fetchMock = vi.fn().mockResolvedValue({
113-ok: true,
114-status: 200,
115-json: async () => ({
116-token: "copilot-token;proxy-ep=proxy.copilot.example",
117-expires_at: Math.floor(Date.now() / 1000) + 3600,
118-}),
119-});
120-globalThis.fetch = fetchMock as unknown as typeof fetch;
121-return fetchMock;
122-}
123-124-export async function withCopilotGithubToken<T>(
125-token: string,
126-fn: (fetchMock: MockFn) => Promise<T>,
127-): Promise<T> {
128-return withTempEnv(COPILOT_DISCOVERY_ENV_VARS, async () => {
129-process.env.COPILOT_GITHUB_TOKEN = token;
130-delete process.env.GH_TOKEN;
131-delete process.env.GITHUB_TOKEN;
132-process.env.OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS = "github-copilot";
133-const fetchMock = mockCopilotTokenExchangeSuccess();
134-return fn(fetchMock);
135-});
136-}
137-13891export const MODELS_CONFIG_IMPLICIT_ENV_VARS = [
13992"OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS",
14093"VITEST",
@@ -193,197 +146,6 @@ export const MODELS_CONFIG_IMPLICIT_ENV_VARS = [
193146"AWS_SHARED_CREDENTIALS_FILE",
194147];
195148196-const TEST_PROVIDER_ENV_TO_PROVIDER_IDS: Record<string, string[]> = {
197-AI_GATEWAY_API_KEY: ["vercel-ai-gateway"],
198-ANTHROPIC_VERTEX_PROJECT_ID: ["anthropic-vertex"],
199-ANTHROPIC_VERTEX_USE_GCP_METADATA: ["anthropic-vertex"],
200-AWS_ACCESS_KEY_ID: ["amazon-bedrock"],
201-AWS_BEARER_TOKEN_BEDROCK: ["amazon-bedrock"],
202-AWS_CONFIG_FILE: ["amazon-bedrock"],
203-AWS_DEFAULT_REGION: ["amazon-bedrock"],
204-AWS_PROFILE: ["amazon-bedrock"],
205-AWS_REGION: ["amazon-bedrock"],
206-AWS_SECRET_ACCESS_KEY: ["amazon-bedrock"],
207-AWS_SESSION_TOKEN: ["amazon-bedrock"],
208-AWS_SHARED_CREDENTIALS_FILE: ["amazon-bedrock"],
209-BYTEPLUS_API_KEY: ["byteplus"],
210-CHUTES_API_KEY: ["chutes"],
211-CHUTES_OAUTH_TOKEN: ["chutes"],
212-CLOUD_ML_REGION: ["anthropic-vertex"],
213-CLOUDFLARE_AI_GATEWAY_API_KEY: ["cloudflare-ai-gateway"],
214-COPILOT_GITHUB_TOKEN: ["github-copilot"],
215-GEMINI_API_KEY: ["google"],
216-GITHUB_TOKEN: ["github-copilot"],
217-GH_TOKEN: ["github-copilot"],
218-GOOGLE_APPLICATION_CREDENTIALS: ["anthropic-vertex"],
219-GOOGLE_CLOUD_LOCATION: ["anthropic-vertex"],
220-GOOGLE_CLOUD_PROJECT: ["anthropic-vertex"],
221-GOOGLE_CLOUD_PROJECT_ID: ["anthropic-vertex"],
222-HF_TOKEN: ["huggingface"],
223-HUGGINGFACE_HUB_TOKEN: ["huggingface"],
224-KILOCODE_API_KEY: ["kilocode"],
225-KIMI_API_KEY: ["moonshot", "kimi"],
226-KIMICODE_API_KEY: ["kimi-coding"],
227-MINIMAX_API_KEY: ["minimax"],
228-MINIMAX_OAUTH_TOKEN: ["minimax"],
229-MODELSTUDIO_API_KEY: ["chutes"],
230-MOONSHOT_API_KEY: ["moonshot"],
231-NVIDIA_API_KEY: ["nvidia"],
232-OLLAMA_API_KEY: ["ollama"],
233-OPENAI_API_KEY: ["openai"],
234-OPENROUTER_API_KEY: ["openrouter"],
235-QIANFAN_API_KEY: ["qianfan"],
236-STEPFUN_API_KEY: ["stepfun"],
237-SYNTHETIC_API_KEY: ["custom-proxy"],
238-TOGETHER_API_KEY: ["together"],
239-VENICE_API_KEY: ["venice"],
240-VLLM_API_KEY: ["vllm"],
241-VOLCANO_ENGINE_API_KEY: ["volcengine"],
242-XIAOMI_API_KEY: ["xiaomi"],
243-};
244-245-export function snapshotImplicitProviderEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
246-const source = env ?? process.env;
247-const snapshot: NodeJS.ProcessEnv = {};
248-249-for (const envVar of MODELS_CONFIG_IMPLICIT_ENV_VARS) {
250-const value = source[envVar];
251-if (value !== undefined) {
252-snapshot[envVar] = value;
253-}
254-}
255-256-// Provider discovery tests can temporarily scrub VITEST/NODE_ENV to exercise
257-// live HTTP paths. Keep the bundled plugin root pinned to the source checkout
258-// so those tests do not fall back to potentially stale dist-runtime wrappers.
259-snapshot.VITEST ??= process.env.VITEST;
260-snapshot.NODE_ENV ??= process.env.NODE_ENV;
261-snapshot.OPENCLAW_BUNDLED_PLUGINS_DIR ??=
262-resolveBundledPluginsDir({ VITEST: "true" } as NodeJS.ProcessEnv) ?? undefined;
263-264-return snapshot;
265-}
266-267-async function inferAuthProfileProviderIds(agentDir?: string): Promise<string[]> {
268-if (!agentDir) {
269-return [];
270-}
271-try {
272-const raw = await fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf8");
273-const parsed = JSON.parse(raw) as {
274-profiles?: Record<string, { provider?: string }>;
275-order?: Record<string, unknown>;
276-};
277-const providers = new Set<string>();
278-for (const providerId of Object.keys(parsed.order ?? {})) {
279-if (providerId.trim()) {
280-providers.add(providerId.trim());
281-}
282-}
283-for (const profile of Object.values(parsed.profiles ?? {})) {
284-const providerId = profile?.provider?.trim();
285-if (providerId) {
286-providers.add(providerId);
287-}
288-}
289-return [...providers];
290-} catch {
291-return [];
292-}
293-}
294-295-async function inferImplicitProviderTestPluginIds(params: {
296-agentDir?: string;
297-config?: OpenClawConfig;
298-explicitProviders?: Record<string, unknown> | null;
299-env: NodeJS.ProcessEnv;
300-workspaceDir?: string;
301-}): Promise<string[]> {
302-const providerIds = new Set<string>();
303-for (const providerId of Object.keys(params.config?.models?.providers ?? {})) {
304-if (providerId.trim()) {
305-providerIds.add(providerId.trim());
306-}
307-}
308-for (const providerId of Object.keys(params.explicitProviders ?? {})) {
309-if (providerId.trim()) {
310-providerIds.add(providerId.trim());
311-}
312-}
313-const legacyGrokApiKey =
314-params.config?.tools?.web?.search &&
315-typeof params.config.tools.web.search === "object" &&
316-"grok" in params.config.tools.web.search
317- ? (params.config.tools.web.search.grok as { apiKey?: unknown } | undefined)?.apiKey
318- : undefined;
319-if (legacyGrokApiKey !== undefined && params.config?.plugins?.entries?.xai?.enabled !== false) {
320-providerIds.add("xai");
321-}
322-for (const [envVar, mappedProviderIds] of Object.entries(TEST_PROVIDER_ENV_TO_PROVIDER_IDS)) {
323-if (!params.env[envVar]?.trim()) {
324-continue;
325-}
326-for (const providerId of mappedProviderIds) {
327-providerIds.add(providerId);
328-}
329-}
330-for (const providerId of await inferAuthProfileProviderIds(params.agentDir)) {
331-providerIds.add(providerId);
332-}
333-for (const [pluginId, entry] of Object.entries(params.config?.plugins?.entries ?? {})) {
334-if (!pluginId.trim() || entry?.enabled === false) {
335-continue;
336-}
337-const pluginConfig =
338-entry.config && typeof entry.config === "object"
339- ? (entry.config as { webSearch?: { apiKey?: unknown } })
340- : undefined;
341-if (pluginConfig?.webSearch?.apiKey !== undefined) {
342-providerIds.add(pluginId);
343-}
344-}
345-if (providerIds.size === 0) {
346-// No config/env/auth hints: keep ambient local auto-discovery focused on the
347-// one provider that is expected to probe localhost in tests.
348-return ["ollama"];
349-}
350-351-const pluginIds = new Set<string>();
352-for (const providerId of providerIds) {
353-const owningPluginIds =
354-resolveOwningPluginIdsForProvider({
355-provider: providerId,
356-config: params.config,
357-workspaceDir: params.workspaceDir,
358-env: params.env,
359-}) ?? [];
360-for (const pluginId of owningPluginIds) {
361-pluginIds.add(pluginId);
362-}
363-}
364-return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
365-}
366-367-export async function resolveImplicitProvidersForTest(
368-params: Parameters<typeof resolveImplicitProviders>[0],
369-) {
370-const env = snapshotImplicitProviderEnv(params.env);
371-const inferredPluginIds = await inferImplicitProviderTestPluginIds({
372-agentDir: params.agentDir,
373-config: params.config,
374-explicitProviders: params.explicitProviders,
375- env,
376-workspaceDir: params.workspaceDir,
377-});
378-if (inferredPluginIds.length > 0) {
379-env.OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS = inferredPluginIds.join(",");
380-}
381-return resolveImplicitProviders({
382- ...params,
383- env,
384-});
385-}
386-387149export const CUSTOM_PROXY_MODELS_CONFIG: OpenClawConfig = {
388150models: {
389151providers: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。