01

整体架构:13 个 Crate 如何组成一个终端 AI 助手

想象一个快递分拣中心——包裹从四面八方涌入,经过分类、检查、打包,再送到不同目的地。DeepSeek-TUI 的 13 个 Crate 就是这样的分工体系。

DeepSeek-TUI 是什么?

DeepSeek-TUI 是一个在终端里运行的 AI 代码助手,用 Rust 语言编写。它能在你的命令行中读文件、改代码、跑命令、搜索网页、管理 Git——所有操作都通过键盘完成。

💡
核心理念

DeepSeek-TUI 不是网页应用,而是一个纯粹的终端程序。你输入命令,AI 回复代码和操作结果,全程无需鼠标。就像一个住在你终端里的高级程序员搭档。

🤖

AI 对话引擎

连接 DeepSeek V4 大模型,支持流式思考过程显示和百万 Token 上下文

🔧

工具系统

30+ 内置工具:文件读写、Shell 执行、Git 操作、网页搜索、代码搜索等

🛡️

沙箱安全

macOS 用 Seatbelt、Linux 用 Landlock、Windows 用 Job Objects 限制程序权限

三种模式

Plan(只读探索)、Agent(需审批)、YOLO(全自动)

系统架构:数据从终端到 AI 再回来

整个系统像一座三层办公楼:一楼是入口(CLI 调度器),二楼是核心(TUI 界面 + 异步引擎),三楼是后勤(工具执行、沙箱、存储)。

入口层

🚪
CLI 调度器 (cli)

核心层

🖥️
TUI 界面 (tui)
⚙️
异步引擎 (core/engine)
📡
API 客户端 (client)

执行层

🔧
工具注册表 (tools)
🔒
沙箱 (sandbox)
🔌
MCP 协议 (mcp)

支撑层

💾
状态存储 (state)
⚙️
配置管理 (config)
点击任意组件查看其职责说明

源码目录结构

整个项目采用 Rust 的 Workspace 组织方式,每个子目录(Crate)是一个独立的模块:

crates/ Rust Workspace 的所有子项目
cli/ 命令行入口,提供 deepseek 命令
tui/ TUI 界面 + 引擎 + 工具(最大的 Crate)
tui-core/ TUI 独立渲染核心,可脱离主项目使用
core/ 引擎主体:对话循环、会话管理、线程调度
agent/ 模型注册表,管理 DeepSeek V4 Pro/Flash
tools/ 工具 Trait 定义和注册表
protocol/ CLI ↔ TUI 之间的通信协议
config/ 配置文件解析与验证
state/ SQLite 状态持久化
mcp/ Model Context Protocol 客户端
sandbox/ 沙箱策略定义
execpolicy/ Shell 命令执行策略引擎
secrets/ API 密钥安全存储
hooks/ 钩子事件分发系统

项目配置:Workspace 如何声明 13 个子项目

一个 Cargo.toml 文件就把所有模块串在一起了:

RUST
[workspace]
members = [
    "crates/agent",
    "crates/app-server",
    "crates/cli",
    "crates/config",
    "crates/core",
    "crates/execpolicy",
    "crates/hooks",
    "crates/mcp",
    "crates/protocol",
    "crates/secrets",
    "crates/state",
    "crates/tools",
    "crates/tui",
    "crates/tui-core",
]
中文解读

声明这是一个 Workspace(工作空间)——一个管理多个子项目的容器。

members 列出了所有子项目。Cargo 会同时编译它们。

agent — AI 模型注册表,知道有哪些模型可用。

app-server — HTTP/SSE 运行时 API 服务器。

cli — 用户输入 deepseek 命令时最先启动的程序。

config — 读取和验证配置文件。

core — 引擎核心,管理对话循环和会话。

execpolicy — 决定 Shell 命令是否需要用户批准。

hooks — 事件钩子系统(如工具执行前后的回调)。

mcp — 连接外部工具服务的协议。

protocol — CLI 和 TUI 之间的通信格式。

secrets — 安全存储 API 密钥。

state — SQLite 数据库,保存聊天历史。

tools — 定义所有工具的接口和注册表。

