Skip to content

Latest commit

 

History

History
750 lines (543 loc) · 25.1 KB

File metadata and controls

750 lines (543 loc) · 25.1 KB

13. 服务层架构分析

源码路径: src/services/

Claude Code 的服务层采用模块化设计, 将核心业务逻辑拆分为独立的服务模块。每个服务负责一个特定的关注点: API 通信、遥测分析、上下文压缩、语言服务器协议、OAuth 认证、企业策略限制等。本文档深入分析各服务模块的内部架构与关键实现。


Architecture Diagram

Services Layer Architecture

1. API Client 架构 (services/api/)

API 客户端是整个系统的通信核心, 负责与 Anthropic API 的所有交互。支持多种后端: 直连 Anthropic API、AWS Bedrock、Google Vertex AI、Azure Foundry。

1.1 客户端工厂 (client.ts)

客户端通过工厂函数创建, 根据环境变量自动选择后端:

// 支持的后端类型:
// - Direct API (ANTHROPIC_API_KEY)
// - AWS Bedrock (CLAUDE_CODE_USE_BEDROCK)
// - Google Vertex AI (CLAUDE_CODE_USE_VERTEX)
// - Azure Foundry (CLAUDE_CODE_USE_FOUNDRY)
// - OAuth (Claude.ai 订阅用户)

function getAnthropicClient(): Promise<Anthropic>

客户端首次创建后复用, 仅在认证失败 (401/403) 或连接重置 (ECONNRESET/EPIPE) 时重建。

1.2 重试机制 (withRetry.ts)

重试系统是 API 层最复杂的部分, 采用 AsyncGenerator 模式, 允许在等待重试期间向调用方发送状态消息:

export async function* withRetry<T>(
  getClient: () => Promise<Anthropic>,
  operation: (
    client: Anthropic,
    attempt: number,
    context: RetryContext,
  ) => Promise<T>,
  options: RetryOptions,
): AsyncGenerator<SystemAPIErrorMessage, T>

核心类型:

export interface RetryContext {
  maxTokensOverride?: number
  model: string
  thinkingConfig: ThinkingConfig
  fastMode?: boolean
}

错误分类与策略:

错误类型 HTTP 状态码 策略
速率限制 429 指数退避, Fast Mode 降级
过载 529 最多 MAX_529_RETRIES=3 次, 仅前台查询重试
认证失败 401 刷新 OAuth Token 后重试
Token 吊销 403 刷新 OAuth Token 后重试
连接重置 ECONNRESET/EPIPE 禁用 keep-alive, 重建客户端
Bedrock 认证 403/CredentialsProviderError 清除凭证缓存, 重建客户端
Vertex 认证 401 刷新 GCP 凭证后重试

前台 529 重试白名单 -- 仅这些 QuerySource 类型会重试 529 错误:

const FOREGROUND_529_RETRY_SOURCES = new Set<QuerySource>([
  'repl_main_thread',
  'sdk',
  'agent:custom', 'agent:default', 'agent:builtin',
  'compact',
  'hook_agent', 'hook_prompt',
  'verification_agent',
  'side_question',
  'auto_mode',
  // ...
])

后台任务 (summaries, titles, suggestions) 遇到 529 立即放弃, 避免容量级联期间的网关放大。

持久重试模式 (CLAUDE_CODE_UNATTENDED_RETRY): 无人值守会话对 429/529 无限重试, 最大退避 5 分钟, 重置上限 6 小时, 心跳间隔 30 秒。

Fast Mode 降级: 短 retry-after 时保持 Fast Mode 等待 (保留 prompt cache); 长 retry-after 进入冷却期切换标准速度; Overage 不可用时永久禁用。

1.3 流式响应 (claude.ts)

核心查询函数 queryModelWithStreaming 是一个 122KB 的大文件, 处理流式 API 调用的完整生命周期:

export async function* queryModelWithStreaming(
  options: QueryModelWithStreamingOptions,
): AsyncGenerator<StreamEvent | Message, void, unknown>

关键职责: 消息规范化、System prompt 组装、Beta header 管理、流事件解析与 token 统计、错误映射为 AssistantMessage、Prompt cache 命中检测、Session cost 累计、遥测记录。

支持 CLAUDE_CODE_EXTRA_BODY 环境变量注入自定义参数, 1P CLI 包含 anti_distillation: ['fake_tools'] 反蒸馏防护。

1.4 Bootstrap API (bootstrap.ts)

启动时拉取客户端配置数据和附加模型选项:

const bootstrapResponseSchema = z.object({
  client_data: z.record(z.unknown()).nullish(),
  additional_model_options: z.array(
    z.object({
      model: z.string(),
      name: z.string(),
      description: z.string(),
    })
  ).nullish(),
})

export async function fetchBootstrapData(): Promise<void>

仅 firstParty provider 有效。使用 OAuth (优先) 或 API key 认证。响应通过 Zod schema 校验, 仅在数据变更时持久化到磁盘 (isEqual 对比)。

1.5 Files API (filesApi.ts)

管理文件上传与下载, 用于会话启动时下载文件附件:

export type FilesApiConfig = {
  oauthToken: string
  baseUrl?: string       // 默认 https://api.anthropic.com
  sessionId: string
}

export type DownloadResult = {
  fileId: string
  path: string
  success: boolean
  error?: string
  bytesWritten?: number
}
// MAX_RETRIES=3, BASE_DELAY_MS=500, MAX_FILE_SIZE=500MB

1.6 错误处理 (errors.ts)

错误处理模块提供分类型的错误消息生成和解析:

export const API_ERROR_MESSAGE_PREFIX = 'API Error'
export const PROMPT_TOO_LONG_ERROR_MESSAGE = 'Prompt is too long'
export const REPEATED_529_ERROR_MESSAGE = 'Repeated 529 Overloaded errors'
export const CUSTOM_OFF_SWITCH_MESSAGE =
  'Opus is experiencing high load, please use /model to switch to Sonnet'

export function parsePromptTooLongTokenCounts(rawMessage: string): {
  actualTokens: number | undefined
  limitTokens: number | undefined
}

export function getPromptTooLongTokenGap(msg: AssistantMessage): number | undefined

getPromptTooLongTokenGap 被 reactive compact 用来计算超限 token 数, 从而跳过多个消息组而非逐个剥离。

Media 错误检测:

export function isMediaSizeError(raw: string): boolean
export function isMediaSizeErrorMessage(msg: AssistantMessage): boolean
// 用于 reactive compact 的 summarize retry, 决定是剥离图片重试还是直接失败

2. Analytics / 遥测服务 (services/analytics/)

2.1 事件架构 (index.ts)

事件系统采用 sink 模式, 核心 API 零依赖以避免循环引用:

export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = never
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED = never

export type AnalyticsSink = {
  logEvent: (eventName: string, metadata: LogEventMetadata) => void
  logEventAsync: (
    eventName: string,
    metadata: LogEventMetadata,
  ) => Promise<void>
}

export function logEvent(
  eventName: string,
  metadata: { [key: string]: boolean | number | undefined },
): void

export function logEventAsync(
  eventName: string,
  metadata: { [key: string]: boolean | number | undefined },
): Promise<void>

关键设计:

  1. 类型安全 PII 防护: AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHSnever 类型, 强制显式 cast
  2. PROTO 前缀: _PROTO_* 键仅路由到 1P 特权列, 通用后端通过 stripProtoFields() 过滤
  3. 预初始化队列: sink 附加前事件入队, 附加后 queueMicrotask 异步排空
  4. 幂等附加: attachAnalyticsSink() 多次调用安全

2.2 事件路由 Sink (sink.ts)

Sink 实现将事件路由到两个后端:

function logEventImpl(eventName: string, metadata: LogEventMetadata): void {
  // 1. 采样检查 (shouldSampleEvent)
  // 2. Datadog -- 通用后端, stripProtoFields 过滤 PII
  // 3. 1P Event Logging -- 接收完整 payload 包括 _PROTO_*
}

Datadog 门控:

const DATADOG_GATE_NAME = 'tengu_log_datadog_events'

function shouldTrackDatadog(): boolean {
  if (isSinkKilled('datadog')) return false
  // GrowthBook feature gate 控制
  return checkStatsigFeatureGate_CACHED_MAY_BE_STALE(DATADOG_GATE_NAME)
}

支持 killswitch (sinkKillswitch.ts) 和 GrowthBook 门控两级开关。

2.3 GrowthBook 集成 (growthbook.ts)

GrowthBook 是 feature flag 和 A/B 实验的核心基础设施:

export type GrowthBookUserAttributes = {
  id: string
  sessionId: string
  deviceID: string
  platform: 'win32' | 'darwin' | 'linux'
  apiBaseUrlHost?: string
  organizationUUID?: string
  accountUUID?: string
  userType?: string
  subscriptionType?: string
  rateLimitTier?: string
  firstTokenTime?: number
  email?: string
  appVersion?: string
  github?: GitHubActionsMetadata
}

核心 API:

export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
  feature: string, defaultValue: T
): T

export function onGrowthBookRefresh(
  listener: GrowthBookRefreshListener
): () => void

export function getDynamicConfig_BLOCKS_ON_INIT<T>(
  configName: string
): T | null

关键机制: Remote Eval 模式 (服务端评估); 磁盘缓存 (上次会话 fallback); 延迟曝光日志 (pendingExposures); 曝光去重 (loggedExposures Set); 环境变量覆盖 (CLAUDE_INTERNAL_FC_OVERRIDES, ant-only); 重初始化等待 (reinitializingPromise)。

2.4 Analytics 配置 (config.ts)

export function isAnalyticsDisabled(): boolean {
  return (
    process.env.NODE_ENV === 'test' ||
    isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
    isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
    isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
    isTelemetryDisabled()
  )
}

三方云提供商 (Bedrock/Vertex/Foundry) 默认禁用 analytics。反馈调查 (isFeedbackSurveyDisabled) 不受三方提供商限制。


3. 消息压缩策略 (services/compact/)

压缩系统是 Claude Code 管理长会话上下文窗口的核心机制。共有四种策略: 自动压缩、微压缩、API 微压缩、反应式压缩。

3.1 自动压缩 (autoCompact.ts)

Token 使用量达到阈值时自动触发全量压缩:

export type AutoCompactTrackingState = {
  compacted: boolean
  turnCounter: number
  turnId: string
  consecutiveFailures?: number
}

export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000
export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3

阈值计算:

export function getAutoCompactThreshold(model: string): number {
  const effectiveContextWindow = getEffectiveContextWindowSize(model)
  return effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS
}

export function getEffectiveContextWindowSize(model: string): number {
  // contextWindow - min(maxOutputTokens, 20_000)
  // 可通过 CLAUDE_CODE_AUTO_COMPACT_WINDOW 环境变量覆盖
}

Token 警告状态: calculateTokenWarningState() 返回 percentLeft、各级阈值标志和 isAtBlockingLimit

递归防护: compact/session_memory/marble_origami 查询源不触发自动压缩。连续失败熔断: MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 (源于 BQ 数据: 1279 会话出现 50+ 次连续失败)。

3.2 全量压缩 (compact.ts)

压缩的核心实现, 59KB 的大文件:

export type CompactionResult = {
  messages: Message[]
  summary: string
  tokensFreed: number
  // ...
}

export type RecompactionInfo = {
  // 重压缩相关信息
}

压缩流程: pre-compact hooks -> 消息按 API round 分组 -> 剥离图片/文档 -> forked agent 生成 <analysis> + <summary> 摘要 -> 构建 compact boundary message -> post-compact 清理 -> 恢复关键文件 -> 记录遥测。

