从 TypeScript 到 C#:Codex SDK 的跨语言移植实践

简介: 本文记录了将 OpenAI 官方 TypeScript Codex SDK 全量移植至 C# 的实战过程,涵盖架构设计、类型系统映射(如 `interface`→`record`)、JSONL 事件解析、进程管理(`CancellationToken` 替代 `AbortSignal`)及跨平台适配等关键挑战,助力 .NET 环境原生集成 Codex CLI。(239字)

从 TypeScript 到 C#:Codex SDK 的跨语言移植实践

怎么说呢,这篇文章也算是个孩子,记录了我们把官方 TypeScript Codex SDK 完整移植到 C# 的全过程。说是"移植",其实更像是一场漫长的冒险,毕竟两种语言的脾性不太一样,总得想办法让它们好好相处。

背景

Codex 这东西,是 OpenAI 推出的 AI Agent CLI 工具,确实挺强大的。官方给了 TypeScript SDK,放在 @openai/codex 这个包里。它呢,通过调用 codex exec --experimental-json 命令跟 Codex CLI 交互,解析 JSONL 格式的事件流。

可是吧,我们在 HagiCode 项目里,需要在一个纯 .NET 环境中使用它。具体来说,就是 C# 后端服务和桌面端应用。你说这事闹的,总不能为了调用一个 CLI 工具而在 .NET 项目中引入 Node.js 运行时吧?那也太折腾了。

于是摆在我们面前的就两条路:一是维护一套复杂的 Node.js 桥接层,二是自己动手丰衣足食,实现一个原生 C# SDK。

我们选择了后者。

关于 HagiCode

其实这篇文也是来自我们在 HagiCode 项目里的实践经验。HagiCode 是个开源的 AI 代码助手项目,听起来挺高大上的,但说白了也就是同时维护着前端 VSCode 扩展、后端 AI 服务、跨平台桌面客户端等多种组件。这种多语言、多平台的复杂度,正是我们需要原生 C# SDK 的直接原因——总不能真的在 .NET 项目里跑个 Node.js 吧?那也太魔幻了。

如果你觉得这篇文章有点帮助,欢迎来 GitHub 给个 Star:github.com/HagiCode-org/site,也欢迎访问官网了解更多:hagicode.com。毕竟一个人品无限的项目能得到支持,也是件开心的事。

核心内容

架构设计对比

在开始代码层面的转化之前,我们得先理解两套 SDK 的架构设计。毕竟知己知彼,百战不殆嘛。

TypeScript SDK 的核心架构是这样的:

Codex (入口类)
  └── CodexExec (执行器,管理子进程)
      └── Thread (对话线程)
          ├── run() / runStreamed() (同步/异步执行)
          └── 事件流解析

C# SDK 呢,保持了相同的架构层次,但在实现细节上做了适配。整体思路是:保持 API 的一致性,但在具体实现上充分利用 C# 语言特性。毕竟语言不同,总得有点区别才行。

类型系统转化

这是最基础也是最重要的工作。毕竟万丈高楼平地起,基础打不好,后面全是麻烦。

TypeScript 的类型系统比 C# 更灵活,这是事实。我们需要找到合适的映射方式:

TypeScript C# 说明
interface / type record C# 使用 record 实现不可变数据结构
`string \ null` string? 可空引用类型
`boolean \ undefined` bool? 可空布尔值
AsyncGenerator IAsyncEnumerable 异步迭代器

事件类型系统是一个典型的例子。TypeScript 使用联合类型来定义事件:

export type ThreadEvent =
  | ThreadStartedEvent
  | TurnStartedEvent
  | TurnCompletedEvent
  | ...

在 C# 中,我们使用继承层次和模式匹配来实现类似的效果:

public abstract record ThreadEvent(string Type);

public sealed record ThreadStartedEvent(string ThreadId) : ThreadEvent("thread.started");
public sealed record TurnStartedEvent() : ThreadEvent("turn.started");
public sealed record TurnCompletedEvent(Usage Usage) : ThreadEvent("turn.completed");
// ...