tui — 最大的 Crate,包含界面、引擎、所有工具实现。

tui-core — 独立的 TUI 渲染核心。

一次对话的完整旅程

当你在终端输入"帮我修复这个 bug",数据经历了什么?

🖥️
终端界面
⚙️
引擎
🤖
DeepSeek AI
🔧
工具+沙箱
点击"下一步"开始追踪数据流

检验你的理解

如果 DeepSeek-TUI 是一个快递中心,哪个 Crate 相当于"分拣主管"——负责协调所有其他组件?

13 个 Crate 中,哪个包含了最多的功能代码?

当用户输入 deepseek --model auto 时,CLI 程序如何把参数传递给 TUI 二进制程序?

02

消息流与引擎循环:AI 思考一次,引擎忙十次

想象电话客服中心——客户(你)提出问题后,客服(AI)可能需要查阅资料、联系同事、确认信息,每一步都是引擎在背后调度。

什么是"Turn Loop"?

DeepSeek-TUI 的核心是一个无限循环,叫做 Turn Loop。你发一条消息,AI 可能需要好几轮工具调用(读文件、跑命令、查代码)才能完成回复。

💡
关键洞察

用户看到的是"一次对话",但引擎内部可能执行了 5-20 个步骤(step)。每个 step 都可能涉及:调 API、执行工具、等待审批、处理错误。

1
用户输入消息

TUI 界面捕获文字,引擎把消息加入会话历史

2
构建 API 请求

引擎组装系统提示词 + 对话历史 + 可用工具列表,发送给 DeepSeek API

3
流式接收 AI 回复

通过 SSE 逐字接收:先看到"思考过程"(reasoning),再看到正文和工具调用

4
解析并执行工具

如果 AI 返回工具调用,引擎解析参数,在沙箱内执行,把结果送回 AI

5
循环或结束

AI 可能继续调用工具(回到步骤 3),也可能给出最终回复(结束循环)

引擎循环的核心代码

这段代码来自 core/engine/turn_loop.rs,是整个 Turn Loop 的入口函数:

RUST
impl Engine {
  pub(super) async fn handle_deepseek_turn(
    &mut self,
    turn: &mut TurnContext,
    tool_registry: Option<&ToolRegistry>,
    tools: Option<Vec<Tool>>,
    mode: AppMode,
  ) -> (TurnOutcomeStatus, Option<String>) {
    let client = self.deepseek_client
      .clone().expect("client should exist");
    let mut consecutive_tool_error_steps = 0u32;
    let mut tool_catalog = tools.unwrap_or_default();
    loop {
      if self.cancel_token.is_cancelled() {
        return (Interrupted, None);
      }
      // ... 发送请求、处理流式响应、执行工具
    }
  }
}
中文解读

Engine 是引擎结构体,这段代码是它的一个方法。

函数名 handle_deepseek_turn:处理一轮 DeepSeek 对话。

&mut self:可变借用自身——引擎会修改自己的内部状态。

turn:当前轮次的上下文(消息历史、步骤计数器等)。

tool_registry:所有可用工具的注册表,可能为空。

tools:本轮可用的工具列表,由引擎动态加载。

mode:当前模式(Plan/Agent/YOLO),影响工具审批行为。

返回值:轮次结果状态 + 可选的错误信息。

获取 API 客户端的副本,如果不存在就崩溃(不应该发生)。

工具连续出错的计数器,用于决定是否终止循环。

初始化工具目录(AI 可调用的工具列表)。

这是一个无限循环!每轮工具调用后会回到这里。

检查取消信号——用户按 Ctrl+C 时优雅退出。

返回中断状态。

省略号处是核心逻辑:发请求、接收流式响应、解析工具调用。

流式响应:为什么 AI 回复像打字一样逐字出现?

DeepSeek-TUI 使用 SSE 流式传输,分三种内容块(Content Block):

🧠

Thinking(思考过程)

AI 的内心独白:分析问题、规划方案。你看到灰色的推理文字就是它。类似 DeepSeek-R1 的链式思考。

💬

Text(正文回复)

AI 给你看的正式回答:解释代码、描述方案、总结结果。这部分是彩色的 Markdown 渲染。

