3.10 API 通信 — 多 Provider 客户端、多层重试引擎与错误恢复体系

3.10 API 通信 --- 多 Provider 客户端、多层重试引擎与错误恢复体系

对应原书 :第3章 3.4节"查询生命周期"中的 API 调用环节、第10章(附录)中的 CLI 双边架构、阶段十一(错误处理)的 API 重试与恢复

辅助源码services/api/ 目录(20个文件)

重点关注:多 Provider 客户端创建、withRetry AsyncGenerator 重试引擎、错误分类与恢复、流式 vs 非流式双路径、Prompt Cache Break 检测、会话持久化、Gateway 检测


1. 导语:API 通信层的定位

在 Claude Code 的架构中,services/api/ 目录位于整个调用链的最底层------它是系统与 Anthropic API(及其他 Provider)交互的唯一通道。查询引擎(QueryEngine)不直接调用 API,而是通过 claude.ts 中的 queryModelWithStreaming() / queryModelWithoutStreaming() 抽象函数完成 LLM 调用。这个目录同时承担了客户端管理、认证、重试、错误处理、日志遥测、缓存检测、会话持久化、文件传输、配置拉取九大职责,是整个系统与外部世界对话的"外交官"。

为什么一个 API 调用层需要 20 个文件?

因为 Claude Code 不只是"调一次 Anthropic API"那么简单。它需要:

  • 支持 4 种 Provider(Direct API / AWS Bedrock / Azure Foundry / Vertex AI)
  • 在 OAuth、API Key、Bearer Token、Azure AD、Google Auth 间灵活切换
  • 处理 529 过载时的模型降级
  • 在超长等待中保持心跳(持久重试模式)
  • 检测 Prompt Cache 中断并归因
  • 对 20+ 种错误类型给出用户友好的提示
  • 支持流式和非流式双模式
  • 将每一次 API 调用的详细信息记录到遥测系统

2. services/api/ 全景:20 文件架构

复制代码
services/api/
├── client.ts                    # 多 Provider 客户端创建(390 行)
├── claude.ts                    # LLM API 调用核心(3419 行)⭐最大
├── withRetry.ts                 # AsyncGenerator 重试引擎(823 行)
├── errors.ts                    # 错误分类与消息转换(1208 行)
├── errorUtils.ts                # SSL/连接错误工具(261 行)
├── logging.ts                   # API 日志与遥测(789 行)
├── promptCacheBreakDetection.ts # Cache Break 两阶段检测(728 行)
├── sessionIngress.ts            # 远程会话日志持久化(515 行)
├── filesApi.ts                  # 文件上传下载(749 行)
├── bootstrap.ts                 # 启动配置拉取(142 行)
├── grove.ts                     # Grove 通知系统(358 行)
├── emptyUsage.ts                # Usage 零值常量(22 行)
├── usage.ts                     # 用量查询(64 行)
├── adminRequests.ts             # 管理员请求
├── dumpPrompts.ts               # Prompt 导出
├── firstTokenDate.ts            # 首 Token 日期
├── metricsOptOut.ts             # 指标退出
├── overageCreditGrant.ts        # 超额信用授权
├── referral.ts                  # 推荐
└── ultrareviewQuota.ts          # UltraReview 配额

职责-大小矩阵

文件 行数 核心职责 关键设计
claude.ts 3419 LLM 调用、流式/非流式、Usage 累积、Cache 控制 AsyncGenerator + 累积式 Usage
errors.ts 1208 20+ 错误分类、900+ 行错误→消息转换 穷举式匹配 + 用户友好文案
withRetry.ts 823 AsyncGenerator 重试引擎、退避、529 特殊处理 yield 通信 + 持久重试
logging.ts 789 请求/错误/成功日志、Gateway 检测、OTLP 结构化日志 + 多网关指纹
filesApi.ts 749 文件上传下载、并发控制 parallelWithLimit + cursor 分页
promptCacheBreakDetection.ts 728 两阶段 Cache Break 检测与归因 12 维状态追踪
sessionIngress.ts 515 会话日志持久化、乐观并发控制 Last-Uuid header 冲突恢复
client.ts 390 多 Provider 客户端创建 工厂函数 + 环境变量路由
grove.ts 358 隐私政策通知 缓存优先非阻塞检查

3. 多 Provider 客户端架构:四路径共存

3.1 入口:getAnthropicClient() 工厂函数

typescript 复制代码
// client.ts --- 核心工厂函数
export function getAnthropicClient(): Anthropic {
  if (process.env.CLAUDE_CODE_USE_BEDROCK) {
    return getBedrockClient()        // AWS Bedrock
  }
  if (process.env.CLAUDE_CODE_USE_FOUNDRY) {
    return getFoundryClient()        // Azure Foundry
  }
  if (process.env.CLAUDE_CODE_USE_VERTEX) {
    return getVertexClient()         // Google Vertex AI
  }
  return getDirectClient()           // Anthropic Direct API (默认)
}

四个环境变量决定走哪条路径,优先级按检查顺序排列。这是典型的"策略模式"实现------每种 Provider 返回一个同样实现了 SDK Anthropic 接口的客户端。

3.2 四条路径对比

路径 环境变量 认证方式 额外配置
Direct API 默认 OAuth → API Key 退化 ANTHROPIC_BASE_URL 代理
AWS Bedrock CLAUDE_CODE_USE_BEDROCK AWS Credential (Bearer token injected) ANTHROPIC_AUTH_TOKEN / CLAUDE_CODE_BEDROCK_BASE_URL / AWS SDK credential chain
Azure Foundry CLAUDE_CODE_USE_FOUNDRY Azure AD (DefaultAzureCredential) → API Key 退化 ANTHROPIC_AUTH_TOKEN / ANTHROPIC_BASE_URL
Google Vertex CLAUDE_CODE_USE_VERTEX Google Auth (GoogleAuth) → API Key 退化 CLOUD_ML_REGION (default: us-east5)

3.3 共通构造:buildFetch() --- 请求 ID 注入

