2.8 配置项:constants/ 全局常量体系

源码文件constants/ 目录下 21 个文件(总计 ~100KB)

核心概念:全局常量分类学、DAG 叶子约束、Feature Flag 编译时消除、System Prompt 装配、OAuth 多环境配置、缓存稳定性设计


导语:为什么需要 21 个常量文件?

原书第 2 章在讨论 bootstrap/state.ts 的 DAG 叶子约束时提到:

如果模块依赖图中存在一个被广泛引用的节点(比如全局状态),那么这个节点必须是叶子节点(不引用其他业务节点),否则它会成为循环依赖的枢纽。

constants/ 目录正是这一原则的另一面——它是一组领域级叶子节点,被整个代码库广泛引用,但自身的依赖被严格约束。与 bootstrap/state.ts 不同的是,constants/ 中的文件并非完全无依赖(部分文件导入 feature()envsettings 等基础设施模块),但它们遵循一个共同的原则:不导入业务逻辑模块

这 21 个文件按领域可以分为 7 大类:

领域 文件 大小 职责
System Prompt prompts.ts 55KB / 914行 整个 System Prompt 装配
system.ts 4.0KB CLI 前缀 + 归因头
systemPromptSections.ts 1.9KB Prompt 章节缓存框架
cyberRiskInstruction.ts 1.5KB 安全指令(Safeguards 团队拥有)
outputStyles.ts 10KB 输出风格配置
API 与限制 apiLimits.ts 3.5KB 图像/PDF/媒体 API 限制
toolLimits.ts 2.2KB 工具结果大小限制
betas.ts 2.3KB Beta Header 字符串
认证 oauth.ts 9.2KB OAuth 多环境配置
github-app.ts 5.5KB GitHub Actions 模板
keys.ts 455B GrowthBook SDK 密钥
工具配置 tools.ts 4.8KB Agent 工具白名单
xml.ts 3.4KB XML 标签名
UI 显示 figures.ts 2.1KB Unicode 符号
spinnerVerbs.ts 3.7KB 200+ Spinner 动词
turnCompletionVerbs.ts 281B 回合完成动词
基础设施 files.ts 2.8KB 二进制文件检测
common.ts 1.5KB 日期工具
product.ts 2.6KB 产品 URL
errorIds.ts 491B 错误 ID
messages.ts 50B 单常量文件

一、System Prompt 装配体系(5 个文件,~73KB)

1.1 prompts.ts — System Prompt 的总装配线(914 行)

这是 constants/ 目录中最大的文件,也是整个 Claude Code 中最关键的文件之一。原书附录 C 在讨论 System Prompt 三层管线时描述了"静态层 → 动态章节 → 用户指令"的架构,这个文件就是那套管线的完整实现。

1.1.1 文件结构与关键函数
prompts.ts (914行)
├── 导入区 (1-99行) — 60+ 导入,含 feature() 条件导入
├── 常量定义 (102-126行) — 文档 URL、边界标记、模型名
├── 静态章节函数 (127-442行) — 7 个章节构造器
│   ├── getHooksSection()
│   ├── getSystemRemindersSection()
│   ├── getSimpleIntroSection() — 含 CYBER_RISK_INSTRUCTION
│   ├── getSimpleSystemSection()
│   ├── getSimpleDoingTasksSection() — 代码风格 + ant 专属指令
│   ├── getActionsSection() — 不可逆操作确认
│   ├── getUsingYourToolsSection() — 工具使用指导
│   ├── getAgentToolSection() — Agent 工具指导
│   ├── getSimpleToneAndStyleSection()
│   └── getOutputEfficiencySection() — ant vs 外部用户不同版本
├── 动态章节函数 (400-600行) — 会话特定指导
│   ├── getSessionSpecificGuidanceSection()
│   └── getDiscoverSkillsGuidance()
├── getSystemPrompt() 主函数 (444-577行) — ⭐核心
├── computeSimpleEnvInfo() (651-710行) — 环境信息
├── enhanceSystemPromptWithEnvDetails() (760-791行) — 子 Agent Prompt 增强
├── getScratchpadInstructions() (797-819行) — 暂存目录指导
├── getFunctionResultClearingSection() (821-839行) — FRC 指导
└── getBriefSection() / getProactiveSection() (843-914行) — Feature Gated 章节
1.1.2 getSystemPrompt() — 核心装配函数
// prompts.ts 第444-577行
export async function getSystemPrompt(
  tools: Tools,
  model: string,
  additionalWorkingDirectories?: string[],
  mcpClients?: MCPServerConnection[],
): Promise<string[]> {
  // 1. Simple 模式快速路径(CLAUDE_CODE_SIMPLE 环境变量)
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
    return [`You are Claude Code, ...\nCWD: ${getCwd()}\nDate: ${getSessionStartDate()}`]
  }

  // 2. Proactive 模式独立路径
  if ((feature('PROACTIVE') || feature('KAIROS')) && proactiveModule?.isProactiveActive()) {
    return [/* 自主 Agent 精简 Prompt */]
  }

  // 3. 标准路径:并行预取 + 动态章节注册
  const [skillToolCommands, outputStyleConfig, envInfo] = await Promise.all([
    getSkillToolCommands(cwd),
    getOutputStyleConfig(),
    computeSimpleEnvInfo(model, additionalWorkingDirectories),
  ])

  // 4. 动态章节注册(systemPromptSection = 缓存,DANGEROUS_uncached = 每轮重算)
  const dynamicSections = [
    systemPromptSection('session_guidance', () => getSessionSpecificGuidanceSection(...)),
    systemPromptSection('memory', () => loadMemoryPrompt()),
    systemPromptSection('env_info_simple', () => computeSimpleEnvInfo(...)),
    systemPromptSection('language', () => getLanguageSection(...)),
    systemPromptSection('output_style', () => getOutputStyleSection(...)),
    DANGEROUS_uncachedSystemPromptSection('mcp_instructions', ..., 'MCP servers connect/disconnect between turns'),
    systemPromptSection('scratchpad', () => getScratchpadInstructions()),
    systemPromptSection('frc', () => getFunctionResultClearingSection(model)),
    systemPromptSection('summarize_tool_results', () => SUMMARIZE_TOOL_RESULTS_SECTION),
    // ... Feature Gated 章节
  ]

  const resolvedDynamicSections = await resolveSystemPromptSections(dynamicSections)

  // 5. 最终装配:静态章节 → 边界标记 → 动态章节
  return [
    getSimpleIntroSection(outputStyleConfig),    // 静态
    getSimpleSystemSection(),                     // 静态
    getSimpleDoingTasksSection(),                 // 静态
    getActionsSection(),                          // 静态
    getUsingYourToolsSection(enabledTools),       // 静态
    getSimpleToneAndStyleSection(),               // 静态
    getOutputEfficiencySection(),                 // 静态
    SYSTEM_PROMPT_DYNAMIC_BOUNDARY,               // === 边界标记 ===
    ...resolvedDynamicSections,                   // 动态
  ].filter(s => s !== null)
}

