职责概述

解决的问题:CC 主循环忙着处理用户对话,但还有很多事需要在后台持续做——检查订阅状态、同步团队记忆、监控 MCP 连接健康、预缓存 Prompt。这些事不能阻塞主流程,需要一套后台服务框架让它们各跑各的。

应用场景:① 用户在对话时,后台默默同步团队共享的记忆文件 ② 订阅到期前自动提醒 ③ MCP Server 断连后自动重连 ④ 定期把 Prompt 模板预热到 API 缓存,减少首次响应延迟 ⑤ 后台服务挂了不影响主对话——"fail open"原则。

一句话理解:就像手机的后台 App 刷新——你在用微信的时候,邮件在后台同步、天气在后台更新,各干各的,互不干扰。

架构设计

服务依赖关系图

API 通信 核心
services/api/*
OAuth 认证 安全
services/oauth/*
Analytics 数据
services/analytics/*
LSP 集成 扩展
services/lsp/*
Settings Sync 数据
services/settingsSync/*
Team Memory Sync 扩展
services/teamMemorySync/*
Policy Limits 安全
services/policyLimits/*
Remote Managed Settings 安全
services/remoteManagedSettings/*
Tips UI
services/tips/*
Prompt Suggestion 扩展
services/PromptSuggestion/*
Magic Docs 扩展
services/MagicDocs/*
Auto Dream 扩展
services/autoDream/*
Agent Summary 扩展
services/AgentSummary/*
Tool Use Summary 扩展
services/toolUseSummary/*
Tools 核心
services/tools/*

服务分层

基础设施服务
API (client.ts, claude.ts)
OAuth (index.ts, client.ts)
Analytics (index.ts, sink.ts)
Tools (toolExecution.ts, toolOrchestration.ts)
安全与治理服务
Policy Limits
Remote Managed Settings
Team Memory Secret Scanner
增强服务
LSP Manager
Settings Sync
Team Memory Sync
Prompt Suggestion
Tips
智能服务
Magic Docs
Auto Dream
Agent Summary
Tool Use Summary

核心数据流

API 通信流程

getAnthropicClient()
根据认证方式(API Key / OAuth / Bedrock / Vertex)创建 SDK 客户端实例
queryModelWithStreaming()
发送流式消息请求,支持 prompt caching、thinking、tool choice
withRetry()
指数退避重试(默认 5 次),处理 429/500/overloaded 等错误
Stream Events
content_block_start/delta/stop 事件流,yield 给 query loop 消费

OAuth 认证流程

startOAuthFlow()
生成 PKCE code_verifier + challenge,启动 localhost 监听器
双模式获取 Code
自动模式:浏览器回调 localhost;手动模式:用户粘贴 code
exchangeCodeForTokens()
用 code + verifier 换取 access/refresh token
fetchProfileInfo()
获取订阅类型、rate limit tier、账户信息

Forked Agent 模式(服务间共享)

调用方准备 CacheSafeParams
systemPrompt + userContext + systemContext + forkContextMessages + toolUseContext
runForkedAgent()
创建独立上下文,共享缓存前缀,限制工具权限(canUseTool 回调)
forked agent 执行
compact / session_memory / extract_memories / agent_summary 均通过此路径
结果回收 + 用量统计
提取写入路径、token 用量、缓存命中率等指标

关键类型与接口

AnalyticsSink — 事件接收器接口

// services/analytics/index.ts:72-78
export type AnalyticsSink = {
  logEvent: (eventName: string, metadata: LogEventMetadata) => void
  logEventAsync: (eventName: string, metadata: LogEventMetadata) => Promise<void>
}

OAuthService — OAuth 服务类

// services/oauth/index.ts:21-198
export class OAuthService {
  private codeVerifier: string
  async startOAuthFlow(
    authURLHandler: (url: string, automaticUrl?: string) => Promise<void>,
    options?: {
      loginWithClaudeAi?: boolean
      inferenceOnly?: boolean
      orgUUID?: string
      skipBrowserOpen?: boolean
    }
  ): Promise<OAuthTokens>
  handleManualAuthCodeInput(params: { authorizationCode: string; state: string }): void
  cleanup(): void
}

PolicyLimitsResponse — 策略限制响应

// services/policyLimits/types.ts:8-12
export const PolicyLimitsResponseSchema = lazySchema(() =>
  z.object({
    restrictions: z.record(z.string(), z.object({ allowed: z.boolean() })),
  })
)

ContextManagementConfig — API 侧上下文管理

// services/compact/apiMicrocompact.ts:35-61
export type ContextEditStrategy =
  | {
      type: 'clear_tool_uses_20250919'
      trigger?: { type: 'input_tokens'; value: number }
      keep?: { type: 'tool_uses'; value: number }
      clear_tool_inputs?: boolean | string[]
      exclude_tools?: string[]
    }
  | {
      type: 'clear_thinking_20251015'
      keep: { type: 'thinking_turns'; value: number } | 'all'
    }

LSP Server Manager 状态

// services/lsp/manager.ts:14-18
type InitializationState = 'not-started' | 'pending' | 'success' | 'failed'
// 单例模式 + generation counter 防止过期初始化
let lspManagerInstance: LSPServerManager | undefined
let initializationState: InitializationState = 'not-started'

设计模式与亮点

1. Fail Open 原则

所有网络服务(Policy Limits、Settings Sync、Team Memory Sync、Remote Managed Settings)都遵循 fail open 策略:如果获取失败且无缓存,继续运行不受限制。这是 CLI 应用的关键设计——网络不应阻断用户工作流。

// services/policyLimits/index.ts:510-526
export function isPolicyAllowed(policy: string): boolean {
  const restrictions = getRestrictionsFromCache()
  if (!restrictions) {
    if (isEssentialTrafficOnly() && ESSENTIAL_TRAFFIC_DENY_ON_MISS.has(policy)) {
      return false  // HIPAA 场景例外:fail closed
    }
    return true  // fail open: 无限制信息 = 允许
  }
  // ...
}

2. ETag + Checksum 缓存

Policy Limits 和 Team Memory Sync 都使用 checksum/ETag 做 HTTP 条件请求。客户端计算本地 checksum,发送 If-None-Match 头,服务端返回 304 时直接使用缓存。文件级缓存(policy-limits.json)在进程重启后仍然有效。

3. 后台轮询 + 单例模式

Policy Limits 和 Remote Managed Settings 都在初始化时启动 1 小时间隔的后台轮询(setInterval + .unref() 避免阻止进程退出)。轮询检测到变化时静默更新,无需用户干预。Generation counter 防止过期的异步操作覆盖最新状态。

4. 分层工具权限(canUseTool 回调)

每个 forked agent 通过 canUseTool 回调限制工具权限。Session Memory 只允许 Edit 一个文件;extractMemories 允许 Read/Grep/Glob 无限制但 Edit/Write 仅限 memory 目录;Auto Dream 复用 extractMemories 的权限模型。这种最小权限设计确保后台 agent 不会意外修改用户文件。

5. 事件队列 + 延迟绑定(Deferred Binding)

Analytics 服务在 sink 附加前将所有事件排入队列,sink 就绪后通过 queueMicrotask 异步刷出。这解耦了事件发射者和后端初始化时序,让任何模块都可以在启动早期安全调用 logEvent()

6. Secret Scanner 安全网

Team Memory Sync 在推送前对每个文件内容运行 scanForSecrets(),检测 API key、密码等敏感信息。匹配的文件被跳过并记录到 SkippedSecretFile 列表,防止意外将秘密泄露到团队共享存储。

开发者实践指南

添加新服务时,参考 Policy Limits 的模式:fail open + ETag 缓存 + 后台轮询 + registerCleanup 清理。

如何添加新服务

  1. services/ 下创建新目录
  2. main.tsxinit.ts 中注册初始化调用
  3. 通过 getFeatureValue_CACHED_MAY_BE_STALE() 控制 feature gate
  4. 使用 logEvent() 记录遥测事件
  5. 如需 forked agent,使用 createCacheSafeParams() 共享缓存
  6. 调用 registerCleanup() 注册退出清理

服务分类速查

类别服务运行模式
API 通信api/, oauth/按需调用
后台提取SessionMemory/, extractMemories/, MagicDocs/, autoDream/Post-sampling hook
上下文管理compact/, AgentSummary/阈值触发
同步服务settingsSync/, teamMemorySync/启动 + 定时轮询
安全治理policyLimits/, remoteManagedSettings/启动 + 定时轮询
增强功能tips/, PromptSuggestion/, toolUseSummary/按需调用
LSP 集成lsp/启动初始化 + 文件监听

API 客户端多后端支持

getAnthropicClient() 支持 5 种认证后端:直接 API Key、Claude.ai OAuth、AWS Bedrock、Azure Foundry、Google Vertex AI。后端选择通过环境变量和配置文件决定,客户端内部处理凭证刷新和区域路由。

架构师决策指南

服务隔离策略

所有后台服务(Session Memory、extractMemories、Magic Docs、Auto Dream)都通过 runForkedAgent() 在独立上下文中运行。这意味着:(1) 后台 agent 的工具调用不会污染主线程的 readFileState;(2) 后台 agent 的 token 用量独立计算;(3) 后台 agent 的失败不会影响主对话流程。这种隔离是以额外的 API 调用开销为代价的。

GrowthBook Feature Flags 的分层使用

服务使用两种 GrowthBook API:getFeatureValue_CACHED_MAY_BE_STALE()(非阻塞,可能返回过期值)和 getDynamicConfig_BLOCKS_ON_INIT()(阻塞,保证最新值)。绝大多数服务使用前者以避免启动延迟;只有 sessionMemoryCompact 的配置初始化使用后者,因为压缩配置的准确性直接影响用户体验。

CacheSafeParams 的设计意图

CacheSafeParams 封装了 system prompt、user context、system context、工具列表和消息前缀。所有 forked agent 共享同一份 params,确保 API 请求的 cache key 完全匹配。这是 prompt caching 的关键——如果任何一个参数不同,整个缓存前缀都会失效。

添加新服务时避免在模块顶层执行网络请求或 I/O。所有初始化应在显式调用的 init 函数中进行,让调用方控制时序。

Team Memory 的冲突解决策略

Team Memory Sync 采用"server wins"语义:拉取时本地文件被覆盖,推送时只上传 hash 不同的条目。文件删除不会传播。这是一种简单但有效的最终一致性模型,适合低频写入的团队记忆场景。

可视化处理拓扑图

queryModelWithStreaming 调用REPL / Agent → 服务层入口
1. 客户端创建 — getAnthropicClient()services/api/client.ts:88 — 智能工厂函数
Anthropic SDKAPI Key
OAuthclaude.ai 订阅
BedrockAWS Credentials
VertexGoogle App Creds
Gateway自定义 headers
环境变量控制,动态 import 仅加载实际使用的 SDK ↓
2. 请求参数构建 — paramsFromContext()services/api/claude.ts:1538合并 beta headers / effort / task budget / thinking 模式
3. 工具模式构建 — toolToAPISchema + ToolSearchservices/api/claude.ts:1235MCP 工具 defer_loading:true / LSP 工具延迟加载
完整请求参数就绪params + tools + messages
4. 缓存断点注入 — addCacheBreakpoints()services/api/claude.ts:3063 — prompt caching 核心system prompt 末尾 + 消息边界处放置 cache_control
should1hCacheTTL 判断L393 — 查询源决定 5min / 1h TTL
缓存命中
前缀缓存复用
无缓存 ↓
重试循环 — withRetry 包装
5. 流式请求发起 — withRetryservices/api/withRetry.ts — AsyncGenerator
shouldRetry 判断L696 — 429/529/500/连接错误/认证错误
不可重试
CannotRetryError
可重试
指数退避 + getRetryDelayRetry-After header 解析
↩ 回到发起请求
流式响应开始AsyncGenerator 逐事件处理
6. 流式事件处理 — yield 管道content_block_start / delta / stop / message_delta每个事件实时更新消息状态 → setState 触发 UI 重渲染
text delta文本流式输出
tool_use delta工具调用 JSON
thinking delta思维过程输出
message_deltausage + stop_reason
7. 用量统计与累积 — updateUsage / accumulateUsageservices/api/claude.ts:2924-3038流式 delta 用量 → 消息累积 → 会话总量
8. 缓存断裂检测 — checkResponseForCacheBreak()services/api/promptCacheBreakDetection.ts:437请求前 hash vs 响应 cache_tokens → 缓存命中验证
cache_read_tokens > 0缓存正常命中
cache_read = 0
缓存断裂记录 diff 并上报分析成本影响:完整 prompt 重新处理
保底通道 — executeNonStreamingRequest
services/api/claude.ts:818 — 当流式请求持续失败时的最后保底
① 流式重试全部失败后触发
② 发起非流式请求(单次完整响应)
③ 牺牲实时性换取可靠性
空闲计时器 resetStreamIdleTimer(L1895)检测流式传输卡住并触发重试
{ messages, tools, system }
paramsFromContextAPI 请求参数
addCacheBreakpoints注入 cache_control
withRetry重试 + 流式
yield eventsAsyncGenerator
Message + Usage最终消息 + 用量统计
透明多云 + 自动容错:上层代码只需调用 queryModelWithStreaming,无需关心底层是 Anthropic API、Bedrock 还是 Vertex。AsyncGenerator 允许重试过程中 yield 中间状态("正在重试第 2/3 次"),实现"可观察的重试"。缓存断裂检测系统帮助发现多个导致隐式缓存失效的 bug。

核心处理流程详解

服务层是 Claude Code 与 Anthropic API 交互的核心,涵盖了从客户端创建、请求构建、流式传输、重试容错到缓存优化的完整链路。整个流程以 queryModelWithStreaming 生成器为核心,通过 withRetry 包装实现自动重试,并支持 Bedrock、Vertex 等多云后端的透明切换。

1. 客户端创建 — getAnthropicClient()
services/api/client.ts:88 根据环境变量决定创建哪种 SDK 客户端:原生 Anthropic SDK、Bedrock SDK(@anthropic-ai/bedrock-sdk,L153)、Vertex SDK(@anthropic-ai/vertex-sdk)。支持 API Key、OAuth、AWS Credentials(含 Bearer Token)、Google Application Credentials 五种认证方式。客户端配置包含 session ID、代理设置、自定义 headers 和超时(默认 600s,L144)。
2. 请求参数构建 — paramsFromContext()
services/api/claude.ts:1538 构建完整的 API 请求参数:合并 beta headers(L1539)、配置 effort 参数(L1563)、配置 task budget(L1571)、处理 thinking 模式(L1596-1617,支持 adaptive 和 budget 两种模式)。参数构建考虑了重试上下文(RetryContext),允许重试时调整 max_tokens 等参数。
3. 工具模式构建 — toolToAPISchema + ToolSearch
services/api/claude.ts:1235 将内部 Tool 对象转换为 API schema。当启用 Tool Search 时(L1120),对 MCP 工具添加 defer_loading: true 标记,让 API 端动态加载工具描述。LSP 工具在初始化未完成时也会延迟(shouldDeferLspTool,L786)。全局缓存策略根据是否包含 MCP 工具动态调整(L1212-1229)。
4. 缓存断点注入 — addCacheBreakpoints()
services/api/claude.ts:3063 为消息数组注入 cache_control 标记。这是 prompt caching 的核心——在 system prompt 末尾、消息边界处放置断点,让 API 端缓存不变的前缀。断点位置经过精心设计:system prompt 始终缓存、消息前缀尽可能长地命中缓存。should1hCacheTTL(L393)根据查询源决定使用 5 分钟还是 1 小时 TTL。
5. 流式请求发起 — withRetry 包装
withRetry(services/api/withRetry.ts)包装实际的 API 调用,实现指数退避重试。shouldRetry(L696)判断 429/529/500/连接错误/认证错误是否可重试。getRetryDelay(L530)计算退避时间,支持 Retry-After header 解析。Bedrock/Vertex 的认证错误会清除凭证缓存后重试(L650-694)。
6. 流式事件处理 — yield 管道
流式响应通过 AsyncGenerator 逐事件处理:content_block_startcontent_block_deltacontent_block_stopmessage_delta(usage + stop_reason)。每个事件实时更新消息状态,通过 React 的 setState 触发 UI 重渲染。空闲计时器(resetStreamIdleTimer,L1895)检测流式传输卡住并触发重试。
7. 用量统计与累积 — updateUsage / accumulateUsage
services/api/claude.ts:2924-3038 处理 API 返回的 token 用量。updateUsage(L2924)将流式 delta 的用量合并到当前消息,accumulateUsage(L2993)将单条消息的用量累积到会话总量。Cache read/creation tokens 被精确跟踪,用于缓存效率分析。
8. 缓存断裂检测 — checkResponseForCacheBreak()
services/api/promptCacheBreakDetection.ts:437 对比请求前记录的 prompt 状态与响应中的 cache tokens。如果 cache_read_tokens 为 0 但前缀应该命中缓存,说明发生了缓存断裂。系统会记录 diff(L708)并上报分析,帮助发现导致缓存失效的参数变化。这对成本优化至关重要——每次缓存断裂意味着完整的 prompt 重新处理。
服务层的设计核心是"透明多云 + 自动容错"。上层代码(REPL、Agent)只需调用 queryModelWithStreaming,无需关心底层是 Anthropic API、Bedrock 还是 Vertex。重试逻辑处理了网络瞬断(429/529)、认证刷新(AWS/GCP credentials 过期)、模型回退(fallback model)等场景。非流式回退(executeNonStreamingRequest,L818)在流式请求持续失败时提供最后的保底通道。

设计精华

1. 五后端透明切换的客户端工厂

getAnthropicClient(services/api/client.ts:88)是一个智能工厂函数,根据环境变量自动选择 SDK 后端。支持 5 种认证后端:直接 API Key、OAuth(claude.ai 订阅)、AWS Credentials(Bedrock,含 Bearer Token 快捷认证)、Google Application Credentials(Vertex)、自定义 headers(网关/代理)。

多云客户端工厂
运行时根据环境变量动态选择 SDK 实现,统一接口
// services/api/client.ts:88-316
export async function getAnthropicClient({...}): Promise<Anthropic> {
  if (isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK)) {
    const { AnthropicBedrock } = await import('@anthropic-ai/bedrock-sdk')
    return new AnthropicBedrock(bedrockArgs)
  }
  if (isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX)) {
    const { AnthropicVertex } = await import('@anthropic-ai/vertex-sdk')
    return new AnthropicVertex(vertexArgs)
  }
  return new Anthropic(defaultArgs) // 原生 SDK
}
这个设计的精妙在于:动态 import(await import())确保只有实际使用的 SDK 被加载。如果用户使用原生 API,Bedrock 和 Vertex 的 SDK 代码根本不会进入内存。这对 CLI 工具尤为重要——启动时间直接影响用户体验。同时,环境变量控制而非配置文件,让 CI/CD 和容器部署的配置变得极其简单。

2. 生成器模式的流式重试架构

queryModelWithStreamingwithRetry 都使用 AsyncGeneratorasync function*)而非传统的 Promise/回调。这意味着调用方可以在流式传输的任何时刻中断(AbortSignal)、收集部分结果、或触发重试。重试时不需要重新构建整个请求——生成器可以从断点继续。

生成器重试管道
withRetry 包装实际 API 调用,yield 系统消息,返回最终结果
// services/api/withRetry.ts — 核心重试循环
async function* withRetry(fn, options) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const stream = await fn(retryContext)
      for await (const event of stream) {
        // 处理流式事件,yield 中间状态
      }
      return finalMessage
    } catch (error) {
      if (!shouldRetry(error)) throw new CannotRetryError(...)
      yield systemMessage  // 通知上层"正在重试"
      await delay(getRetryDelay(attempt))
      retryContext = adjustForRetry(error) // 调整参数
    }
  }
}
AsyncGenerator 在重试场景中有独特优势:它允许在重试过程中 yield 中间状态(如"正在重试,第 2/3 次"),调用方可以实时展示这些信息。如果使用 Promise,重试是"黑盒"的——调用方只知道最终成功或失败。这种"可观察的重试"在 CLI 工具中极大改善了用户体验。

3. 缓存断裂检测系统

promptCacheBreakDetection.ts 实现了一个两阶段的缓存诊断系统:recordPromptState(L247)在 API 调用前记录 system prompt hash、tool schema hash、model 等状态;checkResponseForCacheBreak(L437)在响应返回后对比实际 cache tokens 与预期。如果前缀应该命中缓存但实际没有,生成 diff 并上报。这个系统帮助团队发现了多个导致隐式缓存失效的 bug。

两阶段缓存诊断
预调用记录状态快照,后调用验证缓存命中率
// services/api/promptCacheBreakDetection.ts:247
export function recordPromptState(snapshot: PromptStateSnapshot): void {
  // 记录: system hash, tool hash, model, betas
  const prev = previousStates.get(trackingKey)
  if (prev && prev.systemHash !== newSystemHash) {
    // 检测到 system prompt 变化 → 记录 diff
    pendingChanges.system = { prev, curr: snapshot }
  }
}

// L437 — 响应验证
export async function checkResponseForCacheBreak(...) {
  if (cacheReadTokens === 0 && shouldHaveCached) {
    // 缓存断裂!生成 diff 并上报
    await writeCacheBreakDiff(prevContent, newContent)
  }
}
缓存断裂检测是一个"被动监控系统"——它不影响正常流程,只在异常时产生诊断数据。这种设计在成本敏感场景中极其重要:prompt caching 可以将 API 成本降低 90%,但只有当缓存命中率稳定时才有效。检测系统让团队在缓存策略变更后立即发现回退,而非等到月底看到成本报告。

4. 认证凭证的自动刷新与缓存清除

withRetry.ts 中的认证错误处理(L650-694)体现了"先清除缓存再重试"的策略。AWS 和 GCP 的凭证有过期时间,错误发生时首先清除凭证缓存(handleAwsCredentialErrorhandleGcpCredentialError),然后重试触发凭证刷新。这确保了长时间运行的会话不会因凭证过期而中断。

分布式系统中的凭证管理有一个常见陷阱:缓存的凭证刚好在 API 调用期间过期。Claude Code 的解决方案是在错误处理器中清除缓存,而非在定时器中预刷新。这种"懒刷新"策略更简单可靠——不依赖时钟同步,也不会在凭证仍有很长时间有效时浪费刷新调用。

Agent 实践借鉴 — 客服 Agent 服务层设计

本节回答:如果你在设计一个客服 Agent 助手,后端需要对接多个 LLM、多个租户、多个外部服务,Claude Code 的服务层有哪些值得抄、哪些不需要抄?

1. 场景映射:客服 Agent 的服务依赖

客服 agent 的后端比 Claude Code 的后端更复杂:不仅要调 LLM,还要对接知识库、订单系统、翻译服务、语音转文字。更关键的是多租户——不同企业客户可能用自己的 LLM 或知识库:

客服 Agent 的 5 个后端依赖
核心服务 vs 辅助服务,挂了影响不同
// 客服 Agent 的后端依赖 — 分核心/辅助
// ──────────────────────────────────
// 核心服务(挂了 = 完全瘫痪):
//   1. LLM API        — agent 的大脑
//   2. 订单系统 API    — 查订单/退单的基础
//   3. 知识库检索      — FAQ/政策查询
//
// 辅助服务(挂了 = 功能降级,不瘫痪):
//   4. 翻译服务        — 外语客户才需要
//   5. 语音转文字      — 电话渠道才需要
//
// 多租户配置:
//   租户 A: 用 OpenAI GPT-4 + 自建知识库
//   租户 B: 用 Claude + 内置 FAQ
//   租户 C: 用本地部署模型 + 企业知识库
CC 借鉴点映射
CC 的 5 后端切换 → 客服的多租户切换
// Claude Code                 →  客服 Agent
// ────────────────────────────────────────────
5 后端 getClient 工厂    →  按租户切换 LLM 后端
Fail-open 原则           →  翻译挂了不影响文字服务
遥测成本追踪             →  按租户统计 API 调用费
Feature Flag 系统        →  按租户开启/关闭功能
凭证自动刷新             →  不同租户的 API Key 管理

2. 借鉴 CC + 客服改造:伪代码实现

以下是客服服务层的核心实现,借鉴 CC 的透明后端切换、fail-open 和成本追踪:

多后端 API 客户端工厂
借鉴 CC 的 getClient 工厂,按租户配置选择 LLM 后端
// 多租户 API 客户端工厂 — 借鉴 CC 的 5 后端自动检测
function getLLMClient(tenant: Tenant): LLMClient {
  const config = tenant.llmConfig

  switch (config.provider) {
    case "openai":
      return createOpenAIClient({ apiKey: config.apiKey, model: config.model })

    case "anthropic":
      return createAnthropicClient({ apiKey: config.apiKey })

    case "azure":
      return createAzureClient({
        endpoint: config.azureEndpoint,
        deploymentName: config.deploymentName,
        apiKey: config.apiKey
      })

    case "local":
      // 租户自建模型 — Ollama/vLLM 等
      return createLocalClient({ baseUrl: config.localEndpoint })

    default:
      // 默认用平台提供的 LLM
      return createPlatformClient()
  }
}

// 透明切换 — 调用方不感知后端差异
async function queryAgent(tenant: Tenant, messages: Message[]) {
  const client = getLLMClient(tenant)  // 根据租户自动选后端
  return client.chat({ messages, tools: getToolsForTenant(tenant) })
}
Fail-open 服务编排
借鉴 CC 的 failOpen 包装器,核心服务必需,辅助服务降级
// Fail-open 服务编排 — 借鉴 CC 的 failOpen<T> 包装器
async function failOpen<T>(
  fn: () => Promise<T>,
  fallback: T,
  context: string
): Promise<T> {
  try {
    return await fn()
  } catch (error) {
    logger.warn(`${context} failed, using fallback`, { error: error.message })
    return fallback
  }
}

// 客服服务编排 — 核心 + 辅助分离
async function handleCustomerQuery(tenant: Tenant, query: string) {
  // 核心服务:直接 await,失败就报错
  const intent = await analyzeIntent(getLLMClient(tenant), query)
  const knowledge = await queryKnowledgeBase(tenant.kbConfig, intent)

  // 辅助服务:fail-open 包装,失败不影响核心流程
  const translatedQuery = await failOpen(
    () => translate(query, "zh", "en"),     // 翻译服务
    query,                                   // 失败就用原文
    "translation service"
  )

  const speechText = await failOpen(
    () => speechToText(queryAudioBuffer),    // 语音转文字
    "[语音转文字服务不可用]",
    "speech-to-text service"
  )

  // 组装结果返回给坐席面板
  return { intent, knowledge, translatedQuery, speechText }
}
按租户成本追踪 + 服务健康检查
借鉴 CC 的遥测系统,按租户统计 API 调用费用
// 成本追踪 — 借鉴 CC 的 telemetry.track() 模式
class TenantCostTracker {
  private costs = new Map<string, TenantCost>()  // tenantId → 成本

  // 每次 LLM 调用后记录 — fail-open 包装,不影响主流程
  recordUsage(tenantId: string, usage: {
    provider: string
    model: string
    inputTokens: number
    outputTokens: number
    latencyMs: number
  }) {
    const cost = this.calculateCost(usage.provider, usage.model, usage)
    const current = this.costs.get(tenantId) ?? { totalCost: 0, calls: 0 }
    this.costs.set(tenantId, {
      totalCost: current.totalCost + cost,
      calls: current.calls + 1
    })
  }

  // 按租户出账单
  getBilling(tenantId: string, period: string): BillingReport {
    return { tenantId, period, ...this.costs.get(tenantId) }
  }
}

// 服务健康检查 + 降级策略
class ServiceHealthMonitor {
  private health = new Map<string, "healthy"|"degraded"|"down">()

  async checkAll() {
    // 核心服务:down 时返回错误,坐席面板显示"系统维护中"
    this.health.set("llm", await this.checkEndpoint(config.llmEndpoint))
    this.health.set("order_system", await this.checkEndpoint(config.orderEndpoint))

    // 辅助服务:down 时标记为 degraded,不影响整体状态
    this.health.set("translation", await this.checkEndpoint(config.translateEndpoint))
  }

  getStatus(): ServiceStatus {
    const coreDown = ["llm","order_system"].some(k => this.health.get(k) === "down")
    return coreDown ? "down" : "healthy"   // 只看核心服务
  }
}

3. 落地清单:必须抄 vs 不需要抄

必须抄:
  • 透明后端切换工厂 — 一个 getClient 函数按租户配置选择 LLM 后端,调用方不感知差异
  • Fail-open 原则 — 核心服务直接 await,辅助服务用 failOpen 包装。翻译挂了不影响文字客服
  • 按租户成本追踪 — 每次 LLM 调用记录 token 用量,按租户出账单。SaaS 模式的基础
  • 服务健康检查 — 定期检查所有依赖服务状态,核心服务 down 时面板提示"系统维护中"
不需要抄:
  • 5 种认证后端 — CC 支持 API Key / OAuth / Bedrock / Vertex / 自定义。客服场景用内部认证网关,不需要这么多种
  • Fork agent cache 共享 — CC 的子 agent 复用父进程 API 客户端共享 prompt cache。客服场景每个请求独立,不需要
  • Prompt cache 断裂检测 — CC 监控缓存命中率异常。客服场景的 LLM 后端多样化,缓存策略不同

4. 常见坑

坑 1:翻译服务挂了导致整个客服系统不可用。把翻译服务的 await 放在了主流程的关键路径上,翻译超时 10 秒 → 坐席等 10 秒才看到回复。解决方案:辅助服务必须用 failOpen 包装,设置 2 秒超时,超时返回 fallback。
坑 2:所有租户共享一个 LLM 客户端。租户 A 配额用完导致租户 B 的请求被限流。解决方案:每个租户独立的 LLM 客户端实例 + 独立的限流配额。工厂模式的意义就是隔离。
坑 3:成本追踪放在主流程的 await 路径上。每次 LLM 调用后 await 写入数据库记录费用,数据库慢了拖慢整个请求。解决方案:成本追踪异步入队 + 批量写入,和 CC 的 telemetry 一样用事件队列。
坑 4:健康检查状态不刷新。启动时检查一次后不再检查,服务挂了面板仍然显示"健康"。解决方案:定时轮询(每 30 秒),且健康检查本身也用 failOpen 包装——健康检查挂了不能影响业务。

代码索引

文件行数说明
services/api/client.ts~100Anthropic SDK 客户端创建,支持 5 种认证后端
services/api/claude.ts~largequeryModelWithStreaming、queryHaiku 等 API 调用封装
services/api/withRetry.ts~medium指数退避重试逻辑
services/api/errors.ts~mediumAPI 错误分类和处理
services/api/promptCacheBreakDetection.ts~mediumPrompt 缓存断裂检测
services/api/usage.ts~mediumAPI 用量跟踪
services/oauth/index.ts~199OAuthService 类:PKCE 流程、双模式获取 code
services/oauth/client.ts~mediumOAuth URL 构建、token 交换
services/oauth/crypto.ts~smallPKCE code_verifier/challenge 生成
services/analytics/index.ts~174事件队列、sink 绑定、logEvent 公共 API
services/analytics/sink.ts~mediumDatadog + 1P 事件路由、采样逻辑
services/analytics/growthbook.ts~mediumGrowthBook feature flag 集成
services/lsp/manager.ts~mediumLSP Server Manager 单例管理
services/lsp/LSPServerManager.ts~mediumLSP 服务器生命周期管理
services/lsp/LSPClient.ts~mediumLSP 客户端通信
services/settingsSync/index.ts~medium用户设置上传/下载同步
services/teamMemorySync/index.ts~medium团队记忆文件同步:delta upload、server wins
services/teamMemorySync/secretScanner.ts~medium团队记忆推送前的秘密扫描
services/policyLimits/index.ts~664策略限制:fail open + ETag 缓存 + 后台轮询
services/remoteManagedSettings/index.ts~medium远程托管设置:企业客户配置下发
services/tips/tipScheduler.ts~smallSpinner 提示调度:按距离上次显示的 session 数选择
services/PromptSuggestion/promptSuggestion.ts~medium提示建议:forked agent 生成下一步建议
services/MagicDocs/magicDocs.ts~mediumMagic Docs:带标记的文件自动文档更新
services/autoDream/autoDream.ts~mediumAuto Dream:跨 session 记忆整合
services/AgentSummary/agentSummary.ts~mediumAgent Summary:子 agent 进度摘要(30s 间隔)
services/toolUseSummary/toolUseSummaryGenerator.ts~medium工具使用摘要:Haiku 生成人类可读的批次描述
services/tools/toolExecution.ts~large工具执行引擎:权限检查、执行、结果收集
services/tools/toolOrchestration.ts~medium工具编排:并发/串行执行策略
services/tools/toolHooks.ts~medium工具 Hook 集成
services/tools/StreamingToolExecutor.ts~medium流式工具执行器