typescript 复制代码
// client.ts
export const CLIENT_REQUEST_ID_HEADER = 'x-client-request-id'

function buildFetch(): typeof globalThis.fetch {
  const originalFetch = globalThis.fetch
  return async (input, init) => {
    const headers = new Headers(init?.headers)
    if (!headers.has(CLIENT_REQUEST_ID_HEADER)) {
      headers.set(CLIENT_REQUEST_ID_HEADER, randomUUID())
    }
    return originalFetch(input, { ...init, headers })
  }
}

每个 API 请求自动携带 x-client-request-id,用于:

  • 超时场景 下与服务器日志关联(logAPIError 中的 clientRequestId 字段)
  • 重复请求去重(幂等性保护)

3.4 SDK 日志器:createStderrLogger()

typescript 复制代码
function createStderrLogger(): Logger {
  return {
    debug: (msg: string) => console.error(`[Anthropic SDK DEBUG] ${msg}`),
    warn:  (msg: string) => console.error(`[Anthropic SDK WARN]  ${msg}`),
    error: (msg: string) => console.error(`[Anthropic SDK ERROR] ${msg}`),
  }
}

使用 console.error 而非 process.stderr.write,确保在 REPL 环境下不会干扰终端渲染。

3.5 自定义 Headers:ANTHROPIC_CUSTOM_HEADERS

typescript 复制代码
function getCustomHeaders(): Record<string, string> {
  const raw = process.env.ANTHROPIC_CUSTOM_HEADERS
  if (!raw) return {}
  return raw.split('\n').reduce((acc, line) => {
    const colonIndex = line.indexOf(':')
    if (colonIndex === -1) return acc
    const key = line.slice(0, colonIndex).trim()
    const val = line.slice(colonIndex + 1).trim()
    if (key) acc[key] = val
    return acc
  }, {} as Record<string, string>)
}

支持换行分隔、冒号分割的多 header 注入,常用于企业代理场景。这与 3.9 中 envUtils.ts 的环境变量解析模式一脉相承。


4. Auth 多层认证:从 OAuth 到 Vertex 的退化链

4.1 认证层次

Claude Code 的认证不是单一方式,而是一条优先退化链

复制代码
OAuth Token (订阅用户)
    ↓ 未配置
API Key (ANTHROPIC_API_KEY / apiKeyHelper)
    ↓ 未配置
Bearer Token (ANTHROPIC_AUTH_TOKEN)
    ↓ 未配置
Azure AD (DefaultAzureCredential) --- Foundry 专用
    ↓ 无法获取
Google Auth (GoogleAuth) --- Vertex 专用
    ↓ 无法获取
→ 报错:无可用认证方式

4.2 configureApiKeyHeaders() --- 认证注入核心

typescript 复制代码
// client.ts
async function configureApiKeyHeaders(client: Anthropic): Promise<void> {
  // 1. 检查并刷新 OAuth token
  await checkAndRefreshOAuthTokenIfNeeded()

  // 2. 如果 OAuth token 可用,注入 Bearer header
  const oauthToken = getOAuthToken()
  if (oauthToken) {
    client.apiKey = 'placeholder'     // SDK 要求 apiKey 不为空
    client.defaultHeaders = {
      ...client.defaultHeaders,
      Authorization: `Bearer ${oauthToken}`,
    }
    return
  }

  // 3. 尝试 API Key
  const apiKey = await getApiKey()
  if (apiKey) {
    client.apiKey = apiKey
    return
  }

  // 4. 尝试 Bearer token(用于 Bedrock 等场景)
  const authToken = process.env.ANTHROPIC_AUTH_TOKEN
  if (authToken) {
    client.apiKey = 'placeholder'
    client.defaultHeaders = {
      ...client.defaultHeaders,
      Authorization: `Bearer ${authToken}`,
    }
  }
}

关键设计 :每次创建客户端时都调用 checkAndRefreshOAuthTokenIfNeeded(),确保 token 不过期。OAuth token 通过 ANTHROPIC_BASE_URL/v1/oauth/token 端点刷新。

4.3 附加保护 Header

typescript 复制代码
// client.ts
if (process.env.CLAUDE_CODE_ADDITIONAL_PROTECTION) {
  client.defaultHeaders['anthropic-additional-protection'] =
    process.env.CLAUDE_CODE_ADDITIONAL_PROTECTION
}

环境变量 CLAUDE_CODE_ADDITIONAL_PROTECTION 可启用额外的保护头,用于企业级安全网关场景。

4.4 disableKeepAlive() --- Stale Connection 防御

当检测到 ECONNRESET/EPIPE 错误时:

typescript 复制代码
// withRetry.ts
function isStaleConnectionError(error: any): boolean {
  const code = error?.cause?.code || error?.code
  return code === 'ECONNRESET' || code === 'EPIPE'
}

// 触发时禁用 keep-alive
if (isStaleConnectionError(error)) {
  disableKeepAlive()  // 下次请求使用 Connection: close
}

5. withRetry:AsyncGenerator 重试引擎深度剖析

5.1 核心签名

typescript 复制代码
// withRetry.ts
export async function* withRetry<T>(
  operation: () => Promise<T>,                      // 要重试的操作
  options: {
    maxRetries?: number                             // DEFAULT_MAX_RETRIES = 10
    source?: QuerySource                            // 查询源(决定 529 策略)
    onRetry?: (error: any, attempt: number) => void  // 重试回调
    signal?: AbortSignal                            // 取消信号
    isStreaming?: boolean                           // 流式模式影响超时
  }
): AsyncGenerator<SystemAPIErrorMessage, T>

返回类型是关键:AsyncGenerator<SystemAPIErrorMessage, T> ------ 这意味着重试过程中可能 yield 系统消息 (如"API 暂时不可用,正在重试..."),最终 return 结果 T

5.2 重试常量