使用 record 而不是 class,是因为事件对象应该是不可变的,这和 TypeScript 中使用普通对象是一个道理。而 sealed 关键字则确保不会有额外的子类继承,编译器可以进行优化。其实也就那么回事,习惯就好了。

核心转化点

1. 事件解析器

事件解析是整个 SDK 的核心,毕竟这决定了我们能否正确理解 Codex CLI 返回的每一条信息。解析错了,后面全是白忙活。

TypeScript 版本使用 JSON.parse() 来解析每一行 JSON:

export function parseEvent(line: string): ThreadEvent {
   
  const data = JSON.parse(line);
  // 处理各种事件类型...
}

C# 版本则使用 System.Text.Json.JsonDocument

public static ThreadEvent Parse(string line)
{
   
    using var document = JsonDocument.Parse(line);
    var root = document.RootElement;
    var type = GetRequiredString(root, "type", "event.type");

    return type switch
    {
   
        "thread.started" => new ThreadStartedEvent(GetRequiredString(root, "thread_id", ...)),
        "turn.started" => new TurnStartedEvent(),
        "turn.completed" => new TurnCompletedEvent(ParseUsage(...)),
        // ...
        _ => new UnknownThreadEvent(type, root.Clone()),
    };
}

这里有一个小技巧:root.Clone() 是必要的,因为 JsonDocument 的元素在文档释放后就会失效,我们需要保留一份副本给未知的事件类型。这也是没办法的事,毕竟 C# 的 JSON 处理和 JavaScript 不太一样。

2. 进程管理差异

这是两个 SDK 差异最大的地方。毕竟 Node.js 和 .NET 的脾性不太一样,总得适应适应。

TypeScript 使用 Node.js 的 spawn() 函数:

const child = spawn(this.executablePath, commandArgs, {
    env, signal });

C# 使用 .NET 的 System.Diagnostics.Process

using var process = new Process {
    StartInfo = startInfo };
process.Start();

// 需要手动管理 stdin/stdout/stderr

具体来说,C# 版本需要这样配置进程:

var startInfo = new ProcessStartInfo
{
   
    FileName = _executablePath,
    RedirectStandardInput = true,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    UseShellExecute = false,
    CreateNoWindow = true,
};

最大的区别在于取消机制。TypeScript 使用 AbortSignal,这是 Web API 的一部分,用起来挺顺手的:

const child = spawn(cmd, args, {
    signal: cancellationSignal });

C# 则使用 CancellationToken

public async IAsyncEnumerable<string> RunAsync(
    CodexExecArgs args,
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
   
    // 在循环中检查取消状态
    while (!cancellationToken.IsCancellationRequested)
    {
   
        // 处理输出...
    }

    // 取消时终止进程
    if (cancellationToken.IsCancellationRequested)
    {
   
        try {
    process.Kill(entireProcessTree: true); } catch {
    }
    }
}

这其中的区别,大概就是Web API 和 .NET 生态的差异吧,说到底也就是那么回事。

3. 配置序列化的保持

两套 SDK 都实现了将 JSON 配置转换为 TOML 配置的逻辑,因为 Codex CLI 接受 TOML 格式的配置覆盖。这部分逻辑必须完全保持一致,否则同样的配置在两个 SDK 中会产生不同的行为。

这叫什么?这就叫工匠精神嘛。毕竟细节决定成败,有些事不能将就。

实现细节

项目结构

我们创建了这样的项目结构:

CodexSdk/
├── CodexSdk.csproj
├── Codex.cs           # 入口类
├── CodexThread.cs     # 对话线程
├── CodexExec.cs       # 执行器
├── Events.cs          # 事件类型定义
├── Items.cs           # 项目类型定义
├── EventParser.cs     # 事件解析器
├── OutputSchemaTempFile.cs  # 临时文件管理
└── ...

看起来也挺整齐的,不是吗?

使用示例

基本的使用方式和 TypeScript SDK 保持一致:

using CodexSdk;