🔧

Tool Use(工具调用)

AI 决定"我需要读取某个文件"或"我要运行一条命令"。引擎解析这些调用并执行对应工具。

RUST
enum ContentBlockKind {
    Text,      // 正文内容
    Thinking,  // 思考过程
    ToolUse,   // 工具调用
}

const DEFAULT_STREAM_CHUNK_TIMEOUT_SECS: u64 = 300;
const STREAM_MAX_CONTENT_BYTES: usize = 10 * 1024 * 1024;
const MAX_STREAM_ERRORS_BEFORE_FAIL: u32 = 5;
中文解读

枚举类型定义三种内容块:正文、思考、工具调用。

引擎根据类型决定如何处理每个数据块。

流式超时:5 分钟内没收到新数据块则视为连接中断。

单次响应最大 10MB(防止 AI 无限输出撑爆内存)。

连续 5 次流式错误才放弃——网络不稳定时更健壮。

组件之间的"群聊":引擎如何协调一次工具调用

当 AI 决定要执行一条 Shell 命令时,引擎内部各组件的对话就像一个工作群聊:

优雅的错误恢复:网络断了怎么办?

DeepSeek-TUI 有三层重试保护机制,确保你的长对话不会因为网络波动而丢失:

1️⃣
透明流式重试(Transparent Stream Retry)

如果连接在 AI 开始回复之前就断了(你还没看到任何内容),引擎会静默重发请求,最多 3 次。用户完全无感知。

2️⃣
连续错误容忍(Error Tolerance)

最多允许 5 次连续流式错误才会放弃。AI 正在思考的长对话不会因为一次网络抖动就被杀掉。

3️⃣
30 分钟墙钟上限(Wall-clock Cap)

即使服务器一直在发送数据(防止卡死),单次流式响应最多 30 分钟。这是安全阀,正常对话永远不会触发。

📌
设计哲学

这三层保护遵循一个原则:宁可多重试也不要浪费用户已经等了很久的 AI 回复。透明重试只在 DeepSeek 还没计费时才触发,不会让你花双倍钱。

检验你的理解

Turn Loop 在什么条件下会结束一轮对话?

为什么"透明流式重试"只在 AI 开始输出内容之前才触发?

03

工具系统与审批门:30 个工具的调度与安全管控

想象机场安检——每位乘客(工具调用)都要经过检查站(审批门),危险品(危险命令)需要额外审查,VIP 乘客(只读操作)可以走快速通道。

30+ 工具,各司其职

DeepSeek-TUI 的工具系统就像一个多功能瑞士军刀。每个工具都是一个独立的模块,有明确的输入输出定义:

📂
文件操作工具

read_filelist_dirgrep_filesfile_searchapply_patch — 读写文件、搜索内容、应用补丁

🖥️
Shell 执行工具

exec_shellexec_shell_waitexec_shell_interact — 运行命令,支持同步、异步和交互模式

🔍
搜索与网络工具

web_searchfetch_urlgithub_issue_context — 搜索网页、抓取 URL、获取 GitHub 上下文

📦
任务与子代理工具

task_createsubagentparallelchecklist_write — 创建后台任务、启动子代理、并行执行

🧪
代码分析工具

diagnosticsreviewcode_execution — LSP 诊断、代码审查、JS 代码执行

工具是如何注册的?

每个工具都需要实现 Trait 接口,并声明自己的能力标签:

RUST
enum ToolCapability {
    ReadOnly,          // 只读操作
    WritesFiles,       // 写文件
    ExecutesCode,      // 执行代码
    Network,           // 网络请求
    Sandboxable,       // 可在沙箱运行
    RequiresApproval,  // 需要审批
}

enum ApprovalRequirement {
    Auto,    // 自动批准
    Suggest, // 建议审批
    Required,// 必须审批
}
中文解读

ToolCapability 枚举:每个工具可以声明自己的能力标签。

ReadOnly — 只读操作(如读取文件),安全,自动批准。

WritesFiles — 会修改文件(如 apply_patch),需要关注。

ExecutesCode — 会执行命令(如 exec_shell),高风险。

Network — 会发起网络请求(如 web_search)。