常量 说明
DEFAULT_MAX_RETRIES 10 默认最大重试次数
MAX_529_RETRIES 3 529 过载最大重试次数(触发模型降级)
BASE_DELAY_MS 500 指数退避基础延迟
PERSISTENT_MAX_BACKOFF_MS 300000 (5min) 持久重试最大退避
PERSISTENT_RESET_CAP_MS 21600000 (6h) 持久重试总时间上限
HEARTBEAT_INTERVAL_MS 30000 (30s) 持久模式心跳间隔
SHORT_RETRY_THRESHOLD_MS 20000 (20s) Fast 模式短退避阈值
DEFAULT_FAST_MODE_FALLBACK_HOLD_MS 1800000 (30min) cooldown 持续时间
MIN_COOLDOWN_MS 600000 (10min) 最小 cooldown 时间

5.3 shouldRetry() --- 重试决策函数

typescript 复制代码
function shouldRetry(error: any, options: RetryOptions): RetryDecision {
  // 1. Mock 模式 → 不重试
  if (isMockTestMode()) return { retry: false }

  // 2. 持久重试模式 → 429 / 529 无条件重试
  if (isPersistentRetryEnabled() && (status === 429 || status === 529)) {
    return { retry: true, persistent: true }
  }

  // 3. CCR 模式 → 401 / 403 也重试(token 可能过期)
  if (isCcrEnabled() && (status === 401 || status === 403)) {
    return { retry: true }
  }

  // 4. overloaded_error → 重试(Anthropic API 特有)
  if (errorType === 'overloaded_error') return { retry: true }

  // 5. max_tokens 上下文溢出 → 重试并调整 maxtokens
  if (isMaxTokensContextOverflow(error)) return { retry: true, adjustMaxTokens: true }

  // 6. x-should-retry header → 按服务器指示
  const shouldRetryHeader = error?.headers?.['x-should-retry']
  if (shouldRetryHeader === 'true') return { retry: true }

  // 7. 5xx 服务端错误 → 重试
  if (status >= 500) return { retry: true }

  // 8. Connection Error → 重试
  if (isConnectionError(error)) return { retry: true }

  // 9. 408 Request Timeout → 重试
  if (status === 408) return { retry: true }

  // 10. 409 Conflict → 重试(乐观锁冲突)
  if (status === 409) return { retry: true }

  // 11. 429 速率限制 → 重试(非持久模式也重试)
  if (status === 429) return { retry: true }

  // 12. 401 + OAuth token 未撤销 → 重试(刷新 token)
  if (status === 401 && !isOAuthTokenRevokedError(error)) return { retry: true }

  // 13. 403 → 不重试
  return { retry: false }
}

设计亮点 :决策函数是纯逻辑的,不包含退避计算。退避延迟由 getRetryDelay() 单独处理,职责分离清晰。

5.4 自定义错误类

typescript 复制代码
// withRetry.ts
class CannotRetryError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'CannotRetryError'
  }
}

class FallbackTriggeredError extends Error {
  constructor(message: string, public readonly fallbackModel: string) {
    super(message)
    this.name = 'FallbackTriggeredError'
  }
}
  • CannotRetryError:用于 403、invalid_api_key、prompt_too_long 等不可重试场景
  • FallbackTriggeredError:529 连续 3 次后触发模型降级,携带降级目标模型名

6. 529 过载错误的特殊处理:模型降级与持久重试

6.1 前台查询源白名单

typescript 复制代码
// withRetry.ts
const FOREGROUND_529_RETRY_SOURCES = new Set([
  'repl_main_thread',
  'sdk',
  'agent:*',           // 所有 agent 子任务
  'compact',
  'hook_agent',
  'auto_mode',
  'bash_classifier',
])

非前台查询源(摘要生成、标题提取等)在 529 时 立即放弃不重试,因为:

  • 这些任务是"锦上添花"而非核心功能
  • 放弃它们可以让 token 预算更充裕地分配给主任务

6.2 模型降级触发

typescript 复制代码
// withRetry.ts
if (consecutive529s >= MAX_529_RETRIES) {
  throw new FallbackTriggeredError(
    `Model ${currentModel} overloaded, falling back to ${fallbackModel}`,
    fallbackModel
  )
}

MAX_529_RETRIES = 3:连续 3 次 529 后触发降级。降级目标由 get3PModelFallbackSuggestion() 确定(在 errors.ts 中)。

6.3 持久重试模式

typescript 复制代码
// withRetry.ts
function isPersistentRetryEnabled(): boolean {
  return (
    !!process.env.CLAUDE_CODE_UNATTENDED_RETRY &&
    featureFlagEnabled('UNATTENDED_RETRY')
  )
}

环境变量 CLAUDE_CODE_UNATTENDED_RETRY 加上 GrowthBook feature flag UNATTENDED_RETRY 双重开关。

