Prevent stale provider proxy env in direct TUI launches

Direct TUI launches read cc-haha managed settings without the desktop host's session-env injection, so a stale proxy URL could survive after switching to an Anthropic-compatible provider. The provider runtime env calculation now has one shared source of truth and CLI config loading recomputes managed provider env from the active provider index before applying it.

Constraint: Direct source-based TUI usage must keep working with .env when no desktop provider is configured
Rejected: Inject desktop runtime only from the embedded terminal shell | native terminals and source TUI launches would still read stale managed settings
Confidence: high
Scope-risk: moderate
Directive: Keep provider env derivation shared between ProviderService and CLI managedEnv loading
Tested: bun test src/server/__tests__/provider-runtime-env.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/conversation-service.test.ts
Tested: bun run check:server
Not-tested: Live provider request from embedded terminal TUI
This commit is contained in:
程序员阿江(Relakkes) 2026-05-20 00:11:22 +08:00
parent 10d75852bb
commit 3a42ca2067
4 changed files with 396 additions and 215 deletions

View File

@ -0,0 +1,105 @@
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
import * as fs from 'fs/promises'
import * as os from 'os'
import * as path from 'path'
import {
mergeActiveProviderManagedEnv,
readActiveProviderManagedEnv,
} from '../services/providerRuntimeEnv.js'
let tmpDir: string
async function writeJson(filePath: string, value: unknown): Promise<void> {
await fs.mkdir(path.dirname(filePath), { recursive: true })
await fs.writeFile(filePath, JSON.stringify(value, null, 2), 'utf-8')
}
describe('providerRuntimeEnv', () => {
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'provider-runtime-env-'))
})
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true })
})
test('derives native Anthropic provider env from the active provider index', async () => {
await writeJson(path.join(tmpDir, 'cc-haha', 'providers.json'), {
activeId: 'provider-1',
providers: [
{
id: 'provider-1',
presetId: 'custom',
name: 'Active Provider',
apiKey: 'sk-active',
authStrategy: 'auth_token',
baseUrl: 'https://api.example.com/anthropic',
apiFormat: 'anthropic',
models: {
main: 'active-main',
haiku: '',
sonnet: 'active-sonnet',
opus: '',
},
},
],
})
const env = readActiveProviderManagedEnv(tmpDir)
expect(env).toMatchObject({
ANTHROPIC_BASE_URL: 'https://api.example.com/anthropic',
ANTHROPIC_API_KEY: '',
ANTHROPIC_AUTH_TOKEN: 'sk-active',
ANTHROPIC_MODEL: 'active-main',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'active-main',
ANTHROPIC_DEFAULT_SONNET_MODEL: 'active-sonnet',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'active-main',
})
})
test('active provider env overrides stale proxy settings while preserving unrelated env', async () => {
await writeJson(path.join(tmpDir, 'cc-haha', 'providers.json'), {
activeId: 'provider-1',
providers: [
{
id: 'provider-1',
presetId: 'custom',
name: 'Sub2API',
apiKey: 'sk-sub2api',
authStrategy: 'auth_token',
baseUrl: 'https://sub2api.example.com',
apiFormat: 'anthropic',
models: {
main: 'gpt-5.5',
haiku: 'gpt-5.5',
sonnet: 'gpt-5.5',
opus: 'gpt-5.5',
},
},
],
})
const env = mergeActiveProviderManagedEnv(
{
ANTHROPIC_BASE_URL: 'http://127.0.0.1:3456/proxy',
ANTHROPIC_API_KEY: 'proxy-managed',
ANTHROPIC_MODEL: 'deepseek-v4-pro',
DISABLE_AUTOUPDATER: '1',
},
tmpDir,
)
expect(env).toMatchObject({
ANTHROPIC_BASE_URL: 'https://sub2api.example.com',
ANTHROPIC_API_KEY: '',
ANTHROPIC_AUTH_TOKEN: 'sk-sub2api',
ANTHROPIC_MODEL: 'gpt-5.5',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gpt-5.5',
ANTHROPIC_DEFAULT_SONNET_MODEL: 'gpt-5.5',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'gpt-5.5',
DISABLE_AUTOUPDATER: '1',
})
})
})

View File

@ -0,0 +1,274 @@
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,
}
}

View File