// 创建 Codex 实例
var codex = new Codex();
var thread = codex.StartThread();

// 执行查询
var result = await thread.RunAsync("Summarize this repository.");
Console.WriteLine(result.FinalResponse);

流式事件处理利用了 C# 的模式匹配能力:

await foreach (var @event in thread.RunStreamedAsync("Analyze the code."))
{
   
    switch (@event)
    {
   
        case ItemCompletedEvent itemCompleted
            when itemCompleted.Item is AgentMessageItem msg:
            Console.WriteLine($"Assistant: {msg.Text}");
            break;
        case TurnCompletedEvent completed:
            Console.WriteLine($"Tokens: in={completed.Usage.InputTokens}");
            break;
        case CommandExecutionItem command:
            Console.WriteLine($"Command: {command.Command}");
            break;
    }
}

注意事项

在实现过程中,我们也不算是白忙活,总结点经验如下:

  1. 进程管理:C# 版本需要手动管理进程的生命周期,包括取消时的进程终止。使用 Kill(entireProcessTree: true) 确保子进程也被清理。这叫什么?这就叫有始有终。

  2. 错误处理:我们使用 InvalidOperationException 抛出解析错误,保持与 TypeScript SDK 相似的错误处理方式。毕竟错误处理这事儿,不能太随意。

  3. 资源清理OutputSchemaTempFile 实现 IAsyncDisposable,确保临时文件被正确清理。这也是没办法的事,资源不清理干净,总会有问题。

  4. 环境变量:C# 版本支持通过 CodexOptions.Env 完全覆盖进程环境变量。这功能虽然小,但挺实用的。

  5. 平台差异:C# 版本不包含 TypeScript 版本中自动查找 npm 包中二进制文件的逻辑。这是因为 .NET 项目通常不依赖 npm,所以需要通过 CODEX_EXECUTABLE 环境变量或 CodexPathOverride 指定 codex 可执行文件路径。这叫什么?这就叫因地制宜。

总结

将一个成熟的 TypeScript SDK 移植到 C#,不仅仅是语法层面的转换,更是对两种语言设计哲学的理解。TypeScript 的灵活性和 JavaScript 生态特性(如 AbortSignal)在 C# 中需要找到对应的替代方案。这其中的酸甜苦辣,大概也只有真正做过的人才能体会。

关键体会是:保持 API 的一致性比保持实现细节的一致性更重要。用户关心的是接口是否易用,而不是内部实现是否相同。这话听起来简单,但做起来需要取舍。

如果你也在做类似的跨语言移植工作,我们的经验是:先完整理解原 SDK 的架构设计,然后逐个模块进行转化,最后通过完整的测试用例确保行为一致。毕竟急不得,一口吃不成胖子。

一切都会好的,都会有的......


参考资料


如果本文对你有帮助


感谢您的阅读,如果您觉得本文有用,快点击下方点赞按钮👍,让更多的人看到本文。

本内容采用人工智能辅助协作,经本人审核,符合本人观点与立场。