持久重试行为

  • 429 / 529 无限重试,不计数
  • 最高退避 5 分钟(PERSISTENT_MAX_BACKOFF_MS
  • 上限 6 小时(PERSISTENT_RESET_CAP_MS)后放弃
  • 每 30 秒打印心跳到 stderr(HEARTBEAT_INTERVAL_MS
  • 心跳格式:⏳ Waiting for API (attempt N, elapsed H:MM:SS)...

这个模式专为无人值守的长时间任务设计(如 CI/CD pipeline 中的自动代码生成),确保即使 API 临时过载也不会导致任务失败。


7. Fast 模式重试策略:短退避保持缓存,长退避触发 cooldown

Fast 模式是 Claude Code 的加速特性------通过简化的系统提示和乐观的缓存策略来加速响应。当 Fast 模式下的 API 调用遇到速率限制时,重试策略需要特殊处理:

typescript 复制代码
// withRetry.ts
function getFastModeRetryStrategy(retryAfterMs: number): FastModeStrategy {
  if (retryAfterMs < SHORT_RETRY_THRESHOLD_MS) {
    // 短退避:保持 Fast 模式重试,保留缓存收益
    return { strategy: 'retain_fast', cooldown: false }
  }

  // 长退避:触发 cooldown,切换到标准速度
  // 因为长时间等待后缓存可能已失效
  return {
    strategy: 'cooldown',
    cooldown: true,
    cooldownDuration: Math.max(
      MIN_COOLDOWN_MS,
      Math.min(retryAfterMs, DEFAULT_FAST_MODE_FALLBACK_HOLD_MS)
    )
  }
}

决策逻辑

retry-after < 20s ≥ 20s
策略 保持 Fast 模式 cooldown 切换
理由 缓存仍有效 缓存可能过期
cooldown 时长 0 max(10min, min(retry-after, 30min))

8. 指数退避与 jitter 算法

typescript 复制代码
// withRetry.ts
function getRetryDelay(attempt: number, response?: Response): number {
  // 1. 优先使用 retry-after header
  const retryAfter = response?.headers?.get('retry-after')
  if (retryAfter) {
    const seconds = parseInt(retryAfter, 10)
    if (!isNaN(seconds)) return seconds * 1000
    // 也支持 HTTP-date 格式(但不常见)
    const dateMs = Date.parse(retryAfter)
    if (!isNaN(dateMs)) return Math.max(0, dateMs - Date.now())
  }

  // 2. 没有 retry-after → 指数退避 + jitter
  const baseMs = BASE_DELAY_MS * Math.pow(2, attempt - 1)  // 指数增长
  const jitter = Math.random() * 0.5 - 0.25                  // ±25%
  const delay = baseMs + baseMs * jitter
  return Math.min(delay, 32_000)                              // 上限 32 秒
}

退避序列(近似,含 jitter):

尝试次数 基础延迟 +25% jitter 范围
attempt 1 500ms 375ms ~ 625ms
attempt 2 1000ms 750ms ~ 1250ms
attempt 3 2000ms 1500ms ~ 2500ms
attempt 4 4000ms 3000ms ~ 5000ms
... ... ...
attempt 7+ 32000ms (cap) 24000ms ~ 32000ms

9. Max Tokens 上下文溢出自动修复

typescript 复制代码
// withRetry.ts
function parseMaxTokensContextOverflowError(error: any): {
  currentTokens: number
  maxTokens: number
  limit: number
} | null {
  const message = error?.error?.message || error?.message || ''
  // 匹配:"input length and max_tokens (X) exceed context limit (Y)"
  const match = message.match(
    /input length .*?(\d+).*?exceed context limit.*?(\d+)/
  )
  if (!match) return null
  return {
    currentTokens: parseInt(match[1], 10),
    limit: parseInt(match[2], 10),
    maxTokens: parseInt(match[1], 10), // 从消息中提取
  }
}

当 400 错误包含上下文溢出信息时,系统自动:

  1. 解析出当前 token 数和上下文限制
  2. 计算可用的 token 余量
  3. 动态设置 maxTokensOverride 降低 max_tokens
  4. 以调整后的参数重试

这是自动修复机制------不需要用户手动调整任何参数。


10. 错误分类系统:20+ 精细错误类型

10.1 classifyAPIError() 完整分类

typescript 复制代码
// errors.ts
function classifyAPIError(error: any): APIErrorType {
  // 1. 用户中断
  if (error?.name === 'AbortError' || error?.code === 'ABORT_ERR')
    return 'aborted'

  // 2. 超时(API 侧 + 客户端侧)
  if (isTimeoutError(error))
    return 'api_timeout'

  // 3. 重复 529 --- 模型降级已触发
  if (isFallbackTriggered(error))
    return 'repeated_529'

  // 4. 529 过载
  if (status === 529)
    return 'server_overload'

  // 5. 429 速率限制
  if (status === 429)
    return 'rate_limit'

  // 6. Prompt 过长
  if (isPromptTooLong(error))
    return 'prompt_too_long'

  // 7. PDF 过大
  if (isPdfTooLarge(error))
    return 'pdf_too_large'

  // 8. 图片过大
  if (isImageTooLarge(error))
    return 'image_too_large'

  // 9. tool_use/tool_result 不匹配
  if (isToolUseMismatch(error))
    return 'tool_use_mismatch'

  // 10. 无效模型
  if (isInvalidModel(error))
    return 'invalid_model'

  // 11. 信用余额不足
  if (isCreditBalanceLow(error))
    return 'credit_balance_low'

  // 12. 无效 API Key
  if (isInvalidApiKey(error))
    return 'invalid_api_key'

  // 13. Token 被撤销
  if (isTokenRevoked(error))
    return 'token_revoked'

  // 14. OAuth 组织不允许
  if (isOauthOrgNotAllowed(error))
    return 'oauth_org_not_allowed'

  // 15. Auth 错误(通用)
  if (status === 401 || status === 403)
    return 'auth_error'

  // 16. Bedrock 模型访问拒绝
  if (isBedrockModelAccessError(error))
    return 'bedrock_model_access'

  // 17. 服务器错误(5xx 非 529)
  if (status >= 500)
    return 'server_error'

  // 18. 客户端错误(4xx 非已处理)
  if (status >= 400)
    return 'client_error'

  // 19. SSL/TLS 证书错误
  if (isSslError(error))
    return 'ssl_cert_error'

  // 20. 连接错误(DNS/TCP)
  if (isConnectionError(error))
    return 'connection_error'

  // 21. 未知错误
  return 'unknown'
}

10.2 设计原则

  • 从具体到通用 :先检查最具体的错误(如 tool_use_mismatch),再检查通用的 HTTP 状态码
  • 可操作性强:每种错误类型都对应一条具体的用户消息,不出现模糊的"出现了一个错误"提示
  • Provider 感知 :Bedrock 专属错误(如 bedrock_model_access)与通用错误分开处理

11. getAssistantMessageFromError:900+ 行错误转消息

这是 services/api/ 目录中最大的单个函数(约 900 行),将各种 API 错误转换为用户友好的 AssistantMessage

11.1 函数结构

typescript 复制代码
// errors.ts
export function getAssistantMessageFromError(
  error: any,
  options: ErrorMessageOptions
): AssistantMessage {
  const type = classifyAPIError(error)

  switch (type) {
    case 'aborted':
      return abortMessage(options)

    case 'api_timeout':
      return timeoutMessage(error, options)

    case 'repeated_529':
      return repeated529Message(error, options)

    case 'rate_limit':
      return rateLimitMessage(error, options)

    case 'prompt_too_long': {
      const { inputTokens, outputTokens } = parsePromptTooLongTokenCounts(error)
      const gap = getPromptTooLongTokenGap(error)
      return promptTooLongMessage(inputTokens, outputTokens, gap, options)
    }

    case 'credit_balance_low':
      return creditBalanceLowMessage(options)

    case 'invalid_api_key':
      return invalidApiKeyMessage(options)

    case 'token_revoked':
      return tokenRevokedMessage(options)

    case 'oauth_org_not_allowed':
      return oauthOrgNotAllowedMessage(options)

    case 'auth_error':
      return authErrorMessage(error, options)

    case 'ssl_cert_error': {
      const sslHint = getSSLErrorHint(error)
      return sslCertErrorMessage(sslHint, options)
    }

    case 'connection_error': {
      const connDetails = extractConnectionErrorDetails(error)
      return connectionErrorMessage(connDetails, options)
    }

    case 'server_error':
      return serverErrorMessage(error, options)

    case 'bedrock_model_access':
      return bedrockModelAccessMessage(error, options)

    // ... 更多 case

    default:
      return unknownErrorMessage(error, options)
  }
}

11.2 错误消息示例

错误类型 用户消息文案(简化)
prompt_too_long "The prompt is too long (X tokens). Try using /compact or starting a new conversation."
credit_balance_low "Your credit balance is too low. Add credits link"
invalid_api_key "Invalid API key. Run /login or set ANTHROPIC_API_KEY."
rate_limit "Rate limited. Retrying in X seconds..."
server_overload "Server overloaded (529). Switching to fallback model..."
ssl_cert_error "SSL error. If using a corporate proxy, set NODE_EXTRA_CA_CERTS."

11.3 Prompt Too Long 的 Token 计数解析

typescript 复制代码
// errors.ts
function parsePromptTooLongTokenCounts(error: any): {
  inputTokens: number
  outputTokens: number
} {
  // 从 400 错误消息中解析
  // "prompt is too long: X tokens > Y maximum"
  const message = error?.error?.message || error?.message || ''
  const match = message.match(/(\d+)\s+tokens?\s*[>≥]\s*(\d+)/)
  return {
    inputTokens: match ? parseInt(match[1], 10) : 0,
    outputTokens: match ? parseInt(match[2], 10) : 0,
  }
}

function getPromptTooLongTokenGap(error: any): number {
  const { inputTokens, outputTokens } = parsePromptTooLongTokenCounts(error)
  return inputTokens - outputTokens  // 需要压缩的 token 数
}

11.4 第三方模型降级建议链

typescript 复制代码
// errors.ts
function get3PModelFallbackSuggestion(model: string): string | null {
  const fallbackMap: Record<string, string> = {
    'claude-opus-4-6-20251101': 'claude-opus-4-1-20250805',
    'claude-sonnet-4-6-20251101': 'claude-sonnet-4-5-20250929',
    'claude-sonnet-4-5-20250929': 'claude-sonnet-4-20250514',
    'claude-haiku-4-5-20251001': 'claude-haiku-3-5-20241022',
  }
  return fallbackMap[model] || null
}

降级遵循"大版本 → 小版本"原则:Opus 4.6 → 4.1、Sonnet 4.6 → 4.5 → 4、Haiku 4.5 → 3.5。


12. 流式 vs 非流式双路径

12.1 两条调用链

复制代码
queryModelWithStreaming() [AsyncGenerator]
    └── withRetry(() => executeStreamingRequest())
            └── client.messages.stream()         // SDK 流式 API
                    └── for await (event of stream)
                            └── yield chunk
                            └── updateUsage()     // 累积式更新

queryModelWithoutStreaming() [Promise]
    └── withRetry(() => executeNonStreamingRequest())
            └── client.messages.create()         // SDK 非流式 API
                    └── return { message, usage }

12.2 流式 Usage 累积

typescript 复制代码
// claude.ts --- updateUsage()
function updateUsage(current: NonNullableUsage, delta: PartialUsage): NonNullableUsage {
  // ⚠️ input 相关字段仅在 > 0 时更新
  // 原因:message_delta 事件的 usage.input_tokens 可能为 0
  // 如果直接覆盖,会丢失 message_start 中的值
  if (delta.input_tokens > 0) {
    current.input_tokens = delta.input_tokens
  }
  if (delta.cache_creation_input_tokens > 0) {
    current.cache_creation_input_tokens = delta.cache_creation_input_tokens
  }
  if (delta.cache_read_input_tokens > 0) {
    current.cache_read_input_tokens = delta.cache_read_input_tokens
  }

  // output 字段始终更新(累加)
  current.output_tokens = delta.output_tokens

  return current
}

Anthropic 流式 API 的 usage 是累积值 (不是增量),但 message_delta 事件的 input_tokens 字段为 0。> 0 的守卫条件防止了信息丢失。

12.3 NonNullableUsage 类型

typescript 复制代码
// emptyUsage.ts
export const EMPTY_USAGE: NonNullableUsage = {
  input_tokens: 0,
  output_tokens: 0,
  cache_creation_input_tokens: 0,
  cache_read_input_tokens: 0,
  server_tool_use: 0,
  cache_creation: { ephemeral_5m_input_tokens: 0, ephemeral_1h_input_tokens: 0 },
  service_tier: 'standard',
  inference_geo: null,
  iterations: 0,
  speed: null,
}

共 10 个字段,涵盖 Anthropic API 的所有 usage 维度。其中 server_tool_use / cache_creation / service_tier / inference_geo / iterations / speed 是非标准扩展字段。

12.4 非流式限制

参数 说明
MAX_NON_STREAMING_TOKENS 64,000 非流式最大 token 数
远程超时 120s getNonstreamingFallbackTimeoutMs()
本地超时 300s 本地 Provider(Bedrock 等)
Thinking Budget 调整 自动降低 adjustParamsForNonStreaming()

13. Prompt Cache Break 检测:两阶段归因系统

13.1 检测系统架构

复制代码
Phase 1 (调用前)                     Phase 2 (调用后)
┌──────────────────┐                ┌─────────────────────────┐
│ recordPromptState │                │ checkResponseForCacheBreak│
│ 记录 12 维状态    │                │ 验证 + 归因              │
└────────┬─────────┘                └──────────┬──────────────┘
         │                                     │
    PreviousState                         compare + diff
         │                                     │
         └─────────────────────────────────────┘

13.2 12 维状态追踪

typescript 复制代码
// promptCacheBreakDetection.ts
type PreviousState = {
  systemHash: string            // 系统提示 hash
  toolsHash: string             // 工具定义 hash
  cacheControlHash: string      // cache_control 断点 hash
  toolNames: string[]           // 工具名列表
  perToolHashes: Record<string, string>  // 每个工具的 hash
  systemCharCount: number       // 系统消息字符数
  model: string                 // 模型名
  fastMode: boolean             // Fast 模式状态
  globalCacheStrategy: 'tool_based' | 'system_prompt' | 'none'
  betas: string[]               // Beta 功能列表
  autoModeActive: boolean       // 自动模式状态
  isUsingOverage: boolean       // Overage 模式
  cachedMCEnabled: boolean      // MCP 缓存
  effortValue?: number          // Effort 值
  extraBodyHash?: string        // 额外 body hash
}

13.3 PendingChanges 变化检测

typescript 复制代码
type PendingChanges = {
  systemChanged: boolean
  toolsChanged: boolean
  cacheControlChanged: boolean
  toolNamesChanged: boolean
  perToolHashesChanged: boolean
  systemCharCountChanged: boolean
  modelChanged: boolean
  fastModeChanged: boolean
  globalCacheStrategyChanged: boolean
  betasChanged: boolean
  autoModeActiveChanged: boolean
  isUsingOverageChanged: boolean
  cachedMCEnabledChanged: boolean
  effortValueChanged: boolean
  extraBodyHashChanged: boolean
}

13.4 归因算法

typescript 复制代码
// promptCacheBreakDetection.ts
function checkResponseForCacheBreak(
  response: Usage,
  previous: PreviousState
): CacheBreakReport | null {
  // 只有 cache_read 下降 ≥ 2000 token 才报告
  if (cacheMissTokens < MIN_CACHE_MISS_TOKENS) return null

  // 归因:逐一比较 12 个维度
  const causes: string[] = []

  if (changes.systemChanged) causes.push('system_prompt_changed')
  if (changes.toolsChanged) causes.push('tools_definition_changed')
  if (changes.toolNamesChanged) causes.push('tool_names_changed')
  // ... 更多原因

  if (causes.length === 0) {
    // 全维度无变化但 cache miss → 可能是 TTL 过期
    const is5mExpiry = elapsed < CACHE_TTL_5MIN_MS
    const is1hExpiry = elapsed < CACHE_TTL_1HOUR_MS
    if (is5mExpiry) causes.push('cache_ttl_5m_expired')
    else if (is1hExpiry) causes.push('cache_ttl_1h_expired')
    else causes.push('cache_ttl_unknown_expired')
  }

  return { cacheMissTokens, causes }
}

13.5 1h Cache TTL 资格判断

typescript 复制代码
// claude.ts
function should1hCacheTTL(): boolean {
  // 1. Provider 检查:Bedrock 环境变量直通
  if (process.env.CLAUDE_CODE_USE_BEDROCK) return true

  // 2. 用户资格检查:ant 用户或非 overage 订阅用户
  if (isAntUser() || (isSubscriber() && !isUsingOverage())) return true

  // 3. GrowthBook allowlist 模式匹配
  if (gbAllowlistPatternMatch()) return true

  return false
}

三层条件,按优先级检查。资格在 session 内 latched(锁定),防止 mid-session 翻转导致缓存行为不一致。

13.6 Cache 基线重置

typescript 复制代码
// 在压缩后调用
notifyCompaction()
// 在手动删除缓存后调用
notifyCacheDeletion()

两个函数将内部 previous state 重置,确保后续检测从新的基线开始。


14. API 日志与遥测:Gateway 检测与 OTLP

14.1 日志体系

函数 调用时机 记录内容
logAPIQuery() 请求前 model / messagesLength / temperature / betas / permissionMode / querySource
logAPIError() 错误时 errorType / gateway / clientRequestId / connectionDetails / teleportInfo
logAPISuccess() 成功时 usage / duration / ttft / costUSD / gateway / cacheStrategy / contentLengths
logAPISuccessAndDuration() 成功时(补充) duration 单独的 timing 日志

14.2 7 种 AI Gateway 检测

typescript 复制代码
// logging.ts
function detectGateway(response: Response): GatewayInfo | null {
  const headers = response.headers

  // 按 header 指纹 + host 后缀双重识别
  const gateways: GatewayFingerprint[] = [
    { name: 'litellm',               header: 'x-litellm-version' },
    { name: 'helicone',              header: 'helicone-id' },
    { name: 'portkey',               header: 'x-portkey-trace-id' },
    { name: 'cloudflare-ai-gateway', header: 'cf-aig-cache-status' },
    { name: 'kong',                  header: 'x-kong-proxy-latency' },
    { name: 'braintrust',            header: 'x-bt-request-id' },
    { name: 'databricks',            hostSuffix: '.databricks.com' },
  ]

  for (const gw of gateways) {
    if (gw.header && headers.get(gw.header)) return { name: gw.name }
    if (gw.hostSuffix && url.includes(gw.hostSuffix)) return { name: gw.name }
  }

  return null
}

14.3 OTLP 遥测事件

两种 OTLP 事件:

  • api_error:错误时触发,携带 errorType / gateway / statusCode
  • api_request:成功时触发,携带 usage / duration / model / cost

14.4 Beta Tracing

typescript 复制代码
// logging.ts
function extractBetaTracingData(response: any): BetaTracingData {
  return {
    modelOutput: response?.content?.[0]?.text?.slice(0, 500),      // 截断
    thinkingOutput: extractThinkingOutput(response),
    hasToolCall: response?.content?.some(isToolUseBlock) ?? false,
  }
}

Beta Tracing 记录模型输出和 thinking 输出的前 500 字符,用于产品分析。hasToolCall 用于统计工具调用率。


15. Session Ingress:乐观并发控制的会话持久化

15.1 架构

复制代码
appendSessionLog()
    └── Promise 链(per-session 串行)
        └── sequential(sessionId, () => appendSessionLogImpl())
            └── 乐观并发控制
                ├── 成功 → 保存返回的 UUID
                └── 409 → 采纳服务器 UUID 重试

15.2 乐观并发控制实现

typescript 复制代码
// sessionIngress.ts
async function appendSessionLogImpl(
  sessionId: string,
  events: SessionEvent[],
  lastUuid: string | null
): Promise<string> {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...(lastUuid ? { 'Last-Uuid': lastUuid } : {}),  // CAS token
      },
      body: JSON.stringify({ events }),
    })

    // 成功:服务器返回新的 UUID
    return response.headers.get('X-Uuid') || ''

  } catch (error) {
    if (error.status === 409) {
      // 冲突:采纳服务器的 UUID
      const serverUuid = error.headers?.['x-uuid']
      if (serverUuid) {
        // 重试:用服务器的 UUID 重新发送
        return appendSessionLogImpl(sessionId, events, serverUuid)
      }
    }
    throw error
  }
}

