source dump of claude code
at main 141 lines 4.6 kB view raw
1import axios from 'axios' 2import isEqual from 'lodash-es/isEqual.js' 3import { 4 getAnthropicApiKey, 5 getClaudeAIOAuthTokens, 6 hasProfileScope, 7} from 'src/utils/auth.js' 8import { z } from 'zod' 9import { getOauthConfig, OAUTH_BETA_HEADER } from '../../constants/oauth.js' 10import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' 11import { logForDebugging } from '../../utils/debug.js' 12import { withOAuth401Retry } from '../../utils/http.js' 13import { lazySchema } from '../../utils/lazySchema.js' 14import { logError } from '../../utils/log.js' 15import { getAPIProvider } from '../../utils/model/providers.js' 16import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js' 17import { getClaudeCodeUserAgent } from '../../utils/userAgent.js' 18 19const bootstrapResponseSchema = lazySchema(() => 20 z.object({ 21 client_data: z.record(z.unknown()).nullish(), 22 additional_model_options: z 23 .array( 24 z 25 .object({ 26 model: z.string(), 27 name: z.string(), 28 description: z.string(), 29 }) 30 .transform(({ model, name, description }) => ({ 31 value: model, 32 label: name, 33 description, 34 })), 35 ) 36 .nullish(), 37 }), 38) 39 40type BootstrapResponse = z.infer<ReturnType<typeof bootstrapResponseSchema>> 41 42async function fetchBootstrapAPI(): Promise<BootstrapResponse | null> { 43 if (isEssentialTrafficOnly()) { 44 logForDebugging('[Bootstrap] Skipped: Nonessential traffic disabled') 45 return null 46 } 47 48 if (getAPIProvider() !== 'firstParty') { 49 logForDebugging('[Bootstrap] Skipped: 3P provider') 50 return null 51 } 52 53 // OAuth preferred (requires user:profile scope — service-key OAuth tokens 54 // lack it and would 403). Fall back to API key auth for console users. 55 const apiKey = getAnthropicApiKey() 56 const hasUsableOAuth = 57 getClaudeAIOAuthTokens()?.accessToken && hasProfileScope() 58 if (!hasUsableOAuth && !apiKey) { 59 logForDebugging('[Bootstrap] Skipped: no usable OAuth or API key') 60 return null 61 } 62 63 const endpoint = `${getOauthConfig().BASE_API_URL}/api/claude_cli/bootstrap` 64 65 // withOAuth401Retry handles the refresh-and-retry. API key users fail 66 // through on 401 (no refresh mechanism — no OAuth token to pass). 67 try { 68 return await withOAuth401Retry(async () => { 69 // Re-read OAuth each call so the retry picks up the refreshed token. 70 const token = getClaudeAIOAuthTokens()?.accessToken 71 let authHeaders: Record<string, string> 72 if (token && hasProfileScope()) { 73 authHeaders = { 74 Authorization: `Bearer ${token}`, 75 'anthropic-beta': OAUTH_BETA_HEADER, 76 } 77 } else if (apiKey) { 78 authHeaders = { 'x-api-key': apiKey } 79 } else { 80 logForDebugging('[Bootstrap] No auth available on retry, aborting') 81 return null 82 } 83 84 logForDebugging('[Bootstrap] Fetching') 85 const response = await axios.get<unknown>(endpoint, { 86 headers: { 87 'Content-Type': 'application/json', 88 'User-Agent': getClaudeCodeUserAgent(), 89 ...authHeaders, 90 }, 91 timeout: 5000, 92 }) 93 const parsed = bootstrapResponseSchema().safeParse(response.data) 94 if (!parsed.success) { 95 logForDebugging( 96 `[Bootstrap] Response failed validation: ${parsed.error.message}`, 97 ) 98 return null 99 } 100 logForDebugging('[Bootstrap] Fetch ok') 101 return parsed.data 102 }) 103 } catch (error) { 104 logForDebugging( 105 `[Bootstrap] Fetch failed: ${axios.isAxiosError(error) ? (error.response?.status ?? error.code) : 'unknown'}`, 106 ) 107 throw error 108 } 109} 110 111/** 112 * Fetch bootstrap data from the API and persist to disk cache. 113 */ 114export async function fetchBootstrapData(): Promise<void> { 115 try { 116 const response = await fetchBootstrapAPI() 117 if (!response) return 118 119 const clientData = response.client_data ?? null 120 const additionalModelOptions = response.additional_model_options ?? [] 121 122 // Only persist if data actually changed — avoids a config write on every startup. 123 const config = getGlobalConfig() 124 if ( 125 isEqual(config.clientDataCache, clientData) && 126 isEqual(config.additionalModelOptionsCache, additionalModelOptions) 127 ) { 128 logForDebugging('[Bootstrap] Cache unchanged, skipping write') 129 return 130 } 131 132 logForDebugging('[Bootstrap] Cache updated, persisting to disk') 133 saveGlobalConfig(current => ({ 134 ...current, 135 clientDataCache: clientData, 136 additionalModelOptionsCache: additionalModelOptions, 137 })) 138 } catch (error) { 139 logError(error) 140 } 141}