openclaw/src/agents/claude-agent-sdk/provider-config.ts

300 lines
9.3 KiB
TypeScript

/**
* Provider configuration builders for the Claude Agent SDK.
*
* Supports multiple authentication methods:
* - Anthropic API key (ANTHROPIC_API_KEY) - when explicitly configured in moltbot.json
* - Claude Code SDK native auth (default) - inherits parent env, SDK handles credential resolution
* - z.AI subscription (via ANTHROPIC_AUTH_TOKEN)
* - OpenRouter (Anthropic-compatible API)
* - AWS Bedrock
* - Google Vertex AI
*
* For SDK native auth, we inherit the full parent process environment and let the SDK
* handle credential resolution. If the user has ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN
* set in their environment, the SDK will use them. Otherwise, it uses its native
* keychain-based OAuth flow.
*/
import type { SdkProviderConfig, SdkProviderEnv } from "./types.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
const log = createSubsystemLogger("agents/claude-agent-sdk");
/**
* Mask a token for logging - shows length and first/last 4 chars.
*/
function maskToken(token: string | undefined): string {
if (!token) return "(empty)";
if (token.length <= 12) return `${"*".repeat(token.length)} (length: ${token.length})`;
const first = token.slice(0, 4);
const last = token.slice(-4);
return `${first}...${"*".repeat(Math.min(8, token.length - 8))}...${last} (length: ${token.length})`;
}
/**
* Log the resolved provider config with masked credentials.
*/
function logProviderConfig(config: SdkProviderConfig, source: string): void {
const envKeys = config.env ? Object.keys(config.env) : [];
const maskedEnv: Record<string, string> = {};
if (config.env) {
for (const [key, value] of Object.entries(config.env)) {
if (key.includes("KEY") || key.includes("TOKEN") || key.includes("SECRET")) {
maskedEnv[key] = maskToken(value);
} else {
maskedEnv[key] = value ?? "(undefined)";
}
}
}
log.debug("[CCSDK-PROVIDER] Provider config resolved", {
source,
providerName: config.name,
envKeys,
maskedEnv,
model: config.model,
maxTurns: config.maxTurns,
});
}
/**
* Build provider config for direct Anthropic API access.
*/
export function buildAnthropicSdkProvider(apiKey: string): SdkProviderConfig {
return {
name: "Anthropic",
env: {
ANTHROPIC_API_KEY: apiKey,
},
};
}
/**
* Build provider config for Claude Code SDK native auth.
*
* This returns a minimal config with no env overrides, allowing the SDK to:
* - Inherit any ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN from parent process
* - Use its internal credential resolution (keychain → OAuth) if no env vars are set
*
* We don't try to be "smart" about unsetting env vars - if the user has auth
* env vars set in their system environment, that's their configuration choice.
*/
export function buildClaudeCliSdkProvider(): SdkProviderConfig {
log.debug("[CCSDK-PROVIDER] buildClaudeCliSdkProvider called - using SDK native auth");
const config: SdkProviderConfig = {
name: "Claude CLI (SDK native)",
env: {},
};
logProviderConfig(config, "sdk-native-auth");
return config;
}
/**
* Build provider config for z.AI subscription access.
*
* z.AI uses Anthropic-compatible API with a different base URL.
*/
export function buildZaiSdkProvider(
authToken: string,
options?: {
baseUrl?: string;
defaultModel?: string;
haikuModel?: string;
sonnetModel?: string;
opusModel?: string;
},
): SdkProviderConfig {
const env: SdkProviderEnv = {
ANTHROPIC_AUTH_TOKEN: authToken,
};
// Set base URL if provided (z.AI may use a custom endpoint)
if (options?.baseUrl) {
env.ANTHROPIC_BASE_URL = options.baseUrl;
}
// Set model tier defaults for z.AI
if (options?.haikuModel) {
env.ANTHROPIC_DEFAULT_HAIKU_MODEL = options.haikuModel;
}
if (options?.sonnetModel) {
env.ANTHROPIC_DEFAULT_SONNET_MODEL = options.sonnetModel;
}
if (options?.opusModel) {
env.ANTHROPIC_DEFAULT_OPUS_MODEL = options.opusModel;
}
const config: SdkProviderConfig = {
name: "z.AI",
env,
model: options?.defaultModel,
};
log.debug("[CCSDK-PROVIDER] Built z.AI provider config", {
hasBaseUrl: Boolean(options?.baseUrl),
baseUrl: options?.baseUrl ?? "(default)",
defaultModel: options?.defaultModel ?? "(sdk default)",
haikuModel: options?.haikuModel ?? "(sdk default)",
sonnetModel: options?.sonnetModel ?? "(sdk default)",
opusModel: options?.opusModel ?? "(sdk default)",
authTokenMasked: maskToken(authToken),
});
return config;
}
/**
* Build provider config for OpenRouter (Anthropic-compatible).
*
* OpenRouter requires explicit model names since their model IDs differ
* from Anthropic's native IDs.
*/
export function buildOpenRouterSdkProvider(
apiKey: string,
options?: {
baseUrl?: string;
defaultModel?: string;
haikuModel?: string;
sonnetModel?: string;
opusModel?: string;
},
): SdkProviderConfig {
const env: SdkProviderEnv = {
ANTHROPIC_BASE_URL: options?.baseUrl ?? "https://openrouter.ai/api/v1",
ANTHROPIC_API_KEY: apiKey,
};
// OpenRouter uses different model naming - set explicit model IDs
// Default OpenRouter model names for Anthropic models:
// - anthropic/claude-3-5-haiku-20241022
// - anthropic/claude-sonnet-4-20250514
// - anthropic/claude-opus-4-20250514
if (options?.haikuModel) {
env.ANTHROPIC_DEFAULT_HAIKU_MODEL = options.haikuModel;
}
if (options?.sonnetModel) {
env.ANTHROPIC_DEFAULT_SONNET_MODEL = options.sonnetModel;
}
if (options?.opusModel) {
env.ANTHROPIC_DEFAULT_OPUS_MODEL = options.opusModel;
}
const config: SdkProviderConfig = {
name: "OpenRouter (Anthropic-compatible)",
env,
model: options?.defaultModel,
};
log.debug("[CCSDK-PROVIDER] Built OpenRouter provider config", {
baseUrl: env.ANTHROPIC_BASE_URL,
defaultModel: options?.defaultModel ?? "(sdk default)",
haikuModel: options?.haikuModel ?? "(sdk default)",
sonnetModel: options?.sonnetModel ?? "(sdk default)",
opusModel: options?.opusModel ?? "(sdk default)",
apiKeyMasked: maskToken(apiKey),
});
return config;
}
/**
* Build provider config for AWS Bedrock.
*
* AWS credentials should be configured via standard AWS mechanisms
* (environment variables, shared credentials file, IAM role, etc.).
*/
export function buildBedrockSdkProvider(): SdkProviderConfig {
return {
name: "AWS Bedrock",
env: {
CLAUDE_CODE_USE_BEDROCK: "1",
},
};
}
/**
* Build provider config for Google Vertex AI.
*
* Google Cloud credentials should be configured via standard GCP mechanisms
* (GOOGLE_APPLICATION_CREDENTIALS, default credentials, etc.).
*/
export function buildVertexSdkProvider(): SdkProviderConfig {
return {
name: "Google Vertex AI",
env: {
CLAUDE_CODE_USE_VERTEX: "1",
},
};
}
/**
* Resolve provider configuration based on available credentials.
*
* Priority order:
* 1. Explicit API key from moltbot config (options.apiKey)
* 2. Explicit auth token from moltbot config (options.authToken) - for z.AI, etc.
* 3. SDK native auth (default) - inherits parent env, SDK handles credential resolution
*
* The SDK will inherit the full parent process environment. If auth env vars
* (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN) are set in the parent process,
* the SDK will use them. Otherwise, it falls back to its native credential
* resolution (keychain → OAuth).
*/
export function resolveProviderConfig(options?: {
apiKey?: string;
authToken?: string;
baseUrl?: string;
useCliCredentials?: boolean;
}): SdkProviderConfig {
log.debug("[CCSDK-PROVIDER] resolveProviderConfig called", {
hasApiKey: Boolean(options?.apiKey),
apiKeyMasked: maskToken(options?.apiKey),
hasAuthToken: Boolean(options?.authToken),
authTokenMasked: maskToken(options?.authToken),
baseUrl: options?.baseUrl ?? "(default)",
useCliCredentials: options?.useCliCredentials ?? true,
});
// 1. Explicit API key from moltbot config takes precedence
if (options?.apiKey) {
log.debug("[CCSDK-PROVIDER] Using explicit API key from moltbot config");
const config = buildAnthropicSdkProvider(options.apiKey);
if (options.baseUrl && config.env) {
config.env.ANTHROPIC_BASE_URL = options.baseUrl;
}
logProviderConfig(config, "explicit-api-key");
return config;
}
// 2. Explicit auth token from moltbot config (for z.AI, custom endpoints, etc.)
if (options?.authToken) {
log.debug("[CCSDK-PROVIDER] Using explicit auth token from moltbot config");
const env: SdkProviderEnv = {
ANTHROPIC_AUTH_TOKEN: options.authToken,
};
if (options.baseUrl) {
env.ANTHROPIC_BASE_URL = options.baseUrl;
}
const config: SdkProviderConfig = {
name: "Anthropic (auth token)",
env,
};
logProviderConfig(config, "explicit-auth-token");
return config;
}
// 3. Default: SDK native auth
// Let the SDK use its internal credential resolution (keychain → OAuth flow).
// We inherit the full parent process env - if the user has ANTHROPIC_API_KEY or
// ANTHROPIC_AUTH_TOKEN set, that's their configuration choice.
log.debug("[CCSDK-PROVIDER] Using SDK native auth (no explicit credentials configured)");
const cliConfig = buildClaudeCliSdkProvider();
if (options?.baseUrl && cliConfig.env) {
cliConfig.env.ANTHROPIC_BASE_URL = options.baseUrl;
}
return cliConfig;
}