@ -17,17 +17,8 @@ import { anthropicToOpenaiResponses } from '../proxy/transform/anthropicToOpenai
import { openaiChatToAnthropic } from '../proxy/transform/openaiChatToAnthropic.js'
import { openaiResponsesToAnthropic } from '../proxy/transform/openaiResponsesToAnthropic.js'
import type { AnthropicRequest, AnthropicResponse } from '../proxy/transform/types.js'
import { PROVIDER_PRESETS } from '../config/providerPresets.js'
import { MODEL_CONTEXT_WINDOWS_ENV_KEY } from '../../utils/model/modelContextWindows.js'
import {
ATTRIBUTION_HEADER_ENV_KEY,
attributionHeaderEnvForModel,
} from './attributionHeaderPolicy.js'
import {
OPENAI_CODEX_OAUTH_FILE_ENV_KEY,
OPENAI_OFFICIAL_PROVIDER,
OPENAI_OAUTH_PROVIDER_ENV_KEY,
buildOpenAIOfficialRuntimeEnv,
isOpenAIOfficialProviderId,
} from './openaiOfficialProvider.js'
import { hahaOpenAIOAuthService } from './hahaOpenAIOAuthService.js'
@ -35,6 +26,15 @@ import {
CURRENT_PROVIDER_INDEX_SCHEMA_VERSION,
ensurePersistentStorageUpgraded,
} from './persistentStorageMigrations.js'
import {
buildProviderAuthEnv,
buildProviderManagedEnv,
getManagedEnvKeys,
getPresetAuthStrategy,
getPresetDefaultEnv,
normalizeModelMapping,
normalizeProvidersIndex,
} from './providerRuntimeEnv.js'
import type {
SavedProvider,
ProvidersIndex,
@ -47,173 +47,11 @@ import type {
ProviderAuthStrategy,
} from '../types/provider.js'
const MANAGED_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 DEFAULT_INDEX: ProvidersIndex = {
schemaVersion: CURRENT_PROVIDER_INDEX_SCHEMA_VERSION,
activeId: null,
providers: [],
}
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)
)
}
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,
}
}
function normalizeSavedProvider(provider: SavedProvider): SavedProvider {
return {
...provider,
apiFormat: provider.apiFormat ?? 'anthropic',
runtimeKind: provider.runtimeKind ?? 'anthropic_compatible',
models: normalizeModelMapping(provider.models),
}
}
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: CURRENT_PROVIDER_INDEX_SCHEMA_VERSION,
activeId,
providers,
}
}
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())),
)
}
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 ?? {}
}
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':
return {
ANTHROPIC_API_KEY: '',
...(key ? { ANTHROPIC_AUTH_TOKEN: key } : {}),
}
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' }
}
}
function getManagedEnvKeys(): string[] {
const keys = new Set<string>(MANAGED_ENV_KEYS)
for (const preset of PROVIDER_PRESETS) {
for (const key of Object.keys(preset.defaultEnv ?? {})) {
keys.add(key)
}
}
return [...keys]
}
export class ProviderService {
private static serverPort = 3456
@ -402,48 +240,10 @@ export class ProviderService {
provider: SavedProvider,
options?: { proxyPath?: string },
): Record<string, string> {
if (provider.runtimeKind === 'openai_oauth') {
return buildOpenAIOfficialRuntimeEnv()
}
const needsProxy = provider.apiFormat != null && provider.apiFormat !== 'anthropic'
const proxyPath = options?.proxyPath ?? '/proxy'
const baseUrl = needsProxy
? `http://127.0.0.1:${ProviderService.serverPort}${proxyPath}`
: provider.baseUrl
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: provider.models.main,
ANTHROPIC_DEFAULT_HAIKU_MODEL: provider.models.haiku,
ANTHROPIC_DEFAULT_SONNET_MODEL: provider.models.sonnet,
ANTHROPIC_DEFAULT_OPUS_MODEL: provider.models.opus,
...attributionHeaderEnvForModel(provider.models.main),
}
return buildProviderManagedEnv(provider, {
proxyPath: options?.proxyPath,
serverPort: ProviderService.serverPort,
})
}
async getProviderRuntimeEnv(id: string): Promise<Record<string, string>> {

View File

@ -1,6 +1,7 @@
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { isRemoteManagedSettingsEligible } from '../services/remoteManagedSettings/syncCache.js'
import { mergeActiveProviderManagedEnv } from '../server/services/providerRuntimeEnv.js'
import { clearCACertsCache } from './caCerts.js'
import { getGlobalConfig } from './config.js'
import { getClaudeConfigHomeDir, isEnvTruthy } from './envUtils.js'
@ -104,9 +105,10 @@ function getCcHahaSettingsEnv(): Record<string, string> {
const ccHahaSettings = join(getClaudeConfigHomeDir(), 'cc-haha', 'settings.json')
const raw = readFileSync(ccHahaSettings, 'utf-8')
const parsed = JSON.parse(raw) as { env?: Record<string, string> }
return normalizeLegacyDeepSeekManagedEnv(parsed.env ?? {}).env
const settingsEnv = normalizeLegacyDeepSeekManagedEnv(parsed.env ?? {}).env
return mergeActiveProviderManagedEnv(settingsEnv, getClaudeConfigHomeDir())
} catch {
return {}
return mergeActiveProviderManagedEnv({}, getClaudeConfigHomeDir())
}
}