整体架构: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 界面 + 异步引擎),三楼是后勤(工具执行、沙箱、存储)。
入口层
核心层
执行层
支撑层
源码目录结构
整个项目采用 Rust 的 Workspace 组织方式,每个子目录(Crate)是一个独立的模块:
项目配置:Workspace 如何声明 13 个子项目
一个 Cargo.toml 文件就把所有模块串在一起了:
[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-TUI 是一个快递中心,哪个 Crate 相当于"分拣主管"——负责协调所有其他组件?
13 个 Crate 中,哪个包含了最多的功能代码?
当用户输入 deepseek --model auto 时,CLI 程序如何把参数传递给 TUI 二进制程序?
消息流与引擎循环:AI 思考一次,引擎忙十次
想象电话客服中心——客户(你)提出问题后,客服(AI)可能需要查阅资料、联系同事、确认信息,每一步都是引擎在背后调度。
什么是"Turn Loop"?
DeepSeek-TUI 的核心是一个无限循环,叫做 Turn Loop。你发一条消息,AI 可能需要好几轮工具调用(读文件、跑命令、查代码)才能完成回复。
用户看到的是"一次对话",但引擎内部可能执行了 5-20 个步骤(step)。每个 step 都可能涉及:调 API、执行工具、等待审批、处理错误。
TUI 界面捕获文字,引擎把消息加入会话历史
引擎组装系统提示词 + 对话历史 + 可用工具列表,发送给 DeepSeek API
通过 SSE 逐字接收:先看到"思考过程"(reasoning),再看到正文和工具调用
如果 AI 返回工具调用,引擎解析参数,在沙箱内执行,把结果送回 AI
AI 可能继续调用工具(回到步骤 3),也可能给出最终回复(结束循环)
引擎循环的核心代码
这段代码来自 core/engine/turn_loop.rs,是整个 Turn Loop 的入口函数:
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 决定"我需要读取某个文件"或"我要运行一条命令"。引擎解析这些调用并执行对应工具。
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 有三层重试保护机制,确保你的长对话不会因为网络波动而丢失:
如果连接在 AI 开始回复之前就断了(你还没看到任何内容),引擎会静默重发请求,最多 3 次。用户完全无感知。
最多允许 5 次连续流式错误才会放弃。AI 正在思考的长对话不会因为一次网络抖动就被杀掉。
即使服务器一直在发送数据(防止卡死),单次流式响应最多 30 分钟。这是安全阀,正常对话永远不会触发。
这三层保护遵循一个原则:宁可多重试也不要浪费用户已经等了很久的 AI 回复。透明重试只在 DeepSeek 还没计费时才触发,不会让你花双倍钱。
检验你的理解
Turn Loop 在什么条件下会结束一轮对话?
为什么"透明流式重试"只在 AI 开始输出内容之前才触发?
工具系统与审批门:30 个工具的调度与安全管控
想象机场安检——每位乘客(工具调用)都要经过检查站(审批门),危险品(危险命令)需要额外审查,VIP 乘客(只读操作)可以走快速通道。
30+ 工具,各司其职
DeepSeek-TUI 的工具系统就像一个多功能瑞士军刀。每个工具都是一个独立的模块,有明确的输入输出定义:
read_file、list_dir、grep_files、file_search、apply_patch — 读写文件、搜索内容、应用补丁
exec_shell、exec_shell_wait、exec_shell_interact — 运行命令,支持同步、异步和交互模式
web_search、fetch_url、github_issue_context — 搜索网页、抓取 URL、获取 GitHub 上下文
task_create、subagent、parallel、checklist_write — 创建后台任务、启动子代理、并行执行
diagnostics、review、code_execution — LSP 诊断、代码审查、JS 代码执行
工具是如何注册的?
每个工具都需要实现 Trait 接口,并声明自己的能力标签:
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。引擎使用"延迟加载"策略,只暴露当前需要的工具:
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 命令时,数据需要经过审批流程:
审批决定的代码实现
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 的行:
fn execute_tool(tool: &Tool, mode: AppMode) -> Result<Output> {
if mode == AppMode::Plan {
return Err("Plan mode: read only");
}
run_tool_without_check(tool)
}
检验你的理解
为什么要对工具使用"延迟加载"策略?
在审批流程中,用户除了"批准"和"拒绝"之外,还有什么选择?
沙箱安全机制:让 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 进程容器。限制进程树的资源,但不声明文件系统隔离。
沙箱系统架构
沙箱管理器
平台后端
命令规格:沙箱如何包装一条命令
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 命令执行:从用户输入到沙箱运行
三个视角展示同一次命令执行的不同层次
命令执行结果的数据结构
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,沙箱能阻止吗?
自动模式与子代理: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 路由判断,引擎还有一套关键词匹配的本地启发式规则:
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 可以同时启动多个 子代理 并行工作,就像一个项目经理同时派多个人去调查不同问题:
父代理调用 agent_open,立即获得一个子代理 ID。子代理有自己的上下文和工具,独立运行。父代理继续工作,不会等待。
默认上限 10 个子代理同时运行,可配置到 20 个。引擎管理线程池,无需手动轮询。
子代理完成后,引擎发送结构化事件:包含执行摘要、证据列表、性能指标。父代理读取摘要继续工作。
子代理的长输出不会全部塞进父代理上下文。通过 var_handle 引用,父代理按需读取切片、范围或 JSONPath 投影。
子代理协作群聊
一次典型的子代理协作场景:用户要求"审查这个项目的所有安全问题"。
模型注册表:DeepSeek-TUI 认识哪些模型
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。
匹配:消息关键词 → 思考等级
把左边的用户消息拖到右边对应的思考等级:
Max(极限思考)— 包含调试/错误关键词
Low(低思考)— 包含搜索/查找关键词
High(深度思考)— 默认等级
检验你的理解
Auto 模式的"两步路由"是怎么工作的?
子代理完成后的长输出如何传递给父代理?
子代理在 Auto 模式下默认使用什么思考等级?
通过这 5 个模块,你已经了解了 DeepSeek-TUI 的完整架构:从 13 个 Crate 的分工、引擎的 Turn Loop 循环、30+ 工具的注册和审批机制、跨平台沙箱安全,到 Auto 模式的智能路由和子代理并行。这些设计思想(分层架构、延迟加载、审批门、操作系统原生沙箱、成本感知路由)适用于任何 AI 代码助手的设计和开发。