Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

/model 是 Claude Code 最常用的 local-jsx 命令之一:无参数时弹出 ModelPicker 菜单;带参数时 inline 设置 AppState.mainLoopModel;--info / --help 变体显示当前模型或用法提示。实现分散在 commands/model/ 与 utils/model/、components/ModelPicker。本章追踪校验链(allowlist、1M access、validateModel)与 fast mode / extra usage 联动。

学完本章你应该能

  • 说明 index.ts 元数据与 model.tsx call 的三路分支
  • 解释 ModelPickerWrapper handleSelect 如何写 state 与 analytics
  • 描述 SetModelAndClose 的 alias / validateModel / 1M 门禁
  • 理解 fastMode 与 isBilledAsExtraUsage 的自动降级
  • 知道 bridge 模式下 /model 被阻断的原因

核心概念(先读懂这些)

mainLoopModel vs mainLoopModelForSession

Plan mode 等场景可设 mainLoopModelForSession 作为 session 级 override;ShowModelAndClose 会同时展示 base model 与 session override。handleSelect 与 setModel 均将 mainLoopModelForSession 置 null,表示用户手动选择覆盖 plan 临时模型。

immediate 与 inference config

index.ts 的 immediate: shouldInferenceConfigCommandBeImmediate() 使 /model 在部分配置下跳过输入队列立即执行——与 /effort 等 inference 相关命令一致,减少「改了模型但要等 turn 结束」的延迟。

validateModel 不 lowercases 非 alias

SetModelAndClose 注释强调:非 alias 模型名 case-sensitive,不能用 parseUserSpecifiedModel(会 lowercase)。已知 alias 走 MODEL_ALIASES 快速路径;其余 async validateModel API 探测。

建议学习步骤

  1. 阅读源码块 A:index.ts Command 定义
  2. 阅读源码块 B:call 三路分支
  3. 阅读源码块 C:ModelPickerWrapper handleSelect
  4. 阅读源码块 D:SetModelAndClose 校验链
  5. 阅读源码块 E:1M access 与 allowlist 检查
  6. 阅读源码块 F:ShowModelAndClose 信息展示
  7. 在 ModelPicker 组件对照 effort 与 fast mode UI

常见误区

注意

default 参数设 model=null 表示恢复 settings 默认,非 API default 字符串

注意

fast mode 自动关闭时不写 settings(仅 session state)

注意

组织 allowlist 拒绝时 display:system,不抛异常

在架构中的位置

模型选择影响整条 query 链:

/model sonnet → setAppState({ mainLoopModel })
  → REPL 下次 query 读 mainLoopModel
  → query.ts → getMainLoopModel() → claude.ts normalizeModelStringForAPI
  → cost-tracker / usage 按新 model 计价

/model 不直接调 API;只改 state 与 settings 持久化(经 setAppState 副作用)。components/ModelPicker 也被 status bar、onboarding 复用;isStandaloneCommand={true} 区分全屏命令菜单与内嵌 picker。

Bridge/mobile:isBridgeSafeCommand 对 local-jsx 返回 false,故 /model 不能从 Remote Control 触发——避免远端弹本地 Ink UI。

index.ts:Command 元数据

commands/model/index.ts 导出 satisfies Command 的默认对象:

  • type: 'local-jsx'
  • name: 'model'
  • getter description:动态嵌入 renderModelName(getMainLoopModel()),typeahead 始终显示当前模型
  • argumentHint: '[model]'
  • getter immediate:shouldInferenceConfigCommandBeImmediate()
  • load: () => import('./model.js') — 编译后 .tsx → .js

这种 getter 模式让 help 文本随 session 刷新,无需 clearCommandsCache。

