claw-code 源码分析:从 TypeScript 心智到 Python/Rust——跨栈移植时类型、边界与错误模型怎么对齐?

涉及源码 :Python src/models.pycommands.pytools.pyquery_engine.py;Rust runtimeconversation.rssession.rspermissions.rs)、api(流式类型)、toolsexecute_tool)。


1. TS 心智在移植里的「锚」是什么?

TypeScript 习惯里常见三层叠在一起:

  1. JSON / API 线 :discriminated union(type 字段)、可选字段、与后端 schema 对齐。
  2. 领域模型:不可变 props、明确 null/undefined 语义。
  3. 错误模型Result 模式(neverthrow 等)或 抛异常 + try/catch,或 HTTP 状态码与 body 分流。

claw-code 没有 在 Python 里复刻完整 TS 类型系统,而是用 三条可对照的轴线 做跨栈对齐:

轴线 Python port 侧重 Rust 产品侧重
边界载荷 reference_data/*.jsondataclass / 元组 serde + JSON Schema 式 ToolSpec、Provider StreamEvent
会话/消息形状 弱:用户串、TurnResult 文本 强:ContentBlock 标签枚举、MessageRoleSession 序列化
错误与拒绝 handled 标志 + 字符串 message;少量 Exception Result<_, E>PermissionOutcomeToolError / String

下面分维度说明「怎么对齐」以及 刻意不对齐 之处(port vs product)。


2. 类型:从「快照 schema」到语言内类型

2.1 Python:dataclass 承载清单元数据,JSON 为真源

命令/工具清单从快照反序列化进 不可变 PortingModule,保证与 TS 侧「条目列表」同构的是 数据,不是行为:

python 复制代码
# 22:31:src/commands.py
        PortingModule(
            name=entry['name'],
            responsibility=entry['responsibility'],
            source_hint=entry['source_hint'],
            status='mirrored',
        )

from __future__ import annotations + PortingModule | None 等,对应 TS 的 可选与联合 的轻量子集:运行时仍靠约定 ,靠测试与 CLI 固定输出守住契约(result/26.md)。

2.2 Rust:serde 标签枚举对齐 API/会话 wire shape

会话消息块用 #[serde(tag = "type")] 模拟 TS 式 discriminated union,边界清晰:

rust 复制代码
// 20:37:rust/crates/runtime/src/session.rs
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
    Text {
        text: String,
    },
    ToolUse {
        id: String,
        name: String,
        input: String,
    },
    ToolResult {
        tool_use_id: String,
        tool_name: String,
        output: String,
        is_error: bool,
    },
}

ToolResultis_error: bool 对应「工具失败不进异常通道、而进对话历史」的常见 harness 语义,与 TS 里「结果对象 + error 标志」心智一致。

2.3 Usage:同一概念,不同严格度

  • Rust:TokenUsageu32 + serde,与 API 用量字段同形(result/24.md)。
  • Python:UsageSummaryint ,且 add_turn词数近似 token------刻意不假装与提供商 usage 一致:
python 复制代码
# 28:36:src/models.py
@dataclass(frozen=True)
class UsageSummary:
    input_tokens: int = 0
    output_tokens: int = 0

    def add_turn(self, prompt: str, output: str) -> 'UsageSummary':
        return UsageSummary(
            input_tokens=self.input_tokens + len(prompt.split()),
            output_tokens=self.output_tokens + len(output.split()),
        )

对齐策略 :名字对齐(便于读源码的人迁移心智),语义在文档与测试里降级说明,避免类型同名却误用。


3. 边界:哪里是「契约面」,哪里是「演示面」

3.1 Python CommandExecution / ToolExecutionhandled 而非 Result

执行 API 总返回 结构体,用 handled 区分成功解析镜像条目 vs 未知名------接近「永远有值」的 TS 返回类型,而 不是 Rust 的 Result

python 复制代码
# 75:80:src/commands.py
def execute_command(name: str, prompt: str = '') -> CommandExecution:
    module = get_command(name)
    if module is None:
        return CommandExecution(name=name, source_hint='', prompt=prompt, handled=False, message=f'Unknown mirrored command: {name}')
    action = f"Mirrored command '{module.name}' from {module.source_hint} would handle prompt {prompt!r}."
    return CommandExecution(name=module.name, source_hint=module.source_hint, prompt=prompt, handled=True, message=action)
python 复制代码
# 81:86:src/tools.py
def execute_tool(name: str, payload: str = '') -> ToolExecution:
    module = get_tool(name)
    if module is None:
        return ToolExecution(name=name, source_hint='', payload=payload, handled=False, message=f'Unknown mirrored tool: {name}')
    action = f"Mirrored tool '{module.name}' from {module.source_hint} would handle payload {payload!r}."
    return ToolExecution(name=module.name, source_hint=module.source_hint, payload=payload, handled=True, message=action)

与 Rust 对照 :Rust runtime 的 ToolExecutorResult<String, ToolError> ,失败是 类型系统的一轨

rust 复制代码
// 35:37:rust/crates/runtime/src/conversation.rs
pub trait ToolExecutor {
    fn execute(&mut self, tool_name: &str, input: &str) -> Result<String, ToolError>;
}

移植教益 :Port 层用 handled + message 适合 CLI 演示与快照回归 ;产品层用 Result 适合 组合与强制处理 。混用时要在架构上画线(result/24.md 已强调 Python 非企业主路径)。

3.2 Rust 工具分发:Result<String, String>serde_json::Value

execute_tool 入口是 serde_json::Valuefrom_value 成强类型 input → 再 to_string_pretty 成工具输出字符串;错误统一 String(含反序列化失败与业务 Err):

rust 复制代码
// 539:567:rust/crates/tools/src/lib.rs
pub fn execute_tool(name: &str, input: &Value) -> Result<String, String> {
    match name {
        "bash" => from_value::<BashCommandInput>(input).and_then(run_bash),
        ...
        _ => Err(format!("unsupported tool: {name}")),
    }
}

fn from_value<T: for<'de> Deserialize<'de>>(input: &Value) -> Result<T, String> {
    serde_json::from_value(input.clone()).map_err(|error| error.to_string())
}

这与 TS「工具输入是 JSON,错误变成可展示字符串」的心智接近; 在整个 workspace 强行统一 thiserror 枚举,属于 工程折中


4. 错误模型:异常、枚举与 Result 的分工

4.1 对话运行时:RuntimeError 与流式解析

ApiClient::streamResult<Vec<AssistantEvent>, RuntimeError>run_turn 同样 Result<TurnSummary, RuntimeError>单层字符串错误 包装,便于从 TS「throw Error(message)」迁思维,但 丢失错误分类 (若需重试用 anyhow/枚举需后续演进)。

4.2 会话持久化:枚举 SessionError + From 转换

与「一切皆 String」相比,Session IO/JSON 用 可区分变体

rust 复制代码
// 52:80:rust/crates/runtime/src/session.rs
#[derive(Debug)]
pub enum SessionError {
    Io(std::io::Error),
    Json(JsonError),
    Format(String),
}
...
impl From<std::io::Error> for SessionError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<JsonError> for SessionError {
    fn from(value: JsonError) -> Self {
        Self::Json(value)
    }
}

对应 TS 里 区分 fs 失败 vs parse 失败instanceof / code 检查,Rust 用 枚举 + trait 显式化。

4.3 权限:与错误通道分离的 PermissionOutcome

授权结果 不是 ToolError,而是独立枚举,便于在 match 里先分流再执行工具------对齐「权限拒绝 ≠ 工具执行异常」的 harness 语义:

rust 复制代码
// 43:47:rust/crates/runtime/src/permissions.rs
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PermissionOutcome {
    Allow,
    Deny { reason: String },
}

Python 侧则用 ToolPermissionContext 过滤列表 + PermissionDenial 出现在 TurnResultquery_engine),是 另一条、更偏演示的建模。

4.4 Python 中真正的异常路径

例如结构化输出渲染失败 RuntimeErrorresult/25.md)------与「正常 turn 总返回 TurnResult」不同,属于 编码/序列化层 的硬失败。


5. 小结:对齐 checklist(给跨栈移植用)

  1. 先锁定 wire format :API JSON、会话 JSON、工具 input schema------用 serde / 快照 JSON 做 单一真源,再在各自语言里生成类型(Rust derive;Python dataclass 手写或 codegen)。
  2. 错误三轨解析错误业务拒绝(权限)运行时失败(工具/API) ------TS 里常混在 Promise reject;Rust 已部分拆开(PermissionOutcome vs ToolError);Python port 用 handled/tuple 简化。
  3. 不要同名不同义UsageSummary vs TokenUsage 是正面例子------名称继承心智,实现诚实降级。
  4. 边界与核心分离 :镜像 execute_* ≠ 真实 ConversationRuntime;文档与测试要写清,否则类型再漂亮也会误用。

相关推荐
hhh3u3u3u2 小时前
Visual C++ 6.0中文版安装包下载教程及win11安装教程
java·c语言·开发语言·c++·python·c#·vc-1
Thomas.Sir2 小时前
AI 医疗之罕见病/疑难病辅助诊断系统从算法到实现【表型驱动与知识图谱推理】
人工智能·算法·ai·知识图谱
好家伙VCC3 小时前
**发散创新:基于Python与ROS的机器人运动控制实战解析**在现代机器人系统开发中,**运动控制**是实现智能行为的核心
java·开发语言·python·机器人
2401_827499993 小时前
python项目实战09-AI智能伴侣(ai_partner_2-3)
开发语言·python
派葛穆3 小时前
汇川PLC-Python与汇川easy521plc进行Modbustcp通讯
开发语言·python
javaGHui3 小时前
QClaw_简单方便_一键部署-多角色共同工作
ai
代码小书生3 小时前
Matplotlib,Python 数据可视化核心库!
python·信息可视化·matplotlib
默 语3 小时前
Records、Sealed Classes这些新特性:Java真的变简单了吗?
java·开发语言·python
架构师老Y3 小时前
013、数据库性能优化:索引、查询与连接池
数据库·python·oracle·性能优化·架构