关键设计Last-Uuid header 充当 CAS (Compare-And-Swap) token。客户端发送最后一次确认的 UUID,服务器检查是否匹配。不匹配返回 409,客户端采纳服务器的 X-Uuid 后重试。

15.3 Per-Session 串行写入

typescript 复制代码
const sessionQueues = new Map<string, Promise<void>>()

function sequential(sessionId: string, fn: () => Promise<void>): Promise<void> {
  const prev = sessionQueues.get(sessionId) || Promise.resolve()
  const next = prev.then(fn).catch(() => {})  // 错误不阻塞后续
  sessionQueues.set(sessionId, next)
  return next
}

每个会话串行写入,防止并发写竞争。但不同会话之间是并行的。

15.4 Teleport Events 分页获取

typescript 复制代码
// sessionIngress.ts
async function getTeleportEvents(sessionId: string): Promise<SessionEvent[]> {
  const events: SessionEvent[] = []
  let cursor: string | null = null
  let pageCount = 0
  const MAX_PAGES = 100

  do {
    const response = await fetch(
      `/v2/sessions/${sessionId}/events?cursor=${cursor}&limit=100`
    )
    const data = await response.json()
    events.push(...data.events)
    cursor = data.next_cursor
    pageCount++
  } while (cursor && pageCount < MAX_PAGES)

  return events
}

