智能工具库

Agent Harness为何偏爱TypeScript

Agent Harness为何偏爱TypeScript
从异步I/O、类型共享到插件生态,解析Coding Agent框架选型TypeScript的实用原因与边界。
1来源:掘金热榜(桥接)

Agent Harness 为何偏爱 TypeScript:从异步 I/O 到类型共享的实用考量

提到 AI 开发,很多人第一反应是 Python。但观察 Claude Code、Gemini CLI、OpenCode、Pi 等 Coding Agent,会发现 TypeScript 出现频率极高。这里的 Harness 可以理解为模型之外负责实际运行任务的那套程序——它管理会话、组织上下文、调用工具,还要处理用户中断、执行结果和任务恢复。我们在终端或编辑器里看到的 Agent,背后正是这些能力在支撑。

Harness 每天在忙什么

假设让 Agent 修改一个项目,它需要先找到相关文件,把必要内容交给模型,再根据模型返回的工具调用检查参数、执行操作,并把结果送回去。过程中,界面要持续显示进度,用户也可能修改要求或随时按下停止。真正动手后,还会碰到更多细节:命令执行到一半失败、文件已改但会话未保存、用户关闭窗口后重新打开、某次工具调用一直没返回。

这些都是 Harness 要处理的问题。核心循环可以很短,但围绕循环建立的执行和状态管理不能省。对于主要调用远程模型的产品,大量时间花在等待网络、文件、数据库和工具进程上,Node.js 的事件循环和非阻塞 I/O 正好适合组织这类工作。TypeScript 提供类型检查,实际执行异步操作的是 Node.js、Bun 这些运行时。

Python 的机器学习生态很强,但模型由远程服务提供后,我们未必需要在 Harness 里运行训练和推理相关库,更常处理的是请求、事件、工具和界面。选型时,这些工作占比很重要。Reddit 相关讨论中,有开发者指出:很多 Agent 工作其实是调用 API,产品开发者会沿用自己熟悉的技术栈。

一个功能常从运行时改到界面

做过带工具调用的聊天界面,就会发现很多功能无法只改一个地方。比如要加一个文件修改审批:工具执行层要产生审批请求,界面要展示修改内容,用户确认后运行时才能继续;时间线还要记录这次操作,会话恢复时也得知道它是否完成。一个看似很小的功能,可能涉及工具执行、消息协议、状态存储和 UI。

如果这些代码都在 TypeScript 工程里,我们可以共享事件类型和运行时校验规则——某个字段改了,相关调用处能一起检查,前端展示的状态也更容易与运行时保持一致。这对已使用 React、Node.js 的团队很有吸引力,不必为了把两种语言接起来额外维护两份接口定义,或再建立一套代码生成流程。

产品运行环境也会影响选择:VS Code 的本地和远程扩展宿主使用 Node.js,Electron 主进程也有 Node.js 环境,界面沿用 Web 技术,在此基础上继续写 TypeScript 通常很顺手。终端产品也能复用这套经验,比如 Gemini CLI 的官方工程说明就列出了 TypeScript、React 和 Ink,并把终端界面与模型编排拆在不同包里。

类型与工具参数可以一起维护

Harness 里有很多看似相似、实际不能混用的数据:文本增量和工具结果需要不同字段,等待审批和执行失败对应不同处理方式,读取文件与修改文件的工具各有参数要求。TypeScript 的联合类型、泛型和类型推导适合描述这些关系,但对 Agent 来说,只有静态类型还不够。

模型返回的数据来自系统外部,代码里写了一个接口,并不代表模型实际返回的 JSON 就符合它,所以 TypeScript 常需配合 Zod、TypeBox 或其他运行时校验方案。例如:

import { z } from "zod";
const ReadFileInputSchema = z.object({
  path: z.string(),
  startLine: z.number().int().positive().optional(),
});
type ReadFileInput = z.infer<typeof ReadFileInputSchema>;
const input = ReadFileInputSchema.parse(modelOutput);

这样既能从 Schema 推导代码里的类型,也能检查运行时传进来的参数。在支持这类 Schema 的 SDK 里,还可以用它向模型描述工具,AI SDK 的 Tool Calling 文档就展示了这套配合方式。好处很具体:给工具增加一个参数时,更容易把模型能看到的描述、执行函数接收的数据和相关调用一起维护。