源码引用: src/commands/model/index.ts · 第 1–16 行(共 17 行)

   1| import type { Command } from '../../commands.js'
   2| import { shouldInferenceConfigCommandBeImmediate } from '../../utils/immediateCommand.js'
   3| import { getMainLoopModel, renderModelName } from '../../utils/model/model.js'
   4| 
   5| export default {
   6|   type: 'local-jsx',
   7|   name: 'model',
   8|   get description() {
   9|     return `Set the AI model for Claude Code (currently ${renderModelName(getMainLoopModel())})`
  10|   },
  11|   argumentHint: '[model]',
  12|   get immediate() {
  13|     return shouldInferenceConfigCommandBeImmediate()
  14|   },
  15|   load: () => import('./model.js'),
  16| } satisfies Command

call 入口:三路分支

model.tsx export call 是 LocalJSXCommandCall:

  1. COMMON_INFO_ARGS(如 --info)→ <ShowModelAndClose />,log tengu_model_command_inline_help
  2. COMMON_HELP_ARGS → onDone 静态 help 字符串,display:'system'
  3. args 非空 → <SetModelAndClose args={args} />,log tengu_model_command_inline
  4. 无 args → <ModelPickerWrapper /> 全屏菜单

args trim 后匹配;default 在 SetModelAndClose 转为 null(恢复默认设置)。

renderModelLabel 用 renderDefaultModelSetting;null 时后缀 (default)。