Cursor-based 分页,每页 100 条,上限 100 页(最多 10,000 条事件)。


16. Files API:并发限制的上传下载

16.1 下载:并行 + 并发限制

typescript 复制代码
// filesApi.ts
async function downloadSessionFiles(
  fileIds: string[],
  outputDir: string
): Promise<DownloadResult[]> {
  return parallelWithLimit(
    fileIds.map(id => () => downloadAndSaveFile(id, outputDir)),
    DEFAULT_CONCURRENCY  // 默认 5
  )
}

function parallelWithLimit<T>(
  tasks: (() => Promise<T>)[],
  limit: number
): Promise<T[]> {
  const results: T[] = new Array(tasks.length)
  let index = 0

  async function worker() {
    while (index < tasks.length) {
      const currentIndex = index++
      results[currentIndex] = await tasks[currentIndex]()
    }
  }

  return Promise.all(Array.from({ length: limit }, () => worker()))
    .then(() => results)
}

16.2 上传:Multipart + 500MB 上限

typescript 复制代码
async function uploadFile(
  filePath: string,
  options?: UploadOptions
): Promise<UploadResult> {
  const stat = await fs.stat(filePath)
  if (stat.size > 500 * 1024 * 1024) {
    throw new Error('File exceeds 500MB limit')
  }

  const form = new FormData()
  form.append('file', createReadStream(filePath))
  form.append('purpose', options?.purpose || 'user_data')

  return fetch('/v1/files', { method: 'POST', body: form })
}