目录
相关文章
|
1月前
|
机器学习/深度学习 编解码 运维
红外小目标检测新突破!异常感知检测头AA-YOLO:节俭又鲁棒,小样本也能精准识别
本文提出AA-YOLO:首个将统计异常检验嵌入YOLO检测头的方法,通过指数分布建模背景,显式识别小目标为统计异常,显著降低误报率;仅需10%数据即达90%全量性能,参数比EFLNet少6倍,轻量高效;在噪声、跨域、跨模态下鲁棒性强,且可无缝适配各类YOLO及实例分割网络。
341 5
|
1月前
|
Linux API 开发工具
新手零踩坑:OpenClaw(Clawdbot)部署全指南(阿里云轻量+Mac/Linux/Windows)+免费百炼API+Skills集成教程
OpenClaw(前身为Clawdbot、Moltbot)作为开源AI智能体接入框架,凭借零代码自动化、模块化技能扩展、多平台适配的核心特性,成为2026年新手入门AI自动化工具的首选。其核心价值在于无需复杂编程,仅通过自然语言指令,即可调用Clawhub技能市场的数千款技能,实现文档处理、网页抓取、任务自动化、团队协作等各类重复性工作,适配个人办公、轻量开发、调研分析等多种场景。
450 3
|
1月前
|
编解码 JavaScript
中文域名转码 在线工具分享
专为中文域名设计的在线转码工具!用Vue开发,无需安装,一键将中文域名与Punycode(xn--开头)互转。自动清理网址前缀,操作极简:输入→转换→复制。注册域名、配置网站、排查链接都好用!
982 12
|
1月前
|
人工智能 安全 搜索推荐
OpenClaw“小龙虾”进阶保姆级攻略!阿里云/本地部署+百炼API配置+常见Skills安装方法
本文详解OpenClaw(“小龙虾”)Skills安装与安全配置:针对新手“能用不好用”痛点,系统梳理4种安装方式(含ClawHub一键安装)、阿里云极速部署全流程、百炼API配置及8大高频实用Skills(如Tavily搜索、Office自动化等),强调安全优先原则,所有命令可直接执行,助你1-2小时打造真正能做事的AI管家。
3725 15
|
1月前
|
人工智能 安全 API
阿里云百炼是什么?2026阿里云百炼AI大模型平台介绍及官网登录入口、功能使用全解析
阿里云百炼是一站式大模型服务平台(MaaS),聚合Qwen全系列等百余款大模型,提供模型调用、低/高代码Agent开发、工作流编排、安全合规部署等能力,支持“零门槛”落地应用。新用户可免费领取超7000万Tokens。(239字)
2745 3
|
Java
CompletableFuture在异常处理方面的一些常见问题和解决方案,建议牢记!
CompletableFuture在异常处理方面的一些常见问题和解决方案,建议牢记!
1079 0
CompletableFuture在异常处理方面的一些常见问题和解决方案,建议牢记!
|
1月前
|
Linux API iOS开发
新手喂饭教程:OpenClaw(Clawdbot)部署(无影云电脑+MacOS/Linux/Windows)+百炼API+Skills集成指南
OpenClaw(原名Clawdbot、Moltbot)作为开源AI智能体接入框架,凭借“自然语言交互+模块化技能扩展+多平台适配”的核心优势,成为2026年新手入门AI自动化工具的首选。其核心价值在于无需复杂编程,仅通过口语化指令,即可调用2868个各类技能(覆盖办公、开发、调研等32个分类),实现文档处理、网页抓取、任务自动化等重复性工作,适配个人办公、轻量开发等多种场景。
402 3
|
Rust 算法 Go
【密码学】一文读懂FNV Hash
FNV哈希全名为Fowler-Noll-Vo算法,是以三位发明人Glenn Fowler,Landon Curt Noll,Phong Vo的名字来命名的,最早在1991年提出。它可以快速hash大量的数据并保持较小的冲突概率,适合hash一些相近的字符串比如IP地址、URL、文件名等等。目前FNV算法有三个版本,分别是: FNV-0(已废弃)、FNV-1以及FNV-1a。这三个算法的结构非常相似,因此呢,在这里就一块说了。
4838 0
【密码学】一文读懂FNV Hash
|
1月前
|
数据采集 人工智能 搜索推荐
2026企业官网真“过时”了?AI时代建站新逻辑与搜索引擎收录指南
2026 年搜索引擎与 AI 规则迭代,传统企业官网成 “数字废墟”,需摒弃旧逻辑,以动态内容、清晰结构、语义化表达适配技术,重塑核心资产价值。
237 6
|
1月前
|
机器学习/深度学习 人工智能 算法
SEP-YOLO:当频域分析遇上YOLO,透明物体实例分割迎来新突破,ISCAS 2026
本文提出SEP-YOLO框架,首创频域细节增强模块(可学习复数权重强化高频边界)、多尺度空间细化流(内容感知对齐+门控细化),并为Trans10K提供首个高质量实例标注。在Trans10K/GVD上mAP50超SOTA 3%+,兼顾精度与实时性。
217 5