压缩提示 (prompt.ts): 包含 NO_TOOLS_PREAMBLE (防止 Sonnet 4.6+ 自适应思考模型在 maxTurns:1 下尝试工具调用) 和 <analysis> + <summary> 输出结构。BASE 变体作用于全量压缩, PARTIAL 变体作用于部分压缩。

Post-compact 恢复: 最多 5 个文件, 总预算 50K tokens, 单文件/Skill 各 5K tokens, Skills 总预算 25K tokens。

3.3 微压缩 (microCompact.ts)

微压缩通过清除旧工具结果来释放空间, 无需 LLM 生成摘要。可压缩工具集: FileRead, Shell, Grep, Glob, WebSearch, WebFetch, FileEdit, FileWrite。

时间维度微压缩 (timeBasedMCConfig.ts): 根据消息年龄清除内容, 保留最近消息。

缓存感知微压缩 (Cached MC): 维护 pending/pinned 两种编辑状态。consumePendingCacheEdits() 获取新编辑 (清除 pending), getPinnedCacheEdits() 返回必须在原位重发的已固定编辑 (保证 cache hit)。

3.4 API 微压缩 (apiMicrocompact.ts)

服务端原生上下文管理, 通过 API 参数指导服务端清除旧内容:

export type ContextEditStrategy =
  | { type: 'clear_tool_uses_20250919'; trigger?: { type: 'input_tokens'; value: number }
      clear_tool_inputs?: boolean | string[]; exclude_tools?: string[]
      clear_at_least?: { type: 'input_tokens'; value: number } }
  | { type: 'clear_thinking_20251015'; keep: { type: 'thinking_turns'; value: number } | 'all' }

export type ContextManagementConfig = { edits: ContextEditStrategy[] }

策略组合: (1) Thinking 清理 -- 保留全部 (> 1h 空闲时仅保留最后 1 个); (2) 工具结果清理 (ant-only, 触发 180K tokens, 目标保留 40K); (3) 工具调用清理 (ant-only, 排除 FileEdit/FileWrite/NotebookEdit)。

3.5 反应式压缩

反应式压缩 (reactive compact) 是 ant-only 的实验性策略, 在 API 返回 prompt-too-long 错误时触发。通过 feature flag REACTIVE_COMPACT 门控, 条件加载:

// query.ts 中的条件加载
const reactiveCompact = feature('REACTIVE_COMPACT')
  ? require('./services/compact/reactiveCompact.js')
  : null

与主动压缩相反, 反应式压缩等待 API 明确拒绝后才行动。利用 getPromptTooLongTokenGap() 计算超限 token 数, 一次跳过多个消息组而非逐个剥离。

tengu_cobalt_raccoon feature flag 开启时, 主动自动压缩被抑制, 完全依赖反应式压缩。

3.6 Session Memory 压缩 (sessionMemoryCompact.ts)

实验性功能, 将 session memory 作为压缩的摘要来源:

export type SessionMemoryCompactConfig = {
  minTokens: number     // 压缩后最少保留的 tokens
  // minTextMessages: 保留的最少文本消息数
}

与传统压缩的区别: 不调用 LLM 生成摘要, 而是使用已提取的 session memory 内容作为压缩后的上下文。


4. LSP 集成 (services/lsp/)

4.1 LSP Client (LSPClient.ts)

LSP Client 类型:

export type LSPClient = {
  readonly capabilities: ServerCapabilities | undefined
  readonly isInitialized: boolean
  start: (command: string, args: string[],
    options?: { env?: Record<string, string>; cwd?: string }) => Promise<void>
  initialize: (params: InitializeParams) => Promise<InitializeResult>
  sendRequest: <TResult>(method: string, params: unknown) => Promise<TResult>
  sendNotification: (method: string, params: unknown) => Promise<void>
  onNotification: (method: string, handler: (params: unknown) => void) => void
  stop: () => Promise<void>
}