16.3 路径遍历防御

typescript 复制代码
function buildDownloadPath(outputDir: string, filename: string): string {
  const resolved = path.resolve(outputDir, filename)
  if (!resolved.startsWith(path.resolve(outputDir))) {
    throw new Error(`Path traversal detected: ${filename}`)
  }
  return resolved
}

防止文件名为 ../../../etc/passwd 的攻击。


17. Bootstrap API:启动时配置拉取

typescript 复制代码
// bootstrap.ts
async function fetchBootstrapAPI(): Promise<BootstrapData | null> {
  try {
    const response = await withOAuth401Retry(() =>
      fetch(`${API_BASE}/v1/bootstrap`)
    )
    if (!response.ok) return null
    return response.json()
  } catch {
    return null  // 启动失败不阻塞 CLI
  }
}

async function fetchBootstrapData(): Promise<void> {
  const data = await fetchBootstrapAPI()
  if (!data) return

  // 持久化到全局配置
  setGlobalConfig({
    clientDataCache: data.client_data,
    additionalModelOptionsCache: data.additional_model_options,
  })
}

设计原则 :启动时异步拉取,失败不阻塞 CLI 启动。OAuth 用户通过 withOAuth401Retry 包装,API Key 用户直接请求。


18. Grove 通知系统:隐私政策更新的非阻塞推送

18.1 非阻塞检测