关键设计

  1. 静态/动态边界标记SYSTEM_PROMPT_DYNAMIC_BOUNDARY(第114行)是一个魔法字符串 '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__',它将 System Prompt 数组分为两部分——边界之前的内容可以跨组织缓存(scope: 'global'),边界之后的内容包含用户/会话特定信息,不可缓存。原书附录 C 描述的"静态层(global 缓存)→ 动态章节(session 缓存)"正是这个边界。

  2. 三种 Prompt 路径

    • Simple 模式CLAUDE_CODE_SIMPLE):极简 Prompt,仅包含身份声明 + CWD + 日期
    • Proactive 模式feature('PROACTIVE')):自主 Agent 精简 Prompt
    • 标准模式:完整的 7 静态章节 + N 动态章节
  3. 并行预取Promise.all([getSkillToolCommands, getOutputStyleConfig, computeSimpleEnvInfo]) 并行执行三个独立的预取操作。

1.1.3 @[MODEL LAUNCH] 标记系统

prompts.ts 中散布着多个 @[MODEL LAUNCH] 注释,标记了需要在模型升级时更新的位置:

// @[MODEL LAUNCH]: Update the latest frontier model.
const FRONTIER_MODEL_NAME = 'Claude Opus 4.6'

// @[MODEL LAUNCH]: Update the model family IDs below to the latest in each tier.
const CLAUDE_4_5_OR_4_6_MODEL_IDS = {
  opus: 'claude-opus-4-6',
  sonnet: 'claude-sonnet-4-6',
  haiku: 'claude-haiku-4-5-20251001',
}

// @[MODEL LAUNCH]: Remove this section when we launch numbat.
function getOutputEfficiencySection(): string { ... }

// @[MODEL LAUNCH]: capy v8 assertiveness counterweight (PR #24302) — un-gate once validated on external via A/B
// @[MODEL LAUNCH]: False-claims mitigation for Capybara v8 (29-30% FC rate vs v4's 16.7%)
// @[MODEL LAUNCH]: Update comment writing for Capybara — remove or soften once the model stops over-commenting by default

这些标记是模型迁移的检查点——当新模型发布时,开发者通过搜索 @[MODEL LAUNCH] 找到所有需要更新的位置。其中 capy v8 相关的标记特别有趣:它们记录了针对特定模型版本的"对抗性指令"(如防止虚假声明、防止过度注释),这些指令在模型行为改善后应该被移除。

1.1.4 Ant-Only 门控内容

大量内容通过 process.env.USER_TYPE === 'ant' 门控,仅对 Anthropic 内部用户生效:

门控内容 位置 目的
代码风格强化指令 第205-213行 ant 用户要求更严格的注释规范
断言性对抗指令 第225-229行 鼓励 ant 用户指出用户的误解
虚假声明防护 第238-242行 防止 ant 用户虚假报告"全部通过"
验证要求 第211行 ant 用户必须运行验证
Bug 报告引导 第243-247行 引导使用 /issue 或 /share
数字长度锚点 第529-537行 “~25 词” 量化约束(实验性)
通信风格 第404-414行 更详细的 prose 风格指导
Undercover 模式 第660行 隐藏模型名称和 ID

1.2 system.ts — CLI 前缀与归因头

