
用 C# 手写一个 ScriptEngine 时最先消耗耐心的往往不是业务逻辑而是 RegisterCommand、参数解析、$last 引用这一堆地基代码。原文演示的正是这套手动流程注册 print/add 两个内置命令逐行解析脚本把注释跳过去把带引号的字符串参数拆干净还得记住上一次命令的返回值。整个过程不难就是繁几十行样板代码写完真正的命令逻辑还没开始。最近我把 Codex 接到了 TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_end的 API 通道上让它照着原结构把脚本引擎生成出来再本地编译验证 print/add 是否跑通。这比纯手写省事不少。1. 手写 ScriptEngine 的样板活比业务命令更占时间原文的 ScriptEngine 核心不难难在把「解释器壳子」先搭好。为了说清楚我先把原文的骨架拆开看。首先是命令登记。用一个字典把命令名映射到方法这步很直白但每加一个命令就要多写一行注册命令一多注册区比执行区还长。其次是参数解析。脚本行print Hello from ScriptEngine!既要保留引号内的空格又要把外面的引号剥掉add 8 4要把字符串转成数字。正则写简单了会拆坏字符串写复杂了又不好维护。第三是 $last 语义。它代表上一条命令的返回值必须在上一条命令执行完后立刻记录下来下一条命令执行前再读出。顺序一错结果就变味。还有一个常被忽略的点解析脚本时如果只用string.Split( )带空格的字符串会被拆成多个 token引号内的空格全部丢失。你不得不用正则去匹配带引号字符串或者手写一个状态机。状态机的分支一多代码的可读性就下降。原文里这是用Regex.Matches处理的但这行正则在落地时往往要调多次才能覆盖单引号、双引号、转义等边界。如果脚本某一行抛异常$last 是保持旧值还是清零也得自己定义。这些决策本身不复杂但叠加起来就是时间。这些还只是「能跑」的门槛。原文继续提出扩展方向变量、条件、循环。如果全手工写等于在解释器之上再叠一套求值器工作量直接翻倍。所以更合理的做法是让 Codex 先把这套壳子生成出来人只负责补充业务命令和测试。问题在于 Codex 默认的模型通道未必稳定或者 Key 分散在多个平台不好管理。TaoToken 做的就是把这层统一收敛到一个 API 地址上让 Codex 的模型提供商配置指向它就行。2. 先把 Codex 的模型提供商切到 TaoToken配置之前需要两样东西一个 API Key一段能写进 Codex 配置的 Base URL。官网和接口地址是两回事别填混。2.1 在官网创建 API Key 并复制 Key先打开 TaoToken注册后进入控制台创建 API Key。Key 创建后复制保存后面填到 Codex 的环境变量里。模型 ID 不要凭记忆写去官网模型广场复制你需要的那个模型的准确 ID避免因为名称多了一个点或日期后缀导致 Codex 报模型不存在。这里特别说明Base URL 是给代码和工具请求用的地址填https://taotoken.net/api官网地址是给人注册、建 Key、看用量用的不要把 UTM 链接填进工具也不要在 API 地址末尾加/v1。两者各司其职。2.2 修改~/.codex/config.tomlCodex CLI 的全局配置在用户目录下的~/.codex/config.tomlWindows 下是%USERPROFILE%\.codex\config.toml。打开后添加如下 provider 配置model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY其中YOUR_MODEL_ID替换为官网模型广场里显示的模型 ID。Key 通过环境变量注入不直接写进配置文件避免误传。终端里设置# Linux / macOS export TAOTOKEN_API_KEYYOUR_API_KEY # Windows PowerShell $env:TAOTOKEN_API_KEYYOUR_API_KEY如果不习惯用 Codex 的原生命令行也可以直接装 TaoToken 的 CLI 走同一通道npm install -g taotoken/taotoken taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m YOUR_MODEL_ID注意taotoken cc命令里的-u参数填https://taotoken.net/api就好不用带 UTM也不要画蛇添足加/v1。官网链接是用来在浏览器里注册和查用量的CLI 和配置文件只需要干净的接口地址。3. 让 Codex 照原文的 RegisterCommand 结构生成 ScriptEngine配置完成后启动 codex把需求用一段提示词说清楚。我这里的提示词直接对应原文的 RegisterCommand、ExecuteScript、$last 结构Codex 会生成一个可编译的 Program.cs。提示词内容写一个 C# ScriptEngine 类要求 1. RegisterCommand(string name, Funcstring[], object action) 注册命令 2. ExecuteScript 按行执行脚本支持 // 注释、双引号包裹的字符串参数用 $last 引用上一行命令的返回值 3. 内置 print 和 add 命令print 把参数用空格拼接后输出到控制台add 把两个参数解析为 double 并返回和 4. Main 方法里依次执行 print Hello from ScriptEngine!、print Calculating...、add 8 4、print Result: $last。Codex 生成的代码大致如下你可以直接存成 Program.cs 编译这里已整理为可运行版本using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; public class ScriptEngine { private readonly Dictionarystring, Funcstring[], object _commands new(); private object _lastResult; public void RegisterCommand(string name, Funcstring[], object action) { _commands[name] action; } public object ExecuteScript(string script) { object lastResult null; foreach (var rawLine in script.Split(\n)) { var line rawLine.Trim(); if (line.Length 0 || line.StartsWith(//)) continue; var parts SplitArguments(line); if (parts.Count 0) continue; var command parts[0]; var args parts.Skip(1).Select(a a $last ? (lastResult ?? null).ToString() : a).ToArray(); if (!_commands.TryGetValue(command, out var action)) throw new InvalidOperationException($Unknown command: {command}); lastResult action(args); } return lastResult; } private static Liststring SplitArguments(string input) { var matches Regex.Matches(input, [^]|[^]|\S); var result new Liststring(); foreach (Match match in matches) result.Add(match.Value.Trim(, \)); return result; } } public class Program { public static void Main() { var engine new ScriptEngine(); engine.RegisterCommand(print, args { var text string.Join( , args); Console.WriteLine(text); return null; }); engine.RegisterCommand(add, args { if (args.Length 2) return 0d; if (double.TryParse(args[0], out var a) double.TryParse(args[1], out var b)) return a b; return 0d; }); var demoScript print Hello from ScriptEngine! print Calculating... add 8 4 print Result: $last ; engine.ExecuteScript(demoScript); } }代码有几点值得注意SplitArguments用正则把带引号字符串或连续非空字符识别为 token引号内的空格不会被拆开$last在参数阶段被替换为上一行命令的返回值add返回 double因此后面的$last拿到12。这些结构和原文演示的完全一致。4. 本地编译运行用 print/add 验证整条链路Codex 生成代码后编译运行这一步在本地完成。新建一个控制台项目把 Program.cs 放进去dotnet new console -n ScriptEngineDemo cd ScriptEngineDemo # 将上面的代码写入 Program.cs dotnet run正常输出Hello from ScriptEngine! Calculating... Result: 12看到这几行输出就说明三点验证通过print命令能拼接并输出带空格的字符串add命令能把字符串参数转成 double 并返回数值$last能把上一行 add 的结果带进下一行。这一步同时验证了 TaoToken 的调用链路是否真的通Codex 从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建的 Key 发出请求模型按提示词生成了对应 C# 代码。如果担心 Key 没生效可以回到 TaoToken 控制台 的用量页核对刚生成的对话会在记录列表里出现。这里成功之后后面继续扩展变量、条件、循环时只需要改提示词不用再动 Codex 的通道配置。5. 排障Key、模型 ID、Base URL、脚本命令这节列几个实际遇到、也最容易混淆的报错按出现概率排序。5.1 401 authenticationCodex 启动时报 401通常是环境变量里的 Key 有问题。先确认TAOTOKEN_API_KEY已经设置且 Key 来自 TaoToken 的创建页面。注意 config.toml 里env_key的名字要和环境变量完全一致大小写也要一致。5.2 model not found 或模型 ID 报错Codex 提示模型不存在多半是model YOUR_MODEL_ID里的 ID 和模型广场不一致。模型 ID 请以官网模型广场显示为准不要自己加日期后缀也不要沿用别的平台的旧名。5.3 connection error 或 404base_url写成了https://taotoken.net/api/v1或者把官网地址填进去了。接口地址固定是https://taotoken.net/api官网地址只用于注册和查看用量两者不要混用。5.4 Unknown command编译运行通过但脚本报Unknown command: xxx说明ExecuteScript解析到了未注册的命令名。检查脚本拼写或者看SplitArguments是否把带空格字符串拆成了多个参数如果参数里有特殊字符用双引号包起来。另外留意一个顺序问题$last要放在有返回值的命令如add之后如果放在print后面拿到的会是null因为print返回null。6. 下一步让 Codex 按原文思路补变量、条件、循环原文最后提到三类扩展变量支持、条件语句、循环语句。这三部分同样可以靠提示词让 Codex 完成不用自己从零写。例如基于上面的 ScriptEngine继续加 - set name value 命令把 value 存到变量表脚本中用 $name 引用 - if 命令格式 if 条件 thenScript条件成立时执行 thenScript - while 命令格式 while 条件 bodyScript循环执行 bodyScript 直到条件不成立。 保持 RegisterCommand 和 ExecuteScript 的现有结构不变。Codex 会在原结构上新增变量字典、表达式求值和循环控制你拿到代码后同样本地编译验证。每扩展一块就补一段测试脚本跑一遍确保 print/add 这些基础命令不被改坏。如果你也正被 C# 脚本引擎的样板代码拖住先打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册、创建 API Key再把 config.toml 的 model_provider 切到 TaoToken。剩下的 RegisterCommand、$last、变量循环这些写提示词让 Codex 去生成你只负责 review 和运行验证这比手敲快得多。