typescript 复制代码
// grove.ts
async function isQualifiedForGrove(): Promise<boolean> {
  // 缓存优先:从 Statsig 检查
  const cached = getGroveCache()
  if (cached !== undefined) return cached

  // 异步非阻塞获取
  const config = await getGroveNoticeConfig()
  if (!config) return false

  // 检查用户是否已查看
  const settings = await getGroveSettings()
  if (settings.lastViewedAt >= config.publishedAt) return false

  // 检查 grace period
  const now = Date.now()
  if (now < config.publishedAt + config.gracePeriod) return false

  return true
}

18.2 非交互模式处理

typescript 复制代码
function checkGroveForNonInteractive(): void {
  if (!isQualifiedForGroveSync()) return

  const config = getGroveConfigSync()
  const now = Date.now()

  if (now < config.publishedAt + config.gracePeriod) {
    // Grace period: 显示信息消息,不阻塞
    console.warn(config.infoMessage)
  } else {
    // 过期:退出并提示用户
    console.error(config.expiredMessage)
    process.exit(1)
  }
}

设计意图:隐私政策更新是合规要求,不能忽略。但在交互模式中,通过 grace period 给用户缓冲时间;在 non-interactive 模式中,过期后强制退出确保合规。


19. 设计模式提炼

19.1 策略模式 --- 多 Provider

typescript 复制代码
// 每次调用 getAnthropicClient() 返回特定 Provider 的客户端
// 但上层代码无需关心具体是哪个 Provider
const client = getAnthropicClient()
const response = await client.messages.create({ ... })

19.2 AsyncGenerator 控制反转 --- withRetry

typescript 复制代码
// 调用方通过 for await 获取系统消息,最终获得结果
for await (const msg of withRetry(operation)) {
  // msg 是 SystemAPIErrorMessage --- 可以展示给用户
  console.log(msg.text)
}
// 循环结束后 result 可用

19.3 CAS 乐观并发 --- Session Ingress

复制代码
客户端记录 lastUuid → 发送请求带 Last-Uuid header
  ├── 200: 匹配,更新 lastUuid
  └── 409: 冲突,采纳服务器的 X-Uuid → 重试

19.4 累积式 Usage --- 流式 API

typescript 复制代码
// input 字段使用 > 0 守卫,防止 message_delta 的 0 覆盖真实值
if (delta.input_tokens > 0) {
  current.input_tokens = delta.input_tokens
}
// output 字段始终更新
current.output_tokens = delta.output_tokens

19.5 两阶段检测 --- Cache Break

复制代码
Phase 1: recordPromptState() --- 在调用前快照
Phase 2: checkResponseForCacheBreak() --- 调用后验证并归因

20. 原书对照验证

书中描述 源码位置 验证结果
"API 调用通过 Anthropic SDK 完成" client.ts:getAnthropicClient() ✅ 使用官方 @anthropic-ai/sdk,工厂函数创建
"流式响应通过 AsyncGenerator 模式实现" claude.ts:queryModelWithStreaming() async function* 返回 AsyncGenerator
"重试机制包含指数退避" withRetry.ts:getRetryDelay() BASE_DELAY_MS * 2^(attempt-1) + 25% jitter
"API 错误有分类处理" errors.ts:classifyAPIError() ✅ 20+ 种错误类型
"成本通过 Usage 对象追踪" claude.ts:updateUsage() / emptyUsage.ts:EMPTY_USAGE ✅ 10 字段 NonNullableUsage
"Prompt Cache 中断有检测机制" promptCacheBreakDetection.ts ✅ 两阶段 12 维检测 + 归因
"支持多个云 Provider" client.ts 四条路径 ✅ Direct API / Bedrock / Foundry / Vertex
"认证有多层退化" client.ts:configureApiKeyHeaders() ✅ OAuth → API Key → Bearer → Azure AD → Google Auth
"Gateway 检测用于日志" logging.ts:detectGateway() ✅ 7 种网关 header 指纹
"持久重试支持无人值守" withRetry.ts:isPersistentRetryEnabled() CLAUDE_CODE_UNATTENDED_RETRY + feature flag

原书对 API 通信的直接描述较少 (主要集中在第3.4节"查询生命周期"),但源码的 services/api/ 模块远比书中描述丰富------20 个文件、约 9000 行代码,构成了完整的 API 通信基础设施。这些代码在原书第11章"错误处理"中也有涉及,构成了阶段十一的前置知识。


总结services/api/ 是一个精密的外交层。它不仅仅是"调 API",而是一个集多 Provider 路由、多层认证、智能重试、错误恢复、缓存检测、会话持久化、遥测日志 于一体的完整通信系统。其中最精妙的设计是 withRetry 的 AsyncGenerator 模式------通过 yield 实现重试进度的外部可见性,使得长时间等待不再是黑盒。

相关推荐
VIP_CQCRE10 小时前
用 Ace Data Cloud 快速接入 OpenAI Chat Completion API:从基础调用到多模态应用
ai·chatgpt·openai·api·acedatacloud
记忆停留w11 小时前
从单体到微服务:Redis 协同 MySQL、Milvus、MinIO 搭建企业级RAG/AI Agent脚手架架构
大数据·人工智能·redis·微服务·ai·架构·milvus
xixixi7777714 小时前
产业全景解读:太空算力、国产芯、国产大模型、6G 空天地、AI 可信身份、后量子安全多线全面突破
人工智能·安全·ai·大模型·数据中心·通信·运营商
慌途L15 小时前
OpenSpec 深度指南:从原理到实战,让 AI 编程告别返工
ai·openspec
ShiMetaPi17 小时前
事件相机商业化落地的 “最后一道关键门槛”
人工智能·计算机视觉·ai·自动驾驶·事件相机
还是转转17 小时前
深入认识 Agent - 构建简单的 Agent 框架
ai·agent·函数调用
kiros_wang18 小时前
DeepSeek 智能效果全景展示
ai
码农阿强18 小时前
GPT-5.6 Sol/Terra/Luna 三模型全量上架StartAPI|分层选型+可运行调用代码实战
大数据·人工智能·gpt·ai·aigc
星释19 小时前
鸿蒙智能体开发实战:19.使用变量
华为·ai·harmonyos·鸿蒙·智能体