Sandboxable — 可以在沙箱中运行(受限环境)。

RequiresApproval — 明确标记需要用户审批。

ApprovalRequirement 枚举:定义三种审批级别。

Auto — 默认值,安全操作自动通过。

Suggest — 建议审批但允许跳过。

Required — 必须等用户明确同意。

工具延迟加载:AI 不需要知道所有工具

不是所有工具都一开始就告诉 AI。引擎使用"延迟加载"策略,只暴露当前需要的工具:

RUST
fn should_default_defer_tool(
  name: &str,
  mode: AppMode,
) -> bool {
  if mode == AppMode::Yolo {
    return false; // YOLO 模式全部加载
  }
  // 这些工具始终加载:
  !matches(name,
    "read_file" | "list_dir"
    | "grep_files" | "diagnostics"
    | "notify" | "todo_write"
  )
}
中文解读

判断一个工具是否应该"延迟加载"(不一开始就暴露给 AI)。

YOLO 模式下所有工具都加载——因为用户信任 AI 自动操作。

以下工具始终加载,不需要延迟:

read_file、list_dir、grep_files — 只读工具,安全。

diagnostics — 代码诊断,安全。

notify、todo_write — 通知和任务管理,安全。

其他工具(如 Shell 执行)默认延迟,AI 需要时才动态加载。

💡
为什么要延迟加载?

两个原因:1) 减少 API 调用的 Token 开销——工具定义也占 Token;2) 降低 AI 误用高风险工具的概率。只读工具始终可用,写操作和命令执行只在需要时才暴露。

审批流程:Agent 模式下的安全门

当 AI 想执行一条 Shell 命令时,数据需要经过审批流程:

🤖
AI 模型
⚙️
引擎
👤
用户
🔒
沙箱
点击"下一步"追踪审批流程

审批决定的代码实现

RUST
enum ApprovalDecision {
  Approved { id: String },
  Denied { id: String },
  RetryWithPolicy {
    id: String,
    policy: SandboxPolicy,
  },
}

async fn await_tool_approval(
  &mut self,
  tool_id: &str,
) -> Result<ApprovalResult, ToolError> {
  loop {
    tokio::select! {
      _ = self.cancel_token.cancelled() => {
        return Err("cancelled".into());
      }
      decision = self.rx_approval.recv() => {
        // 处理审批决定
      }
    }
  }
}
中文解读

审批决定有三种可能。

Approved — 用户批准,工具正常执行。

Denied — 用户拒绝,工具不执行。

RetryWithPolicy — 用户要求换一个更严格的沙箱策略重试。

await_tool_approval 函数:引擎在这里阻塞等待用户决定。

tokio::select! 同时监听两个事件。

如果收到取消信号(Ctrl+C),立即返回错误。

否则等待用户从 TUI 界面发送审批决定。

处理用户的具体决定(批准/拒绝/换策略重试)。

找找 bug:审批流程中的问题

这段审批代码有什么安全隐患?点击你认为有 bug 的行:

1 fn execute_tool(tool: &Tool, mode: AppMode) -> Result<Output> {
2 if mode == AppMode::Plan {
3 return Err("Plan mode: read only");
4 }
5 run_tool_without_check(tool)
6 }

检验你的理解

为什么要对工具使用"延迟加载"策略?

在审批流程中,用户除了"批准"和"拒绝"之外,还有什么选择?

04

沙箱安全机制:让 AI 在笼子里干活

想象一个带安全护栏的施工场地——工人(AI)可以在指定区域自由操作,但绝不能碰外面的东西。每个操作系统的"护栏"不一样。

为什么需要沙箱?

AI 会执行 Shell 命令。如果 AI 不小心运行了 rm -rf /,你的整个系统就没了。沙箱(Sandbox)就是防止这种情况的最后防线。

⚠️
真实风险

2025 年已有多起 AI 代码助手误删用户文件的案例。DeepSeek-TUI 的沙箱确保所有 Shell 命令只能在你的项目目录内操作,不能越界。

🍎

macOS:Seatbelt

苹果自带的 sandbox-exec 工具。通过声明式配置限制文件系统和网络访问。