// system.ts 第10-12行 — 三种 CLI 前缀
const DEFAULT_PREFIX = `You are Claude Code, Anthropic's official CLI for Claude.`
const AGENT_SDK_CLAUDE_CODE_PRESET_PREFIX = `You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK.`
const AGENT_SDK_PREFIX = `You are a Claude agent, built on Anthropic's Claude Agent SDK.`

getCLISyspromptPrefix() 根据运行环境选择前缀:

  • Vertex AI:始终使用 DEFAULT_PREFIX
  • 非交互式 + 有 AppendSystemPromptAGENT_SDK_CLAUDE_CODE_PRESET_PREFIX
  • 非交互式 + 无 AppendSystemPromptAGENT_SDK_PREFIX
  • 交互式(默认)DEFAULT_PREFIX

归因头getAttributionHeader())是一个精心设计的安全机制:

// system.ts 第73-95行
export function getAttributionHeader(fingerprint: string): string {
  const version = `${MACRO.VERSION}.${fingerprint}`
  const entrypoint = process.env.CLAUDE_CODE_ENTRYPOINT ?? 'unknown'
  // cch=00000 占位符会被 Bun 的 HTTP 栈覆盖为 attestation token
  const cch = feature('NATIVE_CLIENT_ATTESTATION') ? ' cch=00000;' : ''
  const workload = getWorkload()
  const workloadPair = workload ? ` cc_workload=${workload};` : ''
  const header = `x-anthropic-billing-header: cc_version=${version}; cc_entrypoint=${entrypoint};${cch}${workloadPair}`
  return header
}

Native Client Attestationcch=00000 是一个固定长度的占位符,Bun 的原生 HTTP 栈在发送请求体时找到这个占位符并用计算出的 attestation hash 覆盖零值。服务器验证此 token 以确认请求来自真实的 Claude Code 客户端。使用占位符而非从 Zig 注入的原因是:等长替换避免了 Content-Length 变化和缓冲区重分配

1.3 systemPromptSections.ts — Prompt 章节缓存框架

这是一个仅 69 行的精巧框架,提供了两种章节类型:

// 缓存型:计算一次,缓存到 /clear 或 /compact
export function systemPromptSection(name: string, compute: ComputeFn): SystemPromptSection {
  return { name, compute, cacheBreak: false }
}

// 危险非缓存型:每轮重新计算,会破坏 prompt cache
export function DANGEROUS_uncachedSystemPromptSection(
  name: string,
  compute: ComputeFn,
  _reason: string,  // 强制提供理由
): SystemPromptSection {
  return { name, compute, cacheBreak: true }
}

命名约定DANGEROUS_ 前缀是一种代码评审信号——开发者看到这个前缀就知道此处会破坏 prompt cache,需要格外谨慎。_reason 参数(带下划线前缀表示未使用)强制开发者写下为什么需要破坏缓存的理由。

当前唯一使用 DANGEROUS_uncachedSystemPromptSection 的是 MCP 指令章节:

DANGEROUS_uncachedSystemPromptSection(
  'mcp_instructions',
  () => isMcpInstructionsDeltaEnabled() ? null : getMcpInstructionsSection(mcpClients),
  'MCP servers connect/disconnect between turns',
)

1.4 cyberRiskInstruction.ts — 安全指令(Safeguards 团队拥有)

/**
 * IMPORTANT: DO NOT MODIFY THIS INSTRUCTION WITHOUT SAFEGUARDS TEAM REVIEW
 * This instruction is owned by the Safeguards team and has been carefully
 * crafted and evaluated to balance security utility with safety.
 * If you need to modify this instruction:
 *   1. Contact the Safeguards team (David Forsythe, Kyla Guru)
 *   2. Ensure proper evaluation of the changes
 *   3. Get explicit approval before merging
 */
export const CYBER_RISK_INSTRUCTION = `IMPORTANT: Assist with authorized security testing, 
defensive security, CTF challenges, and educational contexts. Refuse requests for destructive 
techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for 
malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit 
development) require clear authorization context: pentesting engagements, CTF competitions, 
security research, or defensive use cases.`

这是整个代码库中唯一一个有明确团队所有权和修改审批流程的常量。它被嵌入 System Prompt 的开头位置(getSimpleIntroSection() 第182行),确保所有安全相关请求都遵循这条边界。

1.5 outputStyles.ts — 输出风格配置

定义了三种内置输出风格:

风格 描述 特性
default 无额外 Prompt null
Explanatory 解释实现选择和代码库模式 keepCodingInstructions: true
Learning 暂停并要求用户编写小段代码 keepCodingInstructions: true,含 TODO(human) 机制

Learning 风格特别有趣——它会要求用户手动编写 2-10 行代码片段,在实践中学习。这通过在代码中插入 TODO(human) 标记实现,Claude 在此标记处暂停等待用户输入。

getAllOutputStyles() 使用 memoize 缓存,并支持从 4 个来源加载自定义风格:

优先级(低→高):built-in → plugin → user → project → managed(policy)

二、API 限制与 Beta Header(3 个文件,~8KB)

2.1 apiLimits.ts — API 服务端限制

