openclaw/src/agents/models-config.e2e-harness.ts
Sally O'Malley 6e20c4baa0
feat: add anthropic-vertex provider for Claude via GCP Vertex AI (#43356)
Reuse pi-ai's Anthropic client injection seam for streaming, and add
the OpenClaw-side provider discovery, auth, model catalog, and tests
needed to expose anthropic-vertex cleanly.

Signed-off-by: sallyom <somalley@redhat.com>
2026-03-20 18:48:42 -04:00

182 lines
4.8 KiB
TypeScript

import { afterEach, beforeEach, vi } from "vitest";
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
import type { OpenClawConfig } from "../config/config.js";
import type { MockFn } from "../test-utils/vitest-mock-fn.js";
import { resolveImplicitProviders } from "./models-config.providers.js";
export async function withModelsTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
return withTempHomeBase(fn, { prefix: "openclaw-models-" });
}
export function installModelsConfigTestHooks(opts?: { restoreFetch?: boolean }) {
let previousHome: string | undefined;
const originalFetch = globalThis.fetch;
beforeEach(() => {
previousHome = process.env.HOME;
});
afterEach(() => {
process.env.HOME = previousHome;
if (opts?.restoreFetch && originalFetch) {
globalThis.fetch = originalFetch;
}
});
}
export async function withTempEnv<T>(vars: string[], fn: () => Promise<T>): Promise<T> {
const previous: Record<string, string | undefined> = {};
for (const envVar of vars) {
previous[envVar] = process.env[envVar];
}
try {
return await fn();
} finally {
for (const envVar of vars) {
const value = previous[envVar];
if (value === undefined) {
delete process.env[envVar];
} else {
process.env[envVar] = value;
}
}
}
}
export function unsetEnv(vars: string[]) {
for (const envVar of vars) {
delete process.env[envVar];
}
}
export const COPILOT_TOKEN_ENV_VARS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
export async function withUnsetCopilotTokenEnv<T>(fn: () => Promise<T>): Promise<T> {
return withTempEnv(COPILOT_TOKEN_ENV_VARS, async () => {
unsetEnv(COPILOT_TOKEN_ENV_VARS);
return fn();
});
}
export function mockCopilotTokenExchangeSuccess(): MockFn {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({
token: "copilot-token;proxy-ep=proxy.copilot.example",
expires_at: Math.floor(Date.now() / 1000) + 3600,
}),
});
globalThis.fetch = fetchMock as unknown as typeof fetch;
return fetchMock;
}
export async function withCopilotGithubToken<T>(
token: string,
fn: (fetchMock: MockFn) => Promise<T>,
): Promise<T> {
return withTempEnv(["COPILOT_GITHUB_TOKEN"], async () => {
process.env.COPILOT_GITHUB_TOKEN = token;
const fetchMock = mockCopilotTokenExchangeSuccess();
return fn(fetchMock);
});
}
export const MODELS_CONFIG_IMPLICIT_ENV_VARS = [
"AI_GATEWAY_API_KEY",
"CLOUDFLARE_AI_GATEWAY_API_KEY",
"COPILOT_GITHUB_TOKEN",
"GH_TOKEN",
"GITHUB_TOKEN",
"HF_TOKEN",
"HUGGINGFACE_HUB_TOKEN",
"MINIMAX_API_KEY",
"MINIMAX_OAUTH_TOKEN",
"MOONSHOT_API_KEY",
"NVIDIA_API_KEY",
"OLLAMA_API_KEY",
"OPENCLAW_AGENT_DIR",
"OPENAI_API_KEY",
"OPENROUTER_API_KEY",
"PI_CODING_AGENT_DIR",
"QIANFAN_API_KEY",
"MODELSTUDIO_API_KEY",
"QWEN_OAUTH_TOKEN",
"QWEN_PORTAL_API_KEY",
"SYNTHETIC_API_KEY",
"TOGETHER_API_KEY",
"VOLCANO_ENGINE_API_KEY",
"BYTEPLUS_API_KEY",
"KILOCODE_API_KEY",
"KIMI_API_KEY",
"KIMICODE_API_KEY",
"GEMINI_API_KEY",
"GOOGLE_APPLICATION_CREDENTIALS",
"GOOGLE_CLOUD_LOCATION",
"GOOGLE_CLOUD_PROJECT",
"GOOGLE_CLOUD_PROJECT_ID",
"VENICE_API_KEY",
"VLLM_API_KEY",
"XIAOMI_API_KEY",
"ANTHROPIC_VERTEX_PROJECT_ID",
"CLOUD_ML_REGION",
// Avoid ambient AWS creds unintentionally enabling Bedrock discovery.
"AWS_ACCESS_KEY_ID",
"AWS_CONFIG_FILE",
"AWS_BEARER_TOKEN_BEDROCK",
"AWS_DEFAULT_REGION",
"AWS_PROFILE",
"AWS_REGION",
"AWS_SESSION_TOKEN",
"AWS_SECRET_ACCESS_KEY",
"AWS_SHARED_CREDENTIALS_FILE",
];
export function snapshotImplicitProviderEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const source = env ?? process.env;
const snapshot: NodeJS.ProcessEnv = {};
for (const envVar of MODELS_CONFIG_IMPLICIT_ENV_VARS) {
const value = source[envVar];
if (value !== undefined) {
snapshot[envVar] = value;
}
}
return snapshot;
}
export async function resolveImplicitProvidersForTest(
params: Parameters<typeof resolveImplicitProviders>[0],
) {
return await resolveImplicitProviders({
...params,
env: snapshotImplicitProviderEnv(params.env),
});
}
export const CUSTOM_PROXY_MODELS_CONFIG: OpenClawConfig = {
models: {
providers: {
"custom-proxy": {
baseUrl: "http://localhost:4000/v1",
apiKey: "TEST_KEY",
api: "openai-completions",
models: [
{
id: "llama-3.1-8b",
name: "Llama 3.1 8B (Proxy)",
api: "openai-completions",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 32000,
},
],
},
},
},
};