🐧

Linux:Landlock

Linux 内核 5.13+ 的 Landlock 安全模块。无需 root 权限,程序自己限制自己。

🪟

Windows:Job Objects

Windows 的 Job Object 进程容器。限制进程树的资源,但不声明文件系统隔离。

沙箱系统架构

沙箱管理器

🏗️
SandboxManager
📋
SandboxPolicy
📝
CommandSpec

平台后端

🍎
Seatbelt 后端
🐧
Landlock 后端
🪟
Windows 后端
点击组件查看详细说明

命令规格:沙箱如何包装一条命令

RUST
struct CommandSpec {
    program: String,
    args: Vec<String>,
    cwd: PathBuf,
    env: HashMap<String, String>,
    timeout: Duration,
    policy: SandboxPolicy,
}
中文解读

CommandSpec 是一条"准备执行的命令"的完整描述。

program — 要运行的程序名(如 "sh"、"python"、"cargo")。

args — 传给程序的参数列表(如 ["-c", "ls -la"])。

cwd — 工作目录,命令在这个目录下执行。

env — 环境变量,沙箱会继承必要的系统变量。

timeout — 命令超时时间,防止卡死(如 30 秒)。

policy — 沙箱策略,定义什么能做什么不能做。

Shell 命令执行:从用户输入到沙箱运行

$ deepseek "帮我运行测试"

AI: 好的,我来运行测试

exec_shell: cargo test

[审批] 是否允许 AI 运行此命令?[Y/n]

Y

running 42 tests ... ok

AI: 全部 42 个测试通过!

三个视角展示同一次命令执行的不同层次

命令执行结果的数据结构

RUST
enum ShellStatus {
    Running,
    Completed,
    Failed,
    Killed,
    TimedOut,
}

struct ShellResult {
    status: ShellStatus,
    exit_code: Option<i32>,
    stdout: String,
    stderr: String,
    duration_ms: u64,
    stdout_len: usize,
    stderr_len: usize,
}
中文解读

ShellStatus 枚举:命令可能处于 5 种状态。

Running — 还在执行中(后台命令)。

Completed — 正常完成。

Failed — 执行失败(非零退出码)。

Killed — 被强制杀死(如超时或用户取消)。

TimedOut — 超过设定的时间限制。

ShellResult 结构体:命令执行的完整结果。

exit_code — 进程退出码(0 表示成功)。

stdout — 标准输出内容(可能有截断)。

stderr — 错误输出内容。

duration_ms — 执行耗时(毫秒)。

stdout_len/stderr_len — 原始输出长度(截断前)。

检验你的理解

DeepSeek-TUI 在三个操作系统上分别使用什么沙箱技术?

如果 AI 在 Windows 上执行 del /s C:\Windows\System32,沙箱能阻止吗?

05

自动模式与子代理:AI 自己决定用多大的"脑力"

想象一个医疗分诊台——护士先快速评估病情(Flash 模型),再决定是安排普通门诊还是专家会诊(Pro 模型 + 深度思考)。Auto 模式就是这个分诊逻辑。

Auto 模式:AI 自己选模型和思考等级

用户可以选择固定模型(如 deepseek-v4-pro),也可以用 --model auto 让 DeepSeek-TUI 自动决定。Auto 模式控制两个维度:

🧠

模型选择

Flash(便宜快速)或 Pro(强大但贵)

思考等级

Reasoning Effort:Off(不思考)、High(深度思考)、Max(极限思考)

💡
两步路由

Auto 模式不是简单的规则,而是一个两步过程:先用 Flash 模型(思考关闭)做一次"路由判断",它看完你的消息后决定这次对话该用哪个模型和思考等级。然后再发真正的请求。路由判断本身很便宜,但能省下大量不必要的 Pro + Max 调用费用。

关键词路由:AI 怎么判断你需要多少"脑力"?

除了 AI 路由判断,引擎还有一套关键词匹配的本地启发式规则:

RUST
const HIGH_EFFORT_KEYWORDS: &[&str] = &[
  "debug", "error",
  "\u{8c03}\u{8bd5}", // 调试
  "\u{9519}\u{8bef}", // 错误
  "\u{62a5}\u{9519}", // 报错
  "\u{5d29}\u{6e83}", // 崩溃
  "\u{30c7}\u{30d0}\u{30c3}\u{30b0}", // デバッグ
];

const LOW_EFFORT_KEYWORDS: &[&str] = &[
  "search", "lookup",
  "\u{641c}\u{7d22}", // 搜索
  "\u{67e5}\u{627e}", // 查找
  "\u{691c}\u{7d22}", // 検索
];
中文解读

高努力关键词:包含这些词时,AI 会用最大思考等级。

英文:debug(调试)、error(错误)。

简体中文:调试、错误。

简体中文:报错、崩溃。

日语:デバッグ(调试)。

低努力关键词:包含这些词时,AI 降低思考等级。

英文:search(搜索)、lookup(查找)。

简体中文:搜索、查找。

日语:検索(搜索)。

这说明 DeepSeek-TUI 对中日英三语做了深度本地化。

子代理:AI 的分身术

DeepSeek-TUI 可以同时启动多个 子代理 并行工作,就像一个项目经理同时派多个人去调查不同问题:

1
非阻塞启动(agent_open)

父代理调用 agent_open,立即获得一个子代理 ID。子代理有自己的上下文和工具,独立运行。父代理继续工作,不会等待。

2
并发执行(最多 10-20 个)

默认上限 10 个子代理同时运行,可配置到 20 个。引擎管理线程池,无需手动轮询。

3
完成通知(subagent.done 事件)

子代理完成后,引擎发送结构化事件:包含执行摘要、证据列表、性能指标。父代理读取摘要继续工作。

4
按需读取大结果(handle_read)

子代理的长输出不会全部塞进父代理上下文。通过 var_handle 引用,父代理按需读取切片、范围或 JSONPath 投影。

子代理协作群聊

一次典型的子代理协作场景:用户要求"审查这个项目的所有安全问题"。

模型注册表:DeepSeek-TUI 认识哪些模型

RUST
struct ModelInfo {
    id: String,
    provider: ProviderKind,
    aliases: Vec<String>,
    supports_tools: bool,
    supports_reasoning: bool,
}

// 注册表中的模型:
// "deepseek-v4-pro"    — Pro 旗舰
// "deepseek-v4-flash"  — Flash 轻量
// 还有 NVIDIA、OpenRouter 等提供商的变体
中文解读

ModelInfo 结构体:每个模型的信息卡片。

id — 模型的唯一标识(如 "deepseek-v4-pro")。

provider — 提供商(DeepSeek、NVIDIA、OpenRouter 等)。

aliases — 别名列表(如 "deepseek-chat" 映射到 Flash)。

supports_tools — 是否支持工具调用(Pro 和 Flash 都支持)。

supports_reasoning — 是否支持思考模式(两者都支持)。

引擎根据 ModelInfo 决定发送什么格式的 API 请求。

Pro 模型:旗舰级,适合复杂编码任务。

Flash 模型:快速便宜,适合简单问答和搜索。

子代理默认用 Flash,父代理可以用 Pro。

匹配:消息关键词 → 思考等级

把左边的用户消息拖到右边对应的思考等级:

我的代码崩溃了,帮我调试
搜索项目中所有 TODO 注释
解释这段 Rust 代码的作用
数据库报错了,error 1045

Max(极限思考)— 包含调试/错误关键词

拖到这里

Low(低思考)— 包含搜索/查找关键词

拖到这里

High(深度思考)— 默认等级

拖到这里

检验你的理解

Auto 模式的"两步路由"是怎么工作的?

子代理完成后的长输出如何传递给父代理?

子代理在 Auto 模式下默认使用什么思考等级?

🎓
课程总结

通过这 5 个模块,你已经了解了 DeepSeek-TUI 的完整架构:从 13 个 Crate 的分工、引擎的 Turn Loop 循环、30+ 工具的注册和审批机制、跨平台沙箱安全,到 Auto 模式的智能路由和子代理并行。这些设计思想(分层架构、延迟加载、审批门、操作系统原生沙箱、成本感知路由)适用于任何 AI 代码助手的设计和开发。