// 文件头部的关键注释
/**
 * These constants define server-side limits enforced by the Anthropic API.
 * Keep this file dependency-free to prevent circular imports.
 * Last verified: 2025-12-22
 * Source: api/api/schemas/messages/blocks/ and api/api/config.py
 * Future: See issue #13240 for dynamic limits fetching from server.
 */

注意三点

  1. “Keep this file dependency-free” — 与 bootstrap/state.ts 的 DAG 叶子约束一脉相承,这是另一个叶子节点
  2. “Last verified: 2025-12-22” — 带日期的验证标记,表明这些限制需要定期与服务端同步
  3. “Future: See issue #13240” — 预留了动态限制获取的路线图
常量 说明
API_IMAGE_MAX_BASE64_SIZE 5 MB Base64 编码后最大图像大小
IMAGE_TARGET_RAW_SIZE 3.75 MB 原始图像大小上限(5MB × 3/4)
IMAGE_MAX_WIDTH/HEIGHT 2000px 客户端缩放上限(服务端 1568px)
PDF_TARGET_RAW_SIZE 20 MB PDF 原始大小上限
API_PDF_MAX_PAGES 100 PDF 最大页数
PDF_EXTRACT_SIZE_THRESHOLD 3 MB 超过此大小转为页面图像提取
PDF_MAX_EXTRACT_SIZE 100 MB 页面提取路径的绝对上限
API_MAX_MEDIA_PER_REQUEST 100 每请求最大媒体数

2.2 toolLimits.ts — 工具结果大小限制

export const DEFAULT_MAX_RESULT_SIZE_CHARS = 50_000       // 单工具结果上限
export const MAX_TOOL_RESULT_TOKENS = 100_000             // Token 上限
export const BYTES_PER_TOKEN = 4                           // 保守估算
export const MAX_TOOL_RESULT_BYTES = MAX_TOOL_RESULT_TOKENS * BYTES_PER_TOKEN  // 400KB
export const MAX_TOOL_RESULTS_PER_MESSAGE_CHARS = 200_000 // 单消息聚合上限
export const TOOL_SUMMARY_MAX_LENGTH = 50                  // 摘要截断长度

两层限制体系

  • 单工具限制(50K chars):单个工具结果超过此值时,持久化到磁盘,模型只收到预览 + 文件路径
  • 单消息聚合限制(200K chars):一轮中 N 个并行工具的结果总和超此值时,最大的几个被持久化

注释明确指出这是为了防止"N 个并行工具各自达到单工具上限,合计产生 10 × 40K = 400K"的情况。此限制可通过 GrowthBook flag tengu_hawthorn_window 运行时覆盖。

2.3 betas.ts — Beta Header 注册表

定义了 17 个 Beta Header 字符串,每个对应一个 API Beta 功能:

export const CLAUDE_CODE_20250219_BETA_HEADER = 'claude-code-20250219'
export const INTERLEAVED_THINKING_BETA_HEADER = 'interleaved-thinking-2025-05-14'
export const CONTEXT_1M_BETA_HEADER = 'context-1m-2025-08-07'
export const CONTEXT_MANAGEMENT_BETA_HEADER = 'context-management-2025-06-27'
export const STRUCTURED_OUTPUTS_BETA_HEADER = 'structured-outputs-2025-12-15'
export const EFFORT_BETA_HEADER = 'effort-2025-11-24'
export const TASK_BUDGETS_BETA_HEADER = 'task-budgets-2026-03-13'
export const FAST_MODE_BETA_HEADER = 'fast-mode-2026-02-01'
export const REDACT_THINKING_BETA_HEADER = 'redact-thinking-2026-02-12'
export const TOKEN_EFFICIENT_TOOLS_BETA_HEADER = 'token-efficient-tools-2026-03-28'
// ...

命名约定{feature-name}-{YYYY-MM-DD},日期是 Beta 发布日期。

Feature Flag 条件消除:部分 Beta Header 使用 feature() 门控:

export const SUMMARIZE_CONNECTOR_TEXT_BETA_HEADER = feature('CONNECTOR_TEXT')
  ? 'summarize-connector-text-2026-03-13'
  : ''

export const AFK_MODE_BETA_HEADER = feature('TRANSCRIPT_CLASSIFIER')
  ? 'afk-mode-2026-01-31'
  : ''

export const CLI_INTERNAL_BETA_HEADER =
  process.env.USER_TYPE === 'ant' ? 'cli-internal-2026-02-09' : ''

feature() 返回 false 时,构建时 Bun 的 bundler 会将整个三元表达式内联为空字符串,实现编译时死代码消除

Bedrock 兼容性:部分 Beta Header 在 Bedrock 上不支持通过 HTTP header 传递,需要放在 extraBodyParams 中:

export const BEDROCK_EXTRA_PARAMS_HEADERS = new Set([
  INTERLEAVED_THINKING_BETA_HEADER,
  CONTEXT_1M_BETA_HEADER,
  TOOL_SEARCH_BETA_HEADER_3P,
])

三、OAuth 认证配置(3 个文件,~15KB)

3.1 oauth.ts — 三环境 OAuth 配置

这是 constants/ 中最复杂的配置文件,支持四种运行环境:

type OauthConfigType = 'prod' | 'staging' | 'local'