崩溃恢复通过 onCrash 回调; 连接就绪前注册的 handler 进入 pending 队列。

4.2 LSP Server Manager (LSPServerManager.ts)

管理多个 LSP 服务器实例, 按文件扩展名路由请求。使用工厂函数 + 闭包模式封装状态:

export type LSPServerManager = {
  initialize(): Promise<void>
  shutdown(): Promise<void>
  getServerForFile(filePath: string): LSPServerInstance | undefined
  ensureServerStarted(filePath: string): Promise<LSPServerInstance | undefined>
  sendRequest<T>(filePath: string, method: string, params: unknown): Promise<T | undefined>
  getAllServers(): Map<string, LSPServerInstance>
  openFile(filePath: string, content: string): Promise<void>
  changeFile(filePath: string, content: string): Promise<void>
  saveFile(filePath: string): Promise<void>
  closeFile(filePath: string): Promise<void>
  isFileOpen(filePath: string): boolean
}

4.3 全局管理器与诊断 (manager.ts, LSPDiagnosticRegistry.ts)

单例模式管理 LSP 生命周期, 状态机: not-started -> pending -> success | failed。Bare mode 下跳过初始化。

诊断注册表存储异步 LSP 诊断通知, 使用 LRUCache (MAX_DELIVERED_FILES=500) 跨 turn 去重, 限制每文件 10 条、总计 30 条诊断。被动反馈 (passiveFeedback.ts) 将编译错误/警告自动注入对话上下文。


5. OAuth 流程 (services/oauth/)

5.1 OAuth Service (index.ts)

实现 OAuth 2.0 Authorization Code Flow with PKCE:

export class OAuthService {
  private codeVerifier: string
  private authCodeListener: AuthCodeListener | null = null
  private port: number | null = null
  private manualAuthCodeResolver: ((authorizationCode: string) => void) | null = null

  async startOAuthFlow(
    authURLHandler: (url: string, automaticUrl?: string) => Promise<void>,
    options?: {
      loginWithClaudeAi?: boolean
      inferenceOnly?: boolean
      expiresIn?: number
      orgUUID?: string
      loginHint?: string
      loginMethod?: string
      skipBrowserOpen?: boolean
    },
  ): Promise<OAuthTokens>
}

双路径认证:

  1. 自动流程: 打开浏览器 -> 重定向到 localhost callback -> 捕获 authorization code
  2. 手动流程: 用户复制粘贴 code (用于无浏览器环境)

SDK 控制协议使用 skipBrowserOpen, 将 URL 交给调用方处理。

5.2 OAuth Client (client.ts)

底层 HTTP 操作: buildAuthUrl() 构建授权 URL, exchangeCodeForTokens() 交换 token。

Scope 体系: claude_ai:inference (inference-only 长生命期 token) 和 ALL_OAUTH_SCOPES (完整 scope)。shouldUseClaudeAIAuth() 检查是否具有推理 scope。

PKCE 加密 (crypto.ts) 生成 code_verifier/code_challenge (S256) 和 state 参数。


6. 策略限制 (services/policyLimits/)

6.1 企业策略执行

从 API 获取组织级策略限制, 用于禁用特定 CLI 功能:

// types.ts
export const PolicyLimitsResponseSchema = z.object({
  restrictions: z.record(
    z.string(),
    z.object({ allowed: z.boolean() })
  ),
})

export type PolicyLimitsResponse = z.infer<
  ReturnType<typeof PolicyLimitsResponseSchema>
>

export type PolicyLimitsFetchResult = {
  success: boolean
  restrictions?: PolicyLimitsResponse['restrictions'] | null
  etag?: string
  error?: string
  skipRetry?: boolean
}

设计原则: Fail Open

API 失败时不阻塞, 继续运行不加限制。只有成功获取到限制时才执行。

