服务层全景
Claude Code 所有后台服务的全景视图:从 API 通信、OAuth 认证到 LSP 集成、记忆同步的完整服务矩阵
职责概述
解决的问题:CC 主循环忙着处理用户对话,但还有很多事需要在后台持续做——检查订阅状态、同步团队记忆、监控 MCP 连接健康、预缓存 Prompt。这些事不能阻塞主流程,需要一套后台服务框架让它们各跑各的。
应用场景:① 用户在对话时,后台默默同步团队共享的记忆文件 ② 订阅到期前自动提醒 ③ MCP Server 断连后自动重连 ④ 定期把 Prompt 模板预热到 API 缓存,减少首次响应延迟 ⑤ 后台服务挂了不影响主对话——"fail open"原则。
一句话理解:就像手机的后台 App 刷新——你在用微信的时候,邮件在后台同步、天气在后台更新,各干各的,互不干扰。
架构设计
服务依赖关系图
服务分层
核心数据流
API 通信流程
OAuth 认证流程
Forked Agent 模式(服务间共享)
关键类型与接口
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 列表,防止意外将秘密泄露到团队共享存储。
开发者实践指南
如何添加新服务
- 在
services/下创建新目录 - 在
main.tsx或init.ts中注册初始化调用 - 通过
getFeatureValue_CACHED_MAY_BE_STALE()控制 feature gate - 使用
logEvent()记录遥测事件 - 如需 forked agent,使用
createCacheSafeParams()共享缓存 - 调用
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 的关键——如果任何一个参数不同,整个缓存前缀都会失效。
Team Memory 的冲突解决策略
Team Memory Sync 采用"server wins"语义:拉取时本地文件被覆盖,推送时只上传 hash 不同的条目。文件删除不会传播。这是一种简单但有效的最终一致性模型,适合低频写入的团队记忆场景。
◈ 可视化处理拓扑图
① 流式重试全部失败后触发
② 发起非流式请求(单次完整响应)
③ 牺牲实时性换取可靠性
空闲计时器 resetStreamIdleTimer(L1895)检测流式传输卡住并触发重试
queryModelWithStreaming,无需关心底层是 Anthropic API、Bedrock 还是 Vertex。AsyncGenerator 允许重试过程中 yield 中间状态("正在重试第 2/3 次"),实现"可观察的重试"。缓存断裂检测系统帮助发现多个导致隐式缓存失效的 bug。⇉ 核心处理流程详解
服务层是 Claude Code 与 Anthropic API 交互的核心,涵盖了从客户端创建、请求构建、流式传输、重试容错到缓存优化的完整链路。整个流程以 queryModelWithStreaming 生成器为核心,通过 withRetry 包装实现自动重试,并支持 Bedrock、Vertex 等多云后端的透明切换。
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)。services/api/claude.ts:1538 构建完整的 API 请求参数:合并 beta headers(L1539)、配置 effort 参数(L1563)、配置 task budget(L1571)、处理 thinking 模式(L1596-1617,支持 adaptive 和 budget 两种模式)。参数构建考虑了重试上下文(RetryContext),允许重试时调整 max_tokens 等参数。services/api/claude.ts:1235 将内部 Tool 对象转换为 API schema。当启用 Tool Search 时(L1120),对 MCP 工具添加 defer_loading: true 标记,让 API 端动态加载工具描述。LSP 工具在初始化未完成时也会延迟(shouldDeferLspTool,L786)。全局缓存策略根据是否包含 MCP 工具动态调整(L1212-1229)。services/api/claude.ts:3063 为消息数组注入 cache_control 标记。这是 prompt caching 的核心——在 system prompt 末尾、消息边界处放置断点,让 API 端缓存不变的前缀。断点位置经过精心设计:system prompt 始终缓存、消息前缀尽可能长地命中缓存。should1hCacheTTL(L393)根据查询源决定使用 5 分钟还是 1 小时 TTL。withRetry(services/api/withRetry.ts)包装实际的 API 调用,实现指数退避重试。shouldRetry(L696)判断 429/529/500/连接错误/认证错误是否可重试。getRetryDelay(L530)计算退避时间,支持 Retry-After header 解析。Bedrock/Vertex 的认证错误会清除凭证缓存后重试(L650-694)。AsyncGenerator 逐事件处理:content_block_start、content_block_delta、content_block_stop、message_delta(usage + stop_reason)。每个事件实时更新消息状态,通过 React 的 setState 触发 UI 重渲染。空闲计时器(resetStreamIdleTimer,L1895)检测流式传输卡住并触发重试。services/api/claude.ts:2924-3038 处理 API 返回的 token 用量。updateUsage(L2924)将流式 delta 的用量合并到当前消息,accumulateUsage(L2993)将单条消息的用量累积到会话总量。Cache read/creation tokens 被精确跟踪,用于缓存效率分析。services/api/promptCacheBreakDetection.ts:437 对比请求前记录的 prompt 状态与响应中的 cache tokens。如果 cache_read_tokens 为 0 但前缀应该命中缓存,说明发生了缓存断裂。系统会记录 diff(L708)并上报分析,帮助发现导致缓存失效的参数变化。这对成本优化至关重要——每次缓存断裂意味着完整的 prompt 重新处理。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(网关/代理)。
// 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
}
await import())确保只有实际使用的 SDK 被加载。如果用户使用原生 API,Bedrock 和 Vertex 的 SDK 代码根本不会进入内存。这对 CLI 工具尤为重要——启动时间直接影响用户体验。同时,环境变量控制而非配置文件,让 CI/CD 和容器部署的配置变得极其简单。2. 生成器模式的流式重试架构
queryModelWithStreaming 和 withRetry 都使用 AsyncGenerator(async function*)而非传统的 Promise/回调。这意味着调用方可以在流式传输的任何时刻中断(AbortSignal)、收集部分结果、或触发重试。重试时不需要重新构建整个请求——生成器可以从断点继续。
// 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) // 调整参数
}
}
}
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)
}
}
4. 认证凭证的自动刷新与缓存清除
withRetry.ts 中的认证错误处理(L650-694)体现了"先清除缓存再重试"的策略。AWS 和 GCP 的凭证有过期时间,错误发生时首先清除凭证缓存(handleAwsCredentialError、handleGcpCredentialError),然后重试触发凭证刷新。这确保了长时间运行的会话不会因凭证过期而中断。
◈ Agent 实践借鉴 — 客服 Agent 服务层设计
1. 场景映射:客服 Agent 的服务依赖
客服 agent 的后端比 Claude Code 的后端更复杂:不仅要调 LLM,还要对接知识库、订单系统、翻译服务、语音转文字。更关键的是多租户——不同企业客户可能用自己的 LLM 或知识库:
// 客服 Agent 的后端依赖 — 分核心/辅助
// ──────────────────────────────────
// 核心服务(挂了 = 完全瘫痪):
// 1. LLM API — agent 的大脑
// 2. 订单系统 API — 查订单/退单的基础
// 3. 知识库检索 — FAQ/政策查询
//
// 辅助服务(挂了 = 功能降级,不瘫痪):
// 4. 翻译服务 — 外语客户才需要
// 5. 语音转文字 — 电话渠道才需要
//
// 多租户配置:
// 租户 A: 用 OpenAI GPT-4 + 自建知识库
// 租户 B: 用 Claude + 内置 FAQ
// 租户 C: 用本地部署模型 + 企业知识库
// Claude Code → 客服 Agent
// ────────────────────────────────────────────
5 后端 getClient 工厂 → 按租户切换 LLM 后端
Fail-open 原则 → 翻译挂了不影响文字服务
遥测成本追踪 → 按租户统计 API 调用费
Feature Flag 系统 → 按租户开启/关闭功能
凭证自动刷新 → 不同租户的 API Key 管理
2. 借鉴 CC + 客服改造:伪代码实现
以下是客服服务层的核心实现,借鉴 CC 的透明后端切换、fail-open 和成本追踪:
// 多租户 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<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 的 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. 常见坑
代码索引
| 文件 | 行数 | 说明 |
|---|---|---|
services/api/client.ts | ~100 | Anthropic SDK 客户端创建,支持 5 种认证后端 |
services/api/claude.ts | ~large | queryModelWithStreaming、queryHaiku 等 API 调用封装 |
services/api/withRetry.ts | ~medium | 指数退避重试逻辑 |
services/api/errors.ts | ~medium | API 错误分类和处理 |
services/api/promptCacheBreakDetection.ts | ~medium | Prompt 缓存断裂检测 |
services/api/usage.ts | ~medium | API 用量跟踪 |
services/oauth/index.ts | ~199 | OAuthService 类:PKCE 流程、双模式获取 code |
services/oauth/client.ts | ~medium | OAuth URL 构建、token 交换 |
services/oauth/crypto.ts | ~small | PKCE code_verifier/challenge 生成 |
services/analytics/index.ts | ~174 | 事件队列、sink 绑定、logEvent 公共 API |
services/analytics/sink.ts | ~medium | Datadog + 1P 事件路由、采样逻辑 |
services/analytics/growthbook.ts | ~medium | GrowthBook feature flag 集成 |
services/lsp/manager.ts | ~medium | LSP Server Manager 单例管理 |
services/lsp/LSPServerManager.ts | ~medium | LSP 服务器生命周期管理 |
services/lsp/LSPClient.ts | ~medium | LSP 客户端通信 |
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 | ~small | Spinner 提示调度:按距离上次显示的 session 数选择 |
services/PromptSuggestion/promptSuggestion.ts | ~medium | 提示建议:forked agent 生成下一步建议 |
services/MagicDocs/magicDocs.ts | ~medium | Magic Docs:带标记的文件自动文档更新 |
services/autoDream/autoDream.ts | ~medium | Auto Dream:跨 session 记忆整合 |
services/AgentSummary/agentSummary.ts | ~medium | Agent 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 | 流式工具执行器 |