function getOauthConfigType(): OauthConfigType {
  if (process.env.USER_TYPE === 'ant') {           // 仅内部用户
    if (isEnvTruthy(process.env.USE_LOCAL_OAUTH)) return 'local'
    if (isEnvTruthy(process.env.USE_STAGING_OAUTH)) return 'staging'
  }
  return 'prod'  // 外部用户始终使用生产环境
}
环境 BASE_API_URL CLIENT_ID 文件后缀 触发条件
prod api.anthropic.com 9d1c250a-... (无) 默认
staging api-staging.anthropic.com 22422756-... -staging-oauth ant + USE_STAGING_OAUTH
local localhost:8000 22422756-... -local-oauth ant + USE_LOCAL_OAUTH
FedStart 自定义 自定义 -custom-oauth CLAUDE_CODE_CUSTOM_OAUTH_URL

FedStart 安全白名单

const ALLOWED_OAUTH_BASE_URLS = [
  'https://beacon.claude-ai.staging.ant.dev',
  'https://claude.fedstart.com',
  'https://claude-staging.fedstart.com',
]

只有这三个 URL 被允许作为自定义 OAuth 端点,防止 OAuth token 被发送到任意端点。如果 URL 不在白名单中,直接 throw new Error

OAuth Scope 分层

// Console OAuth scopes — API key 创建
export const CONSOLE_OAUTH_SCOPES = [
  'org:create_api_key',
  'user:profile',
] as const

// Claude.ai OAuth scopes — Pro/Max/Team/Enterprise 订阅者
export const CLAUDE_AI_OAUTH_SCOPES = [
  'user:profile',
  'user:inference',
  'user:sessions:claude_code',
  'user:mcp_servers',
  'user:file_upload',
] as const

// 登录时请求所有 scope(并集)
export const ALL_OAUTH_SCOPES = Array.from(
  new Set([...CONSOLE_OAUTH_SCOPES, ...CLAUDE_AI_OAUTH_SCOPES]),
)

3.2 github-app.ts — GitHub Actions 工作流模板

包含两个完整的 GitHub Actions workflow YAML 模板:

  • WORKFLOW_CONTENT:标准 Claude Code 集成(@claude mention 触发)
  • CODE_REVIEW_PLUGIN_WORKFLOW_CONTENT:代码审查插件(PR 打开/更新时自动触发)

以及 PR 标题和 PR Body 模板,用于 claude github-action 命令。

3.3 keys.ts — GrowthBook SDK 密钥

export function getGrowthBookClientKey(): string {
  return process.env.USER_TYPE === 'ant'
    ? isEnvTruthy(process.env.ENABLE_GROWTHBOOK_DEV)
      ? 'sdk-yZQvlplybuXjYh6L'  // ant + dev
      : 'sdk-xRVcrliHIlrg4og4'  // ant + prod
    : 'sdk-zAZezfDKGoZuXXKe'    // external
}

三个 SDK 密钥对应三种用户群体。注释说明 ENABLE_GROWTHBOOK_DEV 来自 globalSettings.env(在模块加载后应用),因此使用函数而非常量来延迟读取。


四、工具配置(2 个文件,~8KB)

4.1 tools.ts — Agent 工具白名单矩阵

定义了三种 Agent 类型的工具访问权限:

// 所有 Agent 禁止使用的工具(递归防护)
export const ALL_AGENT_DISALLOWED_TOOLS = new Set([
  TASK_OUTPUT_TOOL_NAME,        // 防止递归
  EXIT_PLAN_MODE_V2_TOOL_NAME,  // Plan mode 是主线程抽象
  ENTER_PLAN_MODE_TOOL_NAME,
  ASK_USER_QUESTION_TOOL_NAME,  // 防止子 Agent 打断用户
  TASK_STOP_TOOL_NAME,          // 需要主线程任务状态
  // ant 用户允许嵌套 Agent,外部用户禁止
  ...(process.env.USER_TYPE === 'ant' ? [] : [AGENT_TOOL_NAME]),
])

// 异步 Agent 允许使用的工具(白名单模式)
export const ASYNC_AGENT_ALLOWED_TOOLS = new Set([
  FILE_READ_TOOL_NAME, WEB_SEARCH_TOOL_NAME, TODO_WRITE_TOOL_NAME,
  GREP_TOOL_NAME, WEB_FETCH_TOOL_NAME, GLOB_TOOL_NAME,
  ...SHELL_TOOL_NAMES, FILE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME,
  NOTEBOOK_EDIT_TOOL_NAME, SKILL_TOOL_NAME, SYNTHETIC_OUTPUT_TOOL_NAME,
  TOOL_SEARCH_TOOL_NAME, ENTER_WORKTREE_TOOL_NAME, EXIT_WORKTREE_TOOL_NAME,
])

// 进程内 Teammate 额外允许的工具
export const IN_PROCESS_TEAMMATE_ALLOWED_TOOLS = new Set([
  TASK_CREATE_TOOL_NAME, TASK_GET_TOOL_NAME, TASK_LIST_TOOL_NAME,
  TASK_UPDATE_TOOL_NAME, SEND_MESSAGE_TOOL_NAME,
  ...(feature('AGENT_TRIGGERS') ? [CRON_CREATE_TOOL_NAME, ...] : []),
])