资格判定:

用户类型 资格
Console (API key) 全部
OAuth Team 有资格
OAuth Enterprise/C4E 有资格
OAuth 其他 无资格

轮询与缓存: ETag 缓存 (304 时复用本地), session 级内存缓存, 1 小时轮询, 最多 5 次重试, 30 秒超时防死锁。initializePolicyLimitsLoadingPromise() 允许其他系统等待首次加载。


7. 远程管理设置 (services/remoteManagedSettings/)

企业客户的远程配置管理, 模式与 policyLimits 类似 (1h 轮询, 5 次重试, fail-open)。额外包含安全检查 (securityCheck.tsx) 和 checksum 验证最小化网络流量。syncCache.ts 管理缓存状态和用户资格判定。


8. 设置同步 (services/settingsSync/)

跨环境同步用户设置和 memory 文件 (10s 超时, 3 次重试, 500KB/文件上限):

环境 方向 触发时机
Interactive CLI 上传本地 -> 远程 preAction
CCR (Remote) 下载远程 -> 本地 插件安装前

增量同步, 需 OAuth 认证和 tengu_enable_settings_sync_push feature flag。


9. 其他服务模块

9.1 Tips 注册表 (services/tips/)

上下文感知的提示系统, 在合适时机向用户展示功能提示:

// tipRegistry.ts
type Tip = {
  // 提示内容、条件、优先级等
}

type TipContext = {
  bashTools?: // ...
}

条件系统: 每个 tip 基于 IDE 检测、终端类型、插件安装状态、用户配置、会话计数、feature flag 等多维条件决定是否显示。调度器控制频率, 历史记录跟踪上次显示时间。

9.2 Prompt Suggestion (services/PromptSuggestion/)

在用户等待时预测性地生成下一步建议:

export type PromptVariant = 'user_intent' | 'stated_intent'

export function shouldEnablePromptSuggestion(): boolean
// GrowthBook gate: tengu_chomp_inflection
// 非交互模式禁用
// Swarm teammate 禁用 (仅 leader 显示)

包含 speculation 子系统 (speculation.ts, 30KB), 在后台预执行可能的用户请求。

9.3 Agent Summary (services/AgentSummary/)

子 agent 的周期性进度摘要:

const SUMMARY_INTERVAL_MS = 30_000  // 每 30 秒

export function startAgentSummarization(
  taskId: string,
  agentId: AgentId,
  cacheSafeParams: CacheSafeParams,
  setAppState: TaskContext['setAppState'],
): { stop: () => void }

使用 runForkedAgent() fork 子 agent 对话, 生成 3-5 词的进度描述 (如 "Reading runAgent.ts"、"Fixing null check in validate.ts")。

为共享 prompt cache, 使用与父 agent 相同的 CacheSafeParams, 工具保留在请求中但通过 canUseTool 拒绝。

9.4 Auto Dream (services/autoDream/)

后台记忆巩固, 条件满足时自动触发 /dream prompt:

type AutoDreamConfig = {
  minHours: number    // 默认 24 小时
  minSessions: number // 默认 5 个会话
}

三级门控 (由廉到贵): 时间门 (>= minHours)、会话门 (>= minSessions)、锁门 (无并发巩固)。通过 tengu_onyx_plover GrowthBook config 控制阈值, 扫描节流 10 分钟。

9.5 Magic Docs (services/MagicDocs/)

自动维护特殊标记的 markdown 文档:

const MAGIC_DOC_HEADER_PATTERN = /^#\s*MAGIC\s+DOC:\s*(.+)$/im

export function detectMagicDocHeader(content: string): {
  title: string
  instructions?: string
} | null

当读取含 # MAGIC DOC: [title] 头部的文件时, 周期性地在后台使用 forked subagent 更新文档内容。支持可选的 italics 行作为更新指令。

9.6 Tool Use Summary (services/toolUseSummary/)

为 SDK 生成工具执行摘要:

export type GenerateToolUseSummaryParams = {
  tools: ToolInfo[]
  signal: AbortSignal
  isNonInteractiveSession: boolean
  lastAssistantText?: string
}

export async function generateToolUseSummary(
  params: GenerateToolUseSummaryParams,
): Promise<string | null>

使用 Haiku 模型生成简短的 (约 30 字符) 工具执行摘要, 用于移动 App 的单行进度显示。非关键路径, 失败时静默返回 null。

9.7 其他小型服务

  • Token 估算 (tokenEstimation.ts, 16KB): 粗略 token 计数, 用于微压缩等不需精确计数的场景
  • Rate Limit 消息 (rateLimitMessages.ts, 10KB): 根据限制类型 (429/529/overage) 生成人性化错误消息
  • Voice Service (voice.ts, 16KB): 语音输入, 含 STT 流处理 (voiceStreamSTT.ts, 20KB)
  • VCR (vcr.ts, 11KB): API 请求录制/回放, withStreamingVCR() / withVCR()
  • Diagnostic Tracking (diagnosticTracking.ts, 12KB): 代码诊断信息跟踪, 作为上下文注入会话
  • Claude.ai Limits (claudeAiLimits.ts, 16KB): 订阅速率限制状态管理, currentLimits() / extractQuotaStatusFromHeaders()

10. 服务间协作模式

10.1 GrowthBook 驱动的特性控制

几乎所有服务都依赖 GrowthBook 进行 feature flag 检查:

analytics/growthbook.ts
  <- compact/autoCompact.ts    (tengu_cobalt_raccoon -- 反应式压缩)
  <- compact/microCompact.ts   (时间维度配置)
  <- api/withRetry.ts          (tengu_disable_keepalive_on_econnreset)
  <- api/claude.ts             (tengu_anti_distill_fake_tool_injection)
  <- PromptSuggestion/         (tengu_chomp_inflection)
  <- autoDream/                (tengu_onyx_plover)
  <- settingsSync/             (tengu_enable_settings_sync_push)
  <- tips/tipRegistry.ts       (多个 gate)

10.2 forkedAgent 模式

多个服务使用 runForkedAgent() 在后台执行 LLM 查询, 共享 prompt cache:

服务 用途
compact 生成对话摘要
AgentSummary 生成子 agent 进度摘要
autoDream 后台记忆巩固
MagicDocs 自动更新标记文档
PromptSuggestion 预测性建议生成
SessionMemory session memory 提取

10.3 Fail-Open 模式

远程配置/策略服务统一采用 fail-open 设计:

policyLimits   -> 获取失败 -> 不限制
remoteManagedSettings -> 获取失败 -> 不加远程设置
settingsSync   -> 获取失败 -> 使用本地设置
bootstrap      -> 获取失败 -> 使用缓存
GrowthBook     -> 初始化前 -> 使用上次缓存值

10.4 ETag / Polling 模式

远程数据服务共享相同的轮询模式:

policyLimits       -> 1h 轮询, ETag 缓存, 5 次重试
remoteManagedSettings -> 1h 轮询, checksum, 5 次重试

11. 关键设计要点

  1. PII 防护贯穿始终: analytics 的类型系统强制显式标记, 防止意外泄露代码或文件路径
  2. 多层压缩策略: 从轻量 (微压缩/API 微压缩) 到重量 (全量压缩), 从主动 (阈值触发) 到被动 (错误触发), 形成完整的上下文管理梯队
  3. Build-time 死码消除: feature() 宏配合 bun:bundle 确保 ant-only 代码不出现在外部构建中
  4. AsyncGenerator 错误传播: withRetry 使用 generator 在重试等待期间向调用方 yield 状态消息, 不阻塞 UI 更新
  5. 连续失败熔断: 自动压缩的 3 次连续失败上限源于真实生产数据 (BQ 2026-03-10), 防止无效 API 调用浪费