问题的起点:TypeScript 生态里缺一块拼图
OpenAlice 是一个全 TypeScript 架构的 AI Agent 项目。选 TypeScript 的理由很直接:主流 Agent SDK 框架——Claude Agent SDK、OpenClaw、Vercel AI SDK——基本都是 TypeScript 生态,甚至 Claude Code 本身也是 TypeScript 写的。对于一个需要前后端一把梭的大型软件来说,TS 几乎是唯一的选择。
但盈透证券(IBKR)的 TWS SDK 只提供 Java、C++ 和 Python 版本。社区虽然有一个 TypeScript 的包,但完成度一般、文档缺失、长期无人维护。这种关键依赖如果自己不能掌控每一行代码,后续的维护就是灾难。
所以只剩一条路:自己用 TypeScript 重写。
为什么不走「桥接」的捷径?
很多人的第一反应是:直接用 Python SDK,起一个 Python 进程做桥接不就行了?
想想这个链路:Alice(TS)→ RESTful → Python 进程 → TWS Socket → 本地 TWS 客户端。中间多了一层完全没必要的进程和端口转换,还会丢类型信息,还需要额外做一遍语义化工作。
更关键的是产品体验问题。对于追求「开箱即用」的产品来说,让用户启动前先配一遍 Python 环境,这是不可接受的。OpenAlice 之前用 OpenBB 做数据源就吃过这个亏——不得不单独起一个 Python 进程,后来被迫把 OpenBB 需要的部分也重构成了 TypeScript。
那 IBKR 官方的 REST API 呢?两个硬伤:一是功能不全,很多 TWS SDK 能做的事 REST API 做不了;二是团队在 Alpaca 上已经被 REST 方案的断连问题坑过——断了之后不到下一次请求你根本不知道,心跳和重连逻辑全得自己做。
一个本地已经在跑的 TWS 实例,用 Socket 直连,比任何 REST 方案都稳定。所以最终方案很明确:用 TypeScript 直接实现 TWS 的 Socket 协议。
产品定位:挂机小精灵
这里有一个值得独立开发者思考的产品思维——Alice 并不想取代 TWS。
理想的使用场景是:用户已经在用 IBKR 的 TWS 做专业分析和手动交易,Alice 挂在后台,像游戏里的挂机小精灵一样,帮你盯盘、做策略执行、处理那些不想盯着屏幕做的事情。TWS 是主操作台,Alice 是自动辅助,两者通过本地 Socket 直连,零延迟、零中间层。
这个定位决定了技术选型:不是替代,而是共存。做一个能和任何专业工具共存的 AI 层。
调研先行:选对翻译的「源语言」
决定自己写之后,第一步不是让 AI 开干,而是先把三个官方 SDK 全部拉下来通读——Java、C++、Python。
这里有一个反直觉的发现:社区那个 TypeScript 包是对着 Java 版逐行翻译的,但 Java 版是最早的实现,代码相当陈旧。后来官方用 Python 重写的版本,结构清晰得多,抽象层次也更合理。
所以团队做了一个关键判断:照 Python 版来写,不照 Java 版。社区包的翻译源头就选错了,自然也没法参考。
工程深水区:理解 Peterffy 的协议设计
真正进到工程里才发现,IBKR 的通信协议比表面复杂得多。
它最早用的是一种纯位置编码的消息流。乍一看跟 FIX(金融信息交换协议)有点像——都是用字符串传结构化的金融数据。但有一个根本区别:
- FIX 协议是自编码的:每个字段前面带着自己的索引号(tag),比如
35=D表示「第 35 号字段的值是 D」。你不需要知道字段在消息里排第几个。 - IBKR 的协议把自编码抠掉了:字段含义完全由位置决定。第 1 个字段是什么、第 N 个字段是什么,纯靠数位置。
这个设计在当时可能是为了「简化」——少传几个字节,解析快一点。但后果是极其难以扩展。一旦要在中间加字段,或者某个字段变成可选的,整个解析逻辑就变得极其脆弱。
IBKR 自己也意识到了这个问题,新版本已经在迁移到 Protobuf——本质上就是回到了 FIX 一开始就做对了的自编码设计。Python 版 SDK 里已经用 Proto 文件来做类型化约束了。
这就产生了一个具体的技术问题:怎么把 Proto 定义编译成 TypeScript 代码?Proto 文件的编译配置、TypeScript 代码生成的选型(protobuf-ts?ts-proto?buf?)——这些具体的事情,就是交给 Claude Code 去处理的。
实操中的关键一步:先拆文件,再让 AI 翻译
IBKR 的 Python SDK 很明显是人写的——对于字段多、结构重复的东西,人类工程师倾向于全维护在一个文件里。结果就是动不动一个文件五六千行、七八千行。
这种规模对 AI 来说是灾难性的。AI 在七千行的上下文里会迷路,会丢失关键细节。
所以第一步不是直接让 AI 翻译大文件,而是先把大文件按功能模块拆小。这步看起来不大,但对后续整个 AI 开发进程的加速作用是巨大的。拆完之后,AI 每次只需要理解一个几百行的小模块。
核心方法论:自上而下 + 测试驱动
拆完文件之后,第二个关键问题是:怎么确保 AI 写出来的是「正确的产物」,而不仅仅是「能跑的代码」?
这两者的区别非常大。AI 写代码有一个常见倾向:当它发现缺少字段或类型不匹配时,不会停下来告诉你「这里有问题」,而是会用启发式的方式把问题糊弄过去——
- 缺一个字段?塞个默认值
- 类型对不上?加个
as any - 某个分支太复杂?直接简化掉
当前上下文里这样改可能看上去无所谓。但换一个新窗口、让另一个 AI 接手时,这种代码就变成地雷:你分不清哪些是「故意这么写的」,哪些是「AI 偷懒糊弄的」。
所以需要一套方法论来约束 AI 的行为:
- 测试驱动开发(TDD):先把官方测试案例搬过来,让 AI 知道「正确的输出长什么样」
- 自上而下对照翻译:Python 版的实现就是 spec,不让 AI 自由发挥,而是给它明确的参照
- 翻译和重构同步进行:在搬运的过程中把大文件拆成小模块,而不是先翻译完再重构
这三步循环下来,一步步把整个重构做完了。
这个方法论的适用范围远不止 IBKR:任何「用 AI 做大型代码翻译/重构」的场景,核心都是三件事——给 AI 明确的参照源、明确的验证标准、小颗粒度的工作单元。
最终效果
整条决策链串起来看:
- Agent 生态是 TypeScript 的 → 工程只能选 TS
- 官方不提供 TS SDK → 社区包不可靠 → 自己写
- Python/Java 桥接多一层无意义的进程 → 不干
- REST API 功能不全且有断连风险 → 本地 Socket 直连
- Claude Code 一晚上搞定边界清晰的重构任务
最终 OpenAlice 又向「一键启动、本地运行、全 TypeScript 栈」迈进了一步。Clone 下来,跑一个命令,Alice 就开始工作。不需要 Python 环境,不需要 Java 运行时,不需要额外的端口桥接。
对于独立开发者来说,这个案例最值得带走的不是具体的 IBKR 重构细节,而是那套方法论:当你需要用 AI 完成一个大型重构时,先拆文件降低单次理解负担,再用测试驱动确保输出正确性,最后自上而下对照翻译而非让 AI 自由探索。下次你面对一个「关键依赖没有目标语言实现」的问题时,不妨想想:如果先把源码拆成几百行一个的小模块,Claude Code 能不能一个晚上帮你解决?