// Coordinator 模式允许的工具(仅输出和 Agent 管理)
export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
  AGENT_TOOL_NAME, TASK_STOP_TOOL_NAME, SEND_MESSAGE_TOOL_NAME,
  SYNTHETIC_OUTPUT_TOOL_NAME,
])

设计模式:黑名单 + 白名单 + 角色特化

  • ALL_AGENT_DISALLOWED_TOOLS:黑名单(所有 Agent 都不能用的工具)
  • ASYNC_AGENT_ALLOWED_TOOLS:白名单(异步 Agent 只能用这些)
  • IN_PROCESS_TEAMMATE_ALLOWED_TOOLS:角色特化(进程内 Teammate 额外允许任务管理工具)
  • COORDINATOR_MODE_ALLOWED_TOOLS:角色特化(Coordinator 只做调度,不做实现)

4.2 xml.ts — XML 标签命名空间

定义了 Claude Code 在消息中使用的一切 XML 标签名,覆盖 6 个领域:

领域 标签 用途
命令元数据 command-name, command-message, command-args Slash 命令展开
终端 I/O bash-input, bash-stdout, bash-stderr, local-command-stdout/stderr/caveat 终端活动标记
任务通知 task-notification, task-id, tool-use-id, task-type, output-file, status, summary, reason 后台任务完成通知
工作树 worktree, worktreePath, worktreeBranch Git 工作树信息
Agent 通信 teammate-message, channel-message, channel, cross-session-message 多 Agent 消息
Fork 指令 fork-boilerplate, fork-directive-prefix Fork 子进程的首条消息

还定义了帮助参数模式:

export const COMMON_HELP_ARGS = ['help', '-h', '--help']
export const COMMON_INFO_ARGS = ['list', 'show', 'display', 'current', 'view', 'get', 'check', 'describe', 'print', 'version', 'about', 'status', '?']

这些用于 Slash 命令路由——当用户输入 /mcp list 时,系统通过 COMMON_INFO_ARGS 识别这是一个信息查询命令。


五、UI 显示常量(3 个文件,~6KB)

5.1 figures.ts — Unicode 符号库

// 平台自适应:macOS 使用 ⏺(垂直对齐更好),Windows/Linux 使用 ●
export const BLACK_CIRCLE = env.platform === 'darwin' ? '⏺' : '●'

// 努力等级指示器
export const EFFORT_LOW = '○'    // \u25cb
export const EFFORT_MEDIUM = '◐'  // \u25d0
export const EFFORT_HIGH = '●'    // \u25cf
export const EFFORT_MAX = '◉'     // \u25c9 — Opus 4.6 only

// Bridge 状态
export const BRIDGE_SPINNER_FRAMES = ['·|·', '·/·', '·—·', '·\·']
export const BRIDGE_READY_INDICATOR = '·✔·'
export const BRIDGE_FAILED_INDICATOR = '×'

这些 Unicode 符号被终端渲染引擎(ink/)使用,原书第 11 章"终端 React 流水线"中描述的渲染管线最终输出这些符号。

5.2 spinnerVerbs.ts — 200+ Spinner 动词

这是整个代码库中最"轻松"的文件——200+ 个现在分词动词,用于加载消息的随机选择:

Accomplishing, Actioning, Actualizing, Architecting, Baking, Beaming, 
Beboppin', Befuddling, Billowing, Blanching, Bloviating, Boogieing, 
Boondoggling, Booping, Bootstrapping, Brewing, Bunning, Burrowing, 
Calculating, Canoodling, Caramelizing, Cascading, Catapulting, 
Cerebrating, Channeling, Channelling, Choreographing, Churning, 
Clauding, ...

getSpinnerVerbs() 支持用户通过配置扩展:replace 模式完全替换,默认模式追加。

5.3 turnCompletionVerbs.ts — 回合完成动词

export const TURN_COMPLETION_VERBS = [
  'Baked', 'Brewed', 'Churned', 'Cogitated', 'Cooked', 'Crunched',
  'Sautéed', 'Worked',
]

过去时形式,用于"Worked for 5s"这样的完成消息。与 spinnerVerbs.ts 的现在分词形成对照。


六、基础设施常量(5 个文件,~7KB)

6.1 files.ts — 二进制文件检测

定义了 80+ 个二进制文件扩展名(BINARY_EXTENSIONS),以及两个检测函数:

// 扩展名检测:O(1) Set 查找
export function hasBinaryExtension(filePath: string): boolean {
  const ext = filePath.slice(filePath.lastIndexOf('.')).toLowerCase()
  return BINARY_EXTENSIONS.has(ext)
}

// 内容检测:检查前 8192 字节
export function isBinaryContent(buffer: Buffer): boolean {
  const checkSize = Math.min(buffer.length, 8192)
  let nonPrintable = 0
  for (let i = 0; i < checkSize; i++) {
    const byte = buffer[i]!
    if (byte === 0) return true  // null 字节 = 强二进制信号
    if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) {
      nonPrintable++
    }
  }
  return nonPrintable / checkSize > 0.1  // 10% 阈值
}