源码引用: src/commands/model/model.tsx · 第 271–296 行(共 338 行)

 271| }
 272| 
 273| function isSonnet1mUnavailable(model: string): boolean {
 274|   const m = model.toLowerCase()
 275|   // Warn about Sonnet and Sonnet 4.6, but not Sonnet 4.5 since that had
 276|   // a different access criteria.
 277|   return (
 278|     !checkSonnet1mAccess() &&
 279|     (m.includes('sonnet[1m]') || m.includes('sonnet-4-6[1m]'))
 280|   )
 281| }
 282| 
 283| function ShowModelAndClose({
 284|   onDone,
 285| }: {
 286|   onDone: (result?: string) => void
 287| }): React.ReactNode {
 288|   const mainLoopModel = useAppState(s => s.mainLoopModel)
 289|   const mainLoopModelForSession = useAppState(s => s.mainLoopModelForSession)
 290|   const effortValue = useAppState(s => s.effortValue)
 291|   const displayModel = renderModelLabel(mainLoopModel)
 292|   const effortInfo =
 293|     effortValue !== undefined ? ` (effort: ${effortValue})` : ''
 294| 
 295|   if (mainLoopModelForSession) {
 296|     onDone(

源码引用: src/commands/model/model.tsx · 第 293–296 行(共 338 行)

 293|     effortValue !== undefined ? ` (effort: ${effortValue})` : ''
 294| 
 295|   if (mainLoopModelForSession) {
 296|     onDone(

ModelPickerWrapper:菜单选择

菜单路径 handleSelect(model, effort?):

  1. logEvent('tengu_model_command_menu', { action, from_model, to_model })
  2. setAppState({ mainLoopModel: model, mainLoopModelForSession: null })
  3. 构建用户可见 message:model label + optional effort
  4. Fast mode 联动:
    • clearFastModeCooldown() on change
    • 新模型不支持 fast 且当前 fastMode → 设 fastMode:false,后缀「Fast mode OFF」
    • 支持且 available → 后缀「Fast mode ON」
  5. isBilledAsExtraUsage → 后缀「Billed as extra usage」

handleCancel log cancel action,onDone「Kept model as …」display:system。

渲染 <ModelPicker initial={mainLoopModel} sessionModel={mainLoopModelForSession} isStandaloneCommand showFastModeNotice />。

源码引用: src/commands/model/model.tsx · 第 18–114 行(共 338 行)

  18|   isFastModeSupportedByModel,
  19| } from '../../utils/fastMode.js'
  20| import { MODEL_ALIASES } from '../../utils/model/aliases.js'
  21| import {
  22|   checkOpus1mAccess,
  23|   checkSonnet1mAccess,
  24| } from '../../utils/model/check1mAccess.js'
  25| import {
  26|   getDefaultMainLoopModelSetting,
  27|   isOpus1mMergeEnabled,
  28|   renderDefaultModelSetting,
  29| } from '../../utils/model/model.js'
  30| import { isModelAllowed } from '../../utils/model/modelAllowlist.js'
  31| import { validateModel } from '../../utils/model/validateModel.js'
  32| 
  33| function ModelPickerWrapper({
  34|   onDone,
  35| }: {
  36|   onDone: (
  37|     result?: string,
  38|     options?: { display?: CommandResultDisplay },
  39|   ) => void
  40| }): React.ReactNode {
  41|   const mainLoopModel = useAppState(s => s.mainLoopModel)
  42|   const mainLoopModelForSession = useAppState(s => s.mainLoopModelForSession)
  43|   const isFastMode = useAppState(s => s.fastMode)
  44|   const setAppState = useSetAppState()
  45| 
  46|   function handleCancel(): void {
  47|     logEvent('tengu_model_command_menu', {
  48|       action:
  49|         'cancel' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  50|     })
  51|     const displayModel = renderModelLabel(mainLoopModel)
  52|     onDone(`Kept model as ${chalk.bold(displayModel)}`, {
  53|       display: 'system',
  54|     })
  55|   }
  56| 
  57|   function handleSelect(
  58|     model: string | null,
  59|     effort: EffortLevel | undefined,
  60|   ): void {
  61|     logEvent('tengu_model_command_menu', {
  62|       action:
  63|         model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  64|       from_model:
  65|         mainLoopModel as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  66|       to_model:
  67|         model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  68|     })
  69|     setAppState(prev => ({
  70|       ...prev,
  71|       mainLoopModel: model,
  72|       mainLoopModelForSession: null,
  73|     }))
  74| 
  75|     let message = `Set model to ${chalk.bold(renderModelLabel(model))}`
  76|     if (effort !== undefined) {
  77|       message += ` with ${chalk.bold(effort)} effort`
  78|     }
  79| 
  80|     // Turn off fast mode if switching to unsupported model
  81|     let wasFastModeToggledOn = undefined
  82|     if (isFastModeEnabled()) {
  83|       clearFastModeCooldown()
  84|       if (!isFastModeSupportedByModel(model) && isFastMode) {
  85|         setAppState(prev => ({
  86|           ...prev,
  87|           fastMode: false,
  88|         }))
  89|         wasFastModeToggledOn = false
  90|         // Do not update fast mode in settings since this is an automatic downgrade
  91|       } else if (
  92|         isFastModeSupportedByModel(model) &&
  93|         isFastModeAvailable() &&
  94|         isFastMode
  95|       ) {
  96|         message += ` · Fast mode ON`
  97|         wasFastModeToggledOn = true
  98|       }
  99|     }
 100| 
 101|     if (
 102|       isBilledAsExtraUsage(
 103|         model,
 104|         wasFastModeToggledOn === true,
 105|         isOpus1mMergeEnabled(),
 106|       )
 107|     ) {
 108|       message += ` · Billed as extra usage`
 109|     }
 110| 
 111|     if (wasFastModeToggledOn === false) {
 112|       // Fast mode was toggled off, show suffix after extra usage billing
 113|       message += ` · Fast mode OFF`
 114|     }

SetModelAndClose:inline 设模型

React.useEffect 内 handleModelChange 异步链:

门禁顺序(fail-fast):

  1. isModelAllowed(model) — 组织 allowlist
  2. isOpus1mUnavailable(model) — Opus 4.6 [1m] 账户权限
  3. isSonnet1mUnavailable(model) — Sonnet 4.6 [1m](不含 4.5 旧规则)
  4. model === null(default)→ setModel(null)
  5. isKnownAlias(model) → setModel 跳过 API 校验
  6. else validateModel(model) async

setModel mirror 菜单路径的 fast mode / extra usage 逻辑,最后 onDone(message)。

1M 不可用错误含 docs 链接 model-config#extended-context-with-1m。

源码引用: src/commands/model/model.tsx · 第 130–231 行(共 338 行)

 130|         isFastModeAvailable()
 131|       }
 132|     />
 133|   )
 134| }
 135| 
 136| function SetModelAndClose({
 137|   args,
 138|   onDone,
 139| }: {
 140|   args: string
 141|   onDone: (
 142|     result?: string,
 143|     options?: { display?: CommandResultDisplay },
 144|   ) => void
 145| }): React.ReactNode {
 146|   const isFastMode = useAppState(s => s.fastMode)
 147|   const setAppState = useSetAppState()
 148|   const model = args === 'default' ? null : args
 149| 
 150|   React.useEffect(() => {
 151|     async function handleModelChange(): Promise<void> {
 152|       if (model && !isModelAllowed(model)) {
 153|         onDone(
 154|           `Model '${model}' is not available. Your organization restricts model selection.`,
 155|           { display: 'system' },
 156|         )
 157|         return
 158|       }
 159| 
 160|       // @[MODEL LAUNCH]: Update check for 1M access.
 161|       if (model && isOpus1mUnavailable(model)) {
 162|         onDone(
 163|           `Opus 4.6 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`,
 164|           { display: 'system' },
 165|         )
 166|         return
 167|       }
 168| 
 169|       if (model && isSonnet1mUnavailable(model)) {
 170|         onDone(
 171|           `Sonnet 4.6 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`,
 172|           { display: 'system' },
 173|         )
 174|         return
 175|       }
 176| 
 177|       // Skip validation for default model
 178|       if (!model) {
 179|         setModel(null)
 180|         return
 181|       }
 182| 
 183|       // Skip validation for known aliases - they're predefined and should work
 184|       if (isKnownAlias(model)) {
 185|         setModel(model)
 186|         return
 187|       }
 188| 
 189|       // Validate and set custom model
 190|       try {
 191|         // Don't use parseUserSpecifiedModel for non-aliases since it lowercases the input
 192|         // and model names are case-sensitive
 193|         const { valid, error } = await validateModel(model)
 194| 
 195|         if (valid) {
 196|           setModel(model)
 197|         } else {
 198|           onDone(error || `Model '${model}' not found`, {
 199|             display: 'system',
 200|           })
 201|         }
 202|       } catch (error) {
 203|         onDone(`Failed to validate model: ${(error as Error).message}`, {
 204|           display: 'system',
 205|         })
 206|       }
 207|     }
 208| 
 209|     function setModel(modelValue: string | null): void {
 210|       setAppState(prev => ({
 211|         ...prev,
 212|         mainLoopModel: modelValue,
 213|         mainLoopModelForSession: null,
 214|       }))
 215|       let message = `Set model to ${chalk.bold(renderModelLabel(modelValue))}`
 216| 
 217|       let wasFastModeToggledOn = undefined
 218|       if (isFastModeEnabled()) {
 219|         clearFastModeCooldown()
 220|         if (!isFastModeSupportedByModel(modelValue) && isFastMode) {
 221|           setAppState(prev => ({
 222|             ...prev,
 223|             fastMode: false,
 224|           }))
 225|           wasFastModeToggledOn = false
 226|           // Do not update fast mode in settings since this is an automatic downgrade
 227|         } else if (isFastModeSupportedByModel(modelValue) && isFastMode) {
 228|           message += ` · Fast mode ON`
 229|           wasFastModeToggledOn = true
 230|         }
 231|       }

源码引用: src/commands/model/model.tsx · 第 233–245 行(共 338 行)

 233|       if (
 234|         isBilledAsExtraUsage(
 235|           modelValue,
 236|           wasFastModeToggledOn === true,
 237|           isOpus1mMergeEnabled(),
 238|         )
 239|       ) {
 240|         message += ` · Billed as extra usage`
 241|       }
 242| 
 243|       if (wasFastModeToggledOn === false) {
 244|         // Fast mode was toggled off, show suffix after extra usage billing
 245|         message += ` · Fast mode OFF`

ShowModelAndClose:查看当前模型

只读路径:读 mainLoopModel、mainLoopModelForSession、effortValue。

若存在 session override(plan mode):

Current model: <session> (session override from plan mode)
Base model: <base> (effort: x)

否则单行 Current model + effort 后缀。

组件 render 后立即 onDone,return null——典型 local-jsx「无 UI 一次性输出」模式。

源码引用: src/commands/model/model.tsx · 第 246–270 行(共 338 行)

 246|       }
 247| 
 248|       onDone(message)
 249|     }
 250| 
 251|     void handleModelChange()
 252|   }, [model, onDone, setAppState])
 253| 
 254|   return null
 255| }
 256| 
 257| function isKnownAlias(model: string): boolean {
 258|   return (MODEL_ALIASES as readonly string[]).includes(
 259|     model.toLowerCase().trim(),
 260|   )
 261| }
 262| 
 263| function isOpus1mUnavailable(model: string): boolean {
 264|   const m = model.toLowerCase()
 265|   return (
 266|     !checkOpus1mAccess() &&
 267|     !isOpus1mMergeEnabled() &&
 268|     m.includes('opus') &&
 269|     m.includes('[1m]')
 270|   )

依赖的 utils 与组件

model.tsx import 边界清晰:

模块用途
components/ModelPicker列表 UI、effort 选择
utils/model/aliasesMODEL_ALIASES、isKnownAlias
utils/model/validateModel非 alias API 探测
utils/model/modelAllowlistisModelAllowed 组织策略
utils/model/check1mAccessOpus/Sonnet 1M entitlement
utils/fastMode支持检测、cooldown、available
utils/extraUsageisBilledAsExtraUsage 计费提示
constants/xmlCOMMON_HELP_ARGS、COMMON_INFO_ARGS
services/analyticstengu_model_command_* 事件

改模型列表或 entitlement 时,多数逻辑在 ModelPicker 与 utils/model,model.tsx 只做命令 glue。

源码引用: src/commands/model/model.tsx · 第 1–17 行(共 338 行)

   1| import chalk from 'chalk'
   2| import * as React from 'react'
   3| import type { CommandResultDisplay } from '../../commands.js'
   4| import { ModelPicker } from '../../components/ModelPicker.js'
   5| import { COMMON_HELP_ARGS, COMMON_INFO_ARGS } from '../../constants/xml.js'
   6| import {
   7|   type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
   8|   logEvent,
   9| } from '../../services/analytics/index.js'
  10| import { useAppState, useSetAppState } from '../../state/AppState.js'
  11| import type { LocalJSXCommandCall } from '../../types/command.js'
  12| import type { EffortLevel } from '../../utils/effort.js'
  13| import { isBilledAsExtraUsage } from '../../utils/extraUsage.js'
  14| import {
  15|   clearFastModeCooldown,
  16|   isFastModeAvailable,
  17|   isFastModeEnabled,

相关命令:/effort /fast

Inference 配置命令族共享模式:

  • /effort — 设 reasoning effort,与 model 正交
  • /fast — toggle fastMode(GrowthBook + 模型支持 gated)
  • /rate-limit-options、/extra-usage — 1P 计费相关,availability gated

/model 切换模型时可能自动关 fast mode,但不自动改 effort——effort 存 effortValue 独立字段。Plan mode session override 仅影响 mainLoopModelForSession,/model 用户选择会清除 override。

Telemetry:tengu_model_command_menu vs inline vs inline_help 三分事件,便于区分菜单与参数用法占比。

Settings 持久化经 setAppState reducer 链写入 disk cache;重启 session 后 description getter 仍反映上次选择。调试「模型未保存」时查 settings.json 与 bootstrap state latch 是否 mid-session 被 reset。

本章小结与延伸

/model = AppState 模型指针的 UX 入口。下一章 mcp-commands,读外部工具服务器管理命令。 继续学习:

  • mcp-commands
  • command-registry
Prev
command-registry · commands.ts 注册与分派
Next
mcp-commands · /mcp 服务器管理