| import * as fs from 'fs' |
| import * as path from 'path' |
|
|
| import { MODEL_CONTEXT_WINDOWS_ENV_KEY } from '../../utils/model/modelContextWindows.js' |
| import { PROVIDER_PRESETS } from '../config/providerPresets.js' |
| import type { |
| ApiFormat, |
| ProviderAuthStrategy, |
| ProvidersIndex, |
| SavedProvider, |
| } from '../types/provider.js' |
| import { |
| ATTRIBUTION_HEADER_ENV_KEY, |
| attributionHeaderEnvForModel, |
| } from './attributionHeaderPolicy.js' |
| import { |
| OPENAI_CODEX_OAUTH_FILE_ENV_KEY, |
| OPENAI_OAUTH_PROVIDER_ENV_KEY, |
| buildOpenAIOfficialRuntimeEnv, |
| isOpenAIOfficialProviderId, |
| } from './openaiOfficialProvider.js' |
|
|
| export const MANAGED_PROVIDER_ENV_KEYS = [ |
| 'ANTHROPIC_BASE_URL', |
| 'ANTHROPIC_API_KEY', |
| 'ANTHROPIC_AUTH_TOKEN', |
| 'ANTHROPIC_MODEL', |
| 'ANTHROPIC_DEFAULT_HAIKU_MODEL', |
| 'ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES', |
| 'ANTHROPIC_DEFAULT_SONNET_MODEL', |
| 'ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES', |
| 'ANTHROPIC_DEFAULT_OPUS_MODEL', |
| 'ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES', |
| 'CLAUDE_CODE_AUTO_COMPACT_WINDOW', |
| ATTRIBUTION_HEADER_ENV_KEY, |
| MODEL_CONTEXT_WINDOWS_ENV_KEY, |
| OPENAI_OAUTH_PROVIDER_ENV_KEY, |
| OPENAI_CODEX_OAUTH_FILE_ENV_KEY, |
| ] as const |
|
|
| const CUSTOM_PROVIDER_MODEL_CAPABILITIES = 'thinking,effort,adaptive_thinking,max_effort' |
| const AUTH_ENV_KEYS = new Set(['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN']) |
|
|
| function isRecord(value: unknown): value is Record<string, unknown> { |
| return !!value && typeof value === 'object' && !Array.isArray(value) |
| } |
|
|
| function isProviderModels(value: unknown): value is SavedProvider['models'] { |
| return ( |
| isRecord(value) && |
| typeof value.main === 'string' && |
| typeof value.haiku === 'string' && |
| typeof value.sonnet === 'string' && |
| typeof value.opus === 'string' |
| ) |
| } |
|
|
| function isSavedProvider(value: unknown): value is SavedProvider { |
| if (!isRecord(value)) return false |
| const runtimeKind = value.runtimeKind |
| return ( |
| typeof value.id === 'string' && |
| typeof value.presetId === 'string' && |
| typeof value.name === 'string' && |
| typeof value.apiKey === 'string' && |
| typeof value.baseUrl === 'string' && |
| ( |
| runtimeKind === undefined || |
| runtimeKind === 'anthropic_compatible' || |
| runtimeKind === 'openai_oauth' |
| ) && |
| isProviderModels(value.models) |
| ) |
| } |
|
|
| export function normalizeModelMapping(models: SavedProvider['models']): SavedProvider['models'] { |
| const main = models.main.trim() |
| return { |
| main, |
| haiku: models.haiku.trim() || main, |
| sonnet: models.sonnet.trim() || main, |
| opus: models.opus.trim() || main, |
| } |
| } |
|
|
| export function normalizeSavedProvider(provider: SavedProvider): SavedProvider { |
| return { |
| ...provider, |
| apiFormat: provider.apiFormat ?? 'anthropic', |
| runtimeKind: provider.runtimeKind ?? 'anthropic_compatible', |
| models: normalizeModelMapping(provider.models), |
| } |
| } |
|
|
| export function normalizeProvidersIndex(value: unknown): ProvidersIndex | null { |
| if (!isRecord(value) || !Array.isArray(value.providers)) { |
| return null |
| } |
|
|
| const { activeProviderId: legacyActiveProviderId, ...rest } = value |
| const providers = value.providers |
| .filter(isSavedProvider) |
| .map((provider) => normalizeSavedProvider(provider)) |
| const rawActiveId = |
| typeof value.activeId === 'string' |
| ? value.activeId |
| : typeof legacyActiveProviderId === 'string' |
| ? legacyActiveProviderId |
| : null |
| const activeId = rawActiveId && ( |
| providers.some((provider) => provider.id === rawActiveId) || |
| isOpenAIOfficialProviderId(rawActiveId) |
| ) |
| ? rawActiveId |
| : null |
|
|
| return { |
| ...rest, |
| schemaVersion: typeof value.schemaVersion === 'number' ? value.schemaVersion : 1, |
| activeId, |
| providers, |
| } |
| } |
|
|
| export function getPresetDefaultEnv(presetId: string): Record<string, string> { |
| return PROVIDER_PRESETS.find((preset) => preset.id === presetId)?.defaultEnv ?? {} |
| } |
|
|
| function omitAuthEnv(env: Record<string, string>): Record<string, string> { |
| return Object.fromEntries( |
| Object.entries(env).filter(([key]) => !AUTH_ENV_KEYS.has(key.toUpperCase())), |
| ) |
| } |
|
|
| export function getPresetAuthStrategy(presetId: string): ProviderAuthStrategy { |
| return PROVIDER_PRESETS.find((preset) => preset.id === presetId)?.authStrategy ?? 'auth_token' |
| } |
|
|
| function getPresetModelContextWindows(presetId: string): Record<string, number> { |
| return PROVIDER_PRESETS.find((preset) => preset.id === presetId)?.modelContextWindows ?? {} |
| } |
|
|
| export function buildProviderAuthEnv( |
| provider: SavedProvider, |
| presetDefaultEnv: Record<string, string>, |
| needsProxy: boolean, |
| ): Record<string, string> { |
| if (needsProxy) { |
| return { ANTHROPIC_API_KEY: 'proxy-managed' } |
| } |
|
|
| const strategy = provider.authStrategy ?? getPresetAuthStrategy(provider.presetId) |
| const key = provider.apiKey || presetDefaultEnv.ANTHROPIC_AUTH_TOKEN || presetDefaultEnv.ANTHROPIC_API_KEY || '' |
|
|
| switch (strategy) { |
| case 'api_key': |
| return key ? { ANTHROPIC_API_KEY: key } : {} |
| case 'auth_token': |
| case 'auth_token_empty_api_key': |
| return { |
| ANTHROPIC_API_KEY: '', |
| ...(key ? { ANTHROPIC_AUTH_TOKEN: key } : {}), |
| } |
| case 'dual_same_token': |
| return key ? { ANTHROPIC_API_KEY: key, ANTHROPIC_AUTH_TOKEN: key } : {} |
| case 'dual_dummy': |
| return { ANTHROPIC_API_KEY: 'dummy', ANTHROPIC_AUTH_TOKEN: 'dummy' } |
| } |
| } |
|
|
| export function getManagedEnvKeys(): string[] { |
| const keys = new Set<string>(MANAGED_PROVIDER_ENV_KEYS) |
| for (const preset of PROVIDER_PRESETS) { |
| for (const key of Object.keys(preset.defaultEnv ?? {})) { |
| keys.add(key) |
| } |
| } |
| return [...keys] |
| } |
|
|
| export function buildProviderManagedEnv( |
| provider: SavedProvider, |
| options?: { proxyPath?: string; serverPort?: number }, |
| ): Record<string, string> { |
| if (provider.runtimeKind === 'openai_oauth') { |
| return buildOpenAIOfficialRuntimeEnv() |
| } |
|
|
| const apiFormat: ApiFormat = provider.apiFormat ?? 'anthropic' |
| const needsProxy = apiFormat !== 'anthropic' |
| const proxyPath = options?.proxyPath ?? '/proxy' |
| const serverPort = options?.serverPort ?? 3456 |
| const baseUrl = needsProxy |
| ? `http://127.0.0.1:${serverPort}${proxyPath}` |
| : provider.baseUrl |
|
|
| const models = normalizeModelMapping(provider.models) |
| const modelContextWindows = { |
| ...getPresetModelContextWindows(provider.presetId), |
| ...(provider.modelContextWindows ?? {}), |
| } |
|
|
| const presetDefaultEnv = getPresetDefaultEnv(provider.presetId) |
| const customProviderCapabilityEnv = |
| provider.presetId === 'custom' |
| ? { |
| ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES: CUSTOM_PROVIDER_MODEL_CAPABILITIES, |
| ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES: CUSTOM_PROVIDER_MODEL_CAPABILITIES, |
| ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES: CUSTOM_PROVIDER_MODEL_CAPABILITIES, |
| } |
| : {} |
|
|
| return { |
| ...omitAuthEnv(presetDefaultEnv), |
| ...customProviderCapabilityEnv, |
| ...(provider.autoCompactWindow !== undefined && { |
| CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(provider.autoCompactWindow), |
| }), |
| ...(Object.keys(modelContextWindows).length > 0 && { |
| [MODEL_CONTEXT_WINDOWS_ENV_KEY]: JSON.stringify(modelContextWindows), |
| }), |
| ANTHROPIC_BASE_URL: baseUrl, |
| ...buildProviderAuthEnv(provider, presetDefaultEnv, needsProxy), |
| ANTHROPIC_MODEL: models.main, |
| ANTHROPIC_DEFAULT_HAIKU_MODEL: models.haiku, |
| ANTHROPIC_DEFAULT_SONNET_MODEL: models.sonnet, |
| ANTHROPIC_DEFAULT_OPUS_MODEL: models.opus, |
| ...attributionHeaderEnvForModel(models.main), |
| } |
| } |
|
|
| export function readActiveProviderManagedEnv( |
| configDir: string, |
| options?: { serverPort?: number }, |
| ): Record<string, string> | null { |
| try { |
| const raw = fs.readFileSync(path.join(configDir, 'cc-haha', 'providers.json'), 'utf-8') |
| const index = normalizeProvidersIndex(JSON.parse(raw)) |
| if (!index?.activeId) return null |
|
|
| if (isOpenAIOfficialProviderId(index.activeId)) { |
| return buildOpenAIOfficialRuntimeEnv() |
| } |
|
|
| const provider = index.providers.find((entry) => entry.id === index.activeId) |
| if (!provider) return null |
|
|
| return buildProviderManagedEnv(provider, { |
| serverPort: options?.serverPort, |
| }) |
| } catch { |
| return null |
| } |
| } |
|
|
| export function mergeActiveProviderManagedEnv( |
| settingsEnv: Record<string, string>, |
| configDir: string, |
| options?: { serverPort?: number }, |
| ): Record<string, string> { |
| const activeProviderEnv = readActiveProviderManagedEnv(configDir, options) |
| if (!activeProviderEnv) { |
| return settingsEnv |
| } |
|
|
| const cleanedEnv = { ...settingsEnv } |
| for (const key of getManagedEnvKeys()) { |
| delete cleanedEnv[key] |
| } |
| return { |
| ...cleanedEnv, |
| ...activeProviderEnv, |
| } |
| } |
|
|