两层检测策略:先用扩展名快速排除(O(1)),再用内容扫描确认(O(n) but bounded to 8KB)。

6.2 common.ts — 日期工具(缓存稳定性)

// 会话开始日期——memoized 以保持 prompt cache 稳定性
export const getSessionStartDate = memoize(getLocalISODate)

为什么需要 memoize? 注释解释了关键原因:

The main interactive path gets this behavior via memoize(getUserContext) in context.ts; simple mode (–bare) calls getSystemPrompt per-request and needs an explicit memoized date to avoid busting the cached prefix at midnight.

如果日期在会话中途变化(午夜跨越),prompt cache 会被破坏。memoize 确保日期在会话开始时固定,直到会话结束。Simple 模式(--bare)每次请求都调用 getSystemPrompt,因此特别需要这个显式 memoize。

还支持 ant 专属的日期覆盖:CLAUDE_CODE_OVERRIDE_DATE 环境变量。

6.3 product.ts — 产品 URL 与远程会话路由

export const PRODUCT_URL = 'https://claude.com/claude-code'
export const CLAUDE_AI_BASE_URL = 'https://claude.ai'
export const CLAUDE_AI_STAGING_BASE_URL = 'https://claude-ai.staging.ant.dev'
export const CLAUDE_AI_LOCAL_BASE_URL = 'http://localhost:4000'

getClaudeAiBaseUrl() 根据会话 ID 和 ingress URL 判断环境:

export function getClaudeAiBaseUrl(sessionId?: string, ingressUrl?: string): string {
  if (isRemoteSessionLocal(sessionId, ingressUrl)) return CLAUDE_AI_LOCAL_BASE_URL
  if (isRemoteSessionStaging(sessionId, ingressUrl)) return CLAUDE_AI_STAGING_BASE_URL
  return CLAUDE_AI_BASE_URL
}

getRemoteSessionUrl() 中的 cse_→session_ 转换是一个临时 shim,由 GrowthBook flag tengu_bridge_repl_v2_cse_shim_enabled 控制。注释详细记录了这个 shim 的原因和移除条件——这种"自带移除说明"的注释风格是代码库的一个优良传统。

6.4 errorIds.ts — 错误 ID 序号系统

/**
 * These errors are represented as individual const exports for optimal
 * dead code elimination (external build will only see the numbers).
 * ADDING A NEW ERROR TYPE:
 * 1. Add a const based on Next ID.
 * 2. Increment Next ID.
 * Next ID: 346
 */
export const E_TOOL_USE_SUMMARY_GENERATION_FAILED = 344

设计要点

  • 每个错误 ID 是独立的 const 导出(而非枚举),这样 bundler 可以做死代码消除——外部构建只会看到用到的数字
  • 使用显式的 “Next ID” 追踪,避免 ID 冲突
  • 文件中目前只有一个错误 ID(344),说明大量错误 ID 已被定义但可能在其他文件中

6.5 messages.ts — 单常量文件

export const NO_CONTENT_MESSAGE = '(no content)'

整个文件只有这一行。这种"一个常量一个文件"的做法看似过度拆分,但实际上是为了保持 DAG 叶子约束——这个常量被多个高层模块导入,如果放在更大的文件中可能引入不必要的依赖。


七、提炼的设计模式

模式 1:DAG 叶子约束的领域级应用

apiLimits.ts 的文件头注释明确写道:

Keep this file dependency-free to prevent circular imports.

这与 bootstrap/state.ts 的 DAG 叶子约束是同一原则的不同应用——bootstrap/state.ts 是全局状态的叶子节点,而 constants/ 中的文件是领域常量的叶子节点。两者都被 200+ 文件导入,自身不导入业务模块。

模式 2:Feature Flag 编译时消除

betas.tsprompts.ts 大量使用 feature() from bun:bundle

export const SUMMARIZE_CONNECTOR_TEXT_BETA_HEADER = feature('CONNECTOR_TEXT')
  ? 'summarize-connector-text-2026-03-13'
  : ''

feature() 在构建时被内联为布尔常量,bundler 随后做死代码消除——不需要的 Beta Header 字符串字面量在最终产物中完全消失。这使得外部构建不包含内部 Beta 功能的任何痕迹。

模式 3:Memoized Cache Stability

common.ts 中的 getSessionStartDate = memoize(getLocalISODate) 是 prompt cache 稳定性的关键保障。原书附录 C 提到 System Prompt 的"静态层使用 global 缓存"——如果日期在会话中途变化,整个静态层缓存会失效。memoize 确保日期在会话级别固定。

模式 4:Safeguards Team Ownership

cyberRiskInstruction.ts 是代码库中唯一有明确团队所有权和修改审批流程的常量。这种"代码所有权"模式在安全关键代码中尤为重要——它确保安全指令的修改经过专业评审,不会被无意间弱化。

模式 5:Static/Dynamic Boundary Marker

prompts.ts 中的 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 是一个魔法字符串,将 System Prompt 数组分为可缓存和不可缓存两部分。这个标记被 utils/api.tsservices/api/claude.ts 引用,用于决定 prompt cache 的 scope。原书附录 C 描述的"静态层(global 缓存)→ 动态章节(session 缓存)"架构正是通过这个标记实现的。