注意:写一个 as ReadFileInput 不会验证数据——TypeScript 的类型在运行时会被移除,外部输入仍要实际检查。参数通过校验也不等于操作一定合理,比如路径是字符串,不代表它就在允许访问的目录内。工具的权限、执行条件和结果检查仍要由程序负责。类型与运行时契约配合能减少接口不一致,却不能保证 Agent 每一步都正确。

现成库和插件影响最终选择

语言选型还有朴素的一面:缺的东西需要自己补多少。Reddit 另一篇讨论中,一位长期使用 Kotlin 的开发者解释,他最终选 TypeScript,是因为适用环境广、现成库足够;如果坚持原语言,就得自己补不少东西。做 Harness 时,要接模型 SDK、处理流式输出、管理子进程,也可能接浏览器、编辑器和各种外部服务。对熟悉 TypeScript 的团队,能从已有工程拿来多少东西,直接影响开发成本。

插件机制也一样:OpenCode 的插件支持本地 JS、TS 文件,也支持 npm 包,插件函数拿到上下文,返回需要注册的 hooks 就能接入事件。现成扩展越多,后来者越容易沿同一套技术继续开发。其他语言并未被协议挡在外面——MCP 官方 SDK 列表里,Python、Go、Rust 等也有正式支持。TypeScript 的吸引力更多在于已有工具能否顺利放进工程。Pi 团队在 Pi Durable 介绍中直接回答过为什么继续选 TypeScript:Because it is the easiest way to bootstrap this.——用 TS 搭起这套系统最省力,这很能说明实际选型时的优先级。

模型熟悉什么也是考虑因素

做开发工具时通常考虑团队熟悉什么,Claude Code 团队还多考虑一件事:参与开发的模型熟悉什么。在 2025 年 9 月的团队采访中,Boris Cherny 解释,他们希望使用 Claude 已熟悉的技术栈,让 Claude Code 能直接参与开发自身,而 TypeScript 和 React 正是当时模型比较擅长的技术。当经常让 Coding Agent 修改项目时,技术栈是否常见、错误反馈是否明确、修改后是否容易验证都会影响体验,类型检查就能提供一部分明确反馈,让模型继续修正接口或参数错误。

不过这只是 Claude Code 团队当时的选择依据,不能推导出所有模型、所有项目都最适合 TypeScript。对我们更有用的问题是:手上的模型能否在这套工程里稳定完成修改,我们能否及时发现它改错了什么。

Codex 转向 Rust 的启示

既然 TypeScript 这么方便,Codex 为什么改用 Rust?Codex 在 2025 年 5 月的官方迁移公告中把理由讲得很直接:减少安装依赖、避免要求用户预装特定版本的 Node.js;更直接地使用原生安全与沙箱绑定;降低运行时和内存开销;通过协议继续支持其他语言扩展。同一公告也肯定了 React 和 Ink,认为早期终端界面很容易搭建和迭代。

这两件事并不矛盾——开发阶段需要快速做出交互、验证功能,而当软件要交付给更多用户时,安装、常驻内存和系统能力会变得更加突出。GitHub 后来公开的 Copilot Agent Runtime 迁移也有类似考虑:核心从 TypeScript、Node.js、V8 迁到 Rust 后,团队更方便控制资源开销,并支持其他语言的应用选择进程内嵌入。迁移的是核心运行时,界面和各语言 SDK 属于另外的层次。

即便模型响应需要等待,本地软件的成本仍然值得认真权衡。对开发者而言,选型时应先问自己:团队熟悉什么?运行时部署约束是什么?哪些层需要快速迭代,哪些层需要长期稳定? TypeScript 的便利在于统一语言、共享类型、丰富生态,但若交付形态要求低依赖、低内存或强沙箱,Rust 等替代方案也值得考虑。

本文目录(6 节)

本文基于 掘金热榜(桥接) 的公开内容,由 AI 辅助整理改写后发布。

原标题:为什么这么多 Agent Harness 都选择 TypeScript

阅读原文