模式 6:DANGEROUS_ 命名约定

DANGEROUS_uncachedSystemPromptSection() 通过命名约定传递设计意图——DANGEROUS_ 前缀警告开发者此函数会破坏 prompt cache,_reason 参数强制提供理由。这是一种"通过命名而非注释传递架构约束"的模式。

模式 7:@[MODEL LAUNCH] 迁移标记

prompts.ts 中的 @[MODEL LAUNCH] 注释是模型迁移的检查点。当新模型发布时,开发者搜索此标记找到所有需要更新的位置。部分标记还记录了针对特定模型版本的对抗性指令(如 capy v8 的虚假声明防护),在模型行为改善后应被移除。这是一种"将迁移点编码到源码中"的模式。

模式 8:Ant-Only 门控分层

大量常量通过 process.env.USER_TYPE === 'ant' 门控,形成内部用户和外部用户的功能分层。这不是简单的 feature flag——它是一个编译时可知的条件(通过 Bun 的 define 注入),因此可以参与死代码消除。外部构建中完全不存在 ant 专属的代码路径和字符串字面量。

模式 9:环境驱动的多环境配置

oauth.tsgetOauthConfig() 支持 prod/staging/local/FedStart 四种环境,通过环境变量和 USER_TYPE 组合判断。FedStart 环境特别使用了白名单机制(ALLOWED_OAUTH_BASE_URLS),防止 OAuth token 被发送到未授权的端点。

模式 10:工具白名单矩阵

tools.ts 定义了三种 Agent 角色的工具访问权限——黑名单(所有 Agent 禁用)+ 白名单(异步 Agent 允许)+ 角色特化(Teammate/Coordinator 额外允许)。这种矩阵式权限管理确保了不同角色的最小权限原则。


八、与原书的对照验证

书中概念 源码验证 结论
System Prompt 三层管线(附录 C) prompts.tsgetSystemPrompt() 明确分为静态章节 → 边界标记 → 动态章节 ✅ 完全一致
静态层使用 global 缓存 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 标记 + shouldUseGlobalCacheScope() ✅ 有条件控制
DAG 叶子约束(第 2 章) apiLimits.ts 头注释 “Keep this file dependency-free” ✅ 原则一致
Feature Flag 编译时消除(第 2 章) betas.tsprompts.ts 使用 feature() from bun:bundle ✅ 完全一致
信任分层初始化(第 2 章) oauth.ts 的 FedStart 白名单机制 ✅ 安全边界一致
工具 Fail-Closed 设计(第 4 章) tools.ts 的白名单模式(默认禁用,显式允许) ✅ 白名单 = Fail-Closed
上下文压缩的 token 预算(第 7 章) toolLimits.tsMAX_TOOL_RESULT_TOKENS = 100_000 ✅ 有明确限制

九、总结

constants/ 目录看似只是一个简单的常量集合,但它实际上是 Claude Code 架构约束的集中体现

  1. DAG 叶子约束的领域级应用——21 个文件都是被广泛引用但不引用业务模块的叶子节点
  2. Feature Flag 编译时消除的实际使用——feature()USER_TYPE 检查遍布每个文件
  3. System Prompt 装配的完整实现——914 行的 prompts.ts 是整个 System Prompt 管线的核心
  4. 缓存稳定性的精心设计——memoize 日期、DANGEROUS_ 命名约定、边界标记
  5. 安全边界的多层防护——Safeguards 团队所有权、FedStart 白名单、工具白名单矩阵

这些常量不是"写死在代码里的配置"——它们是架构决策的固化形式。每一个常量背后都有一个设计决策,每一个文件头注释都记录着一个架构约束。


📂 文件清单

constants/ (21 files, ~100KB)
├── prompts.ts                 55KB / 914行  ⭐ System Prompt 总装配
├── outputStyles.ts            10KB          输出风格配置
├── oauth.ts                   9.2KB         OAuth 多环境配置
├── github-app.ts              5.5KB         GitHub Actions 模板
├── tools.ts                   4.8KB         Agent 工具白名单
├── system.ts                  4.0KB         CLI 前缀 + 归因头
├── spinnerVerbs.ts            3.7KB         200+ Spinner 动词
├── apiLimits.ts               3.5KB         API 服务端限制
├── xml.ts                     3.4KB         XML 标签命名空间
├── files.ts                   2.8KB         二进制文件检测
├── product.ts                 2.6KB         产品 URL
├── toolLimits.ts              2.2KB         工具结果大小限制
├── betas.ts                   2.3KB         Beta Header 注册表
├── figures.ts                 2.1KB         Unicode 符号库
├── systemPromptSections.ts    1.9KB         Prompt 章节缓存框架
├── common.ts                  1.5KB         日期工具(缓存稳定)
├── cyberRiskInstruction.ts    1.5KB         安全指令(Safeguards 团队)
├── errorIds.ts                491B          错误 ID 序号
├── keys.ts                    455B          GrowthBook SDK 密钥
├── turnCompletionVerbs.ts     281B          回合完成动词
└── messages.ts                50B           单常量文件
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