ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

@coze-arch/idl2ts-cli 实战:在 coze-loop 前端从 Thrift/Protobuf IDL 一键生成 TypeScript API 客户端

@coze-arch/idl2ts-cli 实战:在 coze-loop 前端从 Thrift/Protobuf IDL 一键生成 TypeScript API 客户端
  • 人工智能
  • 大模型
  • AI Agent
  • 提示工程
  • AI 评测
  • 可观测性
  • 后端
  • 前端

【免费下载链接】coze-loop

Next-generation AI Agent Optimization Platform: Cozeloop addresses challenges in AI agent development by providing full-lifecycle management capabilities from development, debugging, and evaluation to monitoring.

项目地址:https://gitcode.com/gh_mirrors/co/coze-loop
点击查看免费下载

@coze-arch/idl2ts-cli是 coze-loop(Cozeloop,AI Agent 优化平台)前端 monorepo 中 IDL 转 TypeScript 代码生成链路的命令行入口:它读取api.config.js中声明的 Thrift/Protobuf 入口,借助@coze-arch/idl2ts-generator、@coze-arch/idl2ts-helper、@coze-arch/idl2ts-plugin完成解析、类型映射、代码生成与格式化,最终产出可直接调用后端接口的createAPI<Req, Res>客户端代码与配套 Mock 数据。本文基于 frontend/infra/idl/idl2ts-cli 包源码与它在@cozeloop/api-schema包中的真实落地实践,完整讲解安装接入、gen/filter两个子命令、配置体系、插件机制、Mock 开发链路与生成产物形态,读完即可在任意前端子包中复刻整套“IDL → API 客户端”流水线。

包定位:IDL 代码生成链路的命令行门面

在 coze-loop 前端frontend/infra/idl目录下,IDL 转 TypeScript 的能力被拆分为四个协作包:

  • idl-parser:IDL 解析层,负责将.thrift/.proto源文件解析为统一 AST;
  • idl2ts-generator:代码生成核心,提供ClientGenerator与插件运行管线(见 frontend/infra/idl/idl2ts-generator/src/core.ts);
  • idl2ts-plugin:插件机制(Program、IPlugin、HOOK常量);
  • idl2ts-cli:本文主角,把上述能力包装成面向用户的命令行工具,同时内置了 Mock、别名、格式化、本地 Mock 配置维护、类型裁剪等一批高频插件。

idl2ts-cli的package.json(见 frontend/infra/idl/idl2ts-cli/package.json)中通过bin字段将命令注册为idl2ts,入口指向./src/cli.js;该入口使用sucrase/register/ts让 Node 直接执行 TypeScript 源码(见 frontend/infra/idl/idl2ts-cli/src/cli.js),因此无需预先构建即可运行 CLI。版本号为0.1.7,许可证 Apache-2.0。

安装与接入:workspace 依赖 + rush update

idl2ts-cli是 Coze Studio monorepo 的 workspace 包,安装方式与普通 npm 依赖一致,但版本号使用workspace:*以关联本地源码:

{ "dependencies": { "@coze-arch/idl2ts-cli": "workspace:*" } }

随后在仓库根目录执行依赖更新:

rush update

idl2ts-cli自身依赖commander(命令解析)、ora(进度 spinner)、prettier(产物格式化)、dayjs与@faker-js/faker(Mock 值生成),以及同为 workspace 的@coze-arch/idl2ts-generator、@coze-arch/idl2ts-helper、@coze-arch/idl2ts-plugin。开发侧使用 TypeScript、ESLint 与 Vitest 保证代码质量(lint、test脚本见 package.json)。

在实际工程中,前端业务包通常把idl2ts-cli放进devDependencies,并在package.json的scripts中直接调用idl2ts。例如@cozeloop/api-schema包(见 frontend/packages/loop-base/api-schema/package.json):

{ "scripts": { "prethrift": "bash ./scripts/download-thrift.sh --branch=main", "thrift": "idl2ts gen ./", "update": "npm run thrift" } }

prethrift先拉取指定分支的 IDL 源文件到本地缓存,thrift再执行生成——这是 coze-loop 前端“IDL 同步 + 代码生成”的标准操作序列。

命令行用法:gen 与 filter 两个子命令

idl2ts的入口逻辑位于 frontend/infra/idl/idl2ts-cli/src/cli.ts,共注册两个子命令:

gen:生成 API 客户端代码

idl2ts gen <projectRoot> [-f --format-config <formatConfig>]
  • <projectRoot>:必选参数,项目根目录,用于定位配置文件与解析相对路径;
  • -f, --format-config <formatConfig>:可选,prettier 配置文件路径,默认值为.prettierrc。

执行时ora会先展示 “Generating api. It may take a few seconds” 的进度提示,成功输出 “Generate api successfully”,失败则输出 “Generate api fail” 并打印错误、以退出码 1 结束进程。gen内部调用 src/actions.ts 的gen()函数,默认读取项目根目录下的api.config(即api.config.js/api.config.ts,见utils.ts的lookupConfig实现,默认配置文件名api.config)。

filter:按方法清单裁剪类型后生成

idl2ts filter <projectRoot> [-f --format-config <formatConfig>]

参数与gen完全一致,但读取的是api.filter.js配置文件(lookupConfig<ApiTypeConfig>(projectRoot, 'api.filter.js')),并在插件链中额外挂载FilterTypesPlugin:只保留filters中指定的 service 方法及其递归依赖的结构体、枚举类型,裁剪掉无关类型。适合在接入方只需要某个服务子集时产出轻量类型包。

两个命令的配置发现逻辑

lookupConfig(见 src/utils.ts)通过require.resolve从当前进程工作目录解析<projectRoot>/<configName>,找不到配置时抛出Can not find api config in path <cwd>错误;配置文件通过require加载并作为数组返回,因此配置文件需要module.exports = [...]。

配置体系:ApiConfig 全字段与 api.config.js 实战

CLI 的配置类型定义在 src/types.ts,核心接口ApiConfig字段如下:

字段类型说明
entriesRecord<string, string>IDL 入口映射:键为服务别名(如promptManage),值为相对idlRoot的 thrift/proto 文件路径
idlRootstringIDL 源文件根目录
commonCodePathstring自定义公共代码(如src/api/config.ts)路径,会合并进产物
outputstring生成产物输出目录
repository{ url, dest }可选,远程 IDL 仓库地址与本地 clone 位置
idlFetchConfig{ source, branch?, commit?, rootDir? }可选,IDL 拉取来源、分支、commit 与根目录
pluginsIPlugin[]可选,追加的自定义插件
aggregationExportstring可选,聚合导出的文件名,默认index
formatter(name, content) => string可选,自定义格式化函数,优先级高于 prettier 配置
bannerstring可选,写入产物头部的版权/声明注释

ApiTypeConfig在其基础上增加filters: Record<string, string[]>——即“服务名 → 需要保留的方法名列表”,供filter命令使用。

coze-loop 的真实配置见 frontend/packages/loop-base/api-schema/api.config.js,它声明了 18 个 IDL 入口,覆盖 prompt、observability、evaluation、data、llm、foundation 六大业务域:

const config = [ { // idl 根目录:prethrift 脚本下载后的缓存位置 idlRoot: path.resolve( __dirname, 'node_modules/.cache/coze-loop-idl/thrift/coze/loop', ), genMock: false, banner: `// Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0`, entries: { promptDebug: './prompt/coze.loop.prompt.debug.thrift', promptManage: './prompt/coze.loop.prompt.manage.thrift', toolManage: './prompt/coze.loop.prompt.tool_manage.thrift', observabilityTrace: './observability/coze.loop.observability.trace.thrift', evaluationEvalSet: './evaluation/coze.loop.evaluation.eval_set.thrift', dataDataset: './data/coze.loop.data.dataset.thrift', llmManage: './llm/coze.loop.llm.manage.thrift', foundationUser: './foundation/coze.loop.foundation.user.thrift', foundationAuthn: './foundation/coze.loop.foundation.authn.thrift', foundationSpace: './foundation/coze.loop.foundation.space.thrift', // ... 其余入口省略 }, commonCodePath: path.resolve(__dirname, './src/api/config.ts'), // 自定义配置文件 output: './src/api/idl', // 产物所在位置 plugins: [], // 自定义插件 }, ]; module.exports = config;

几个要点:

  • entries的键会通过AliasPlugin改写 thrift 中 service 的名字,成为产物模块中 API 对象的命名空间;值则是相对idlRoot的 IDL 文件路径。
  • banner会由生成器侧的BannerPlugin写入每个产物的文件头部(生成结果见 src/api/idl/prompt/coze.loop.prompt.manage.ts 第 1~2 行)。
  • 配置中的genMock项是生成器Options的一部分,但从actions.ts的gen()看,CLI 目前硬编码向genClient传入genMock: false,即实际 Mock 生成与否以 CLI 传参为准(从源码结构可推断该字段在 CLI 场景下暂不生效)。
  • commonCodePath指向的src/api/config.ts会作为公共代码并入产物,业务侧通常用它定义ApiOption、ApiResponse等公共类型(@cozeloop/api-schema的src/index.ts即从./api/config导出这两个类型,见 frontend/packages/loop-base/api-schema/src/index.ts)。

gen()在内部按配置逐条执行:将entries的值解析为绝对路径、以键构建aliasMap,然后调用genClient(来自idl2ts-generator),默认entryName取aggregationExport || 'index'(见 src/actions.ts)。

生成产物形态:createAPI 客户端代码

生成的产物是可被业务代码直接引用的 TypeScript 模块。以promptManage入口为例,产物 coze.loop.prompt.manage.ts 中每个 thrift service 方法被转换为一个createAPI<Req, Res>调用,包含 URL、HTTP 方法、参数映射等元信息:

export const CreatePrompt = /*#__PURE__*/createAPI<CreatePromptRequest, CreatePromptResponse>({ "url": "/api/prompt/v1/prompts", "method": "POST", "name": "CreatePrompt", "reqType": "CreatePromptRequest", "reqMapping": { "body": ["workspace_id", "prompt_name", "prompt_key", "prompt_description", "prompt_type", "security_level", "draft_detail"] }, "resType": "CreatePromptResponse", "schemaRoot": "api://schemas/prompt_coze.loop.prompt.manage", "service": "promptManage" });

reqMapping中path/query/body分组清晰地标出了每个参数落入 HTTP 请求的哪个部分(如ClonePrompt的path: ["prompt_id"]、GetPrompt的query: [...]),这也是生成器在解析阶段按 thrift 注解与字段位置推断的结果。

@cozeloop/api-schema的src/index.ts进一步把各入口聚合为业务域 API 对象:

export const StoneEvaluationApi = { ...evaluationEvalSet, ...evaluationEvalTarget, ...evaluationEvaluator, ...evaluationExpt }; export const DataApi = { ...dataDataset, ...dateTag }; export const FoundationApi = { ...foundationUpload, ...foundationAuthn, ...foundationUser, ...foundationSpace }; export const StonePromptApi = { ...promptManage, ...promptDebug };

包在package.json的exports中按observation/evaluation/data/llm-manage/foundation/prompt等子路径对外暴露,业务侧可按需引入。

生成链路与源码实现:ClientGenerator 与插件 HOOK

CLI 只负责解析配置与组装参数,真正的生成由@coze-arch/idl2ts-generator的ClientGenerator驱动(见 frontend/infra/idl/idl2ts-generator/src/core.ts):

  1. 解析:parseAst在HOOK.PARSE_ENTRY阶段调用parseDSL把 thrift/proto 入口解析为统一 AST;
  2. 处理:processIdlAst在HOOK.PROCESS_IDL_AST阶段遍历语句,对入口文件中的 service 定义触发HOOK.PARSE_FUN_META,逐节点触发HOOK.PROCESS_IDL_NODE;其中按idlPath去重,同一文件只处理一次;
  3. 生成:各插件在HOOK.GEN_FILE_AST、HOOK.GEN_MOCK_FILED等钩子上产出.ts内容(以 Babel AST 表示,最终由@babel/generator输出代码、JSON.stringify输出 JSON、文本原样输出 text 类型文件);
  4. 落盘:HOOK.WRITE_FILE阶段经safeWriteFile写入产物文件,保证写入安全性。

ClientGenerator构造函数按固定顺序注册了一批内置插件:AutoFixPathPlugin(路径修复)、CommentFormatPlugin(注释格式化)、AutoFixDuplicateIncludesPlugin/AutoFixDuplicateNamespacePlugin(去重)、BannerPlugin(版权头)、AdapterPlugin、MetaPlugin、IgnoreStructFiledPlugin;当genClient开启时额外加载ClientPlugin与PkgEntryPlugin,genSchema时加载SchemaPlugin,genMock时加载MockTransformerPlugin。完整插件清单见 frontend/infra/idl/idl2ts-generator/src/plugin/index.ts。

CLI 侧传入的用户插件排在管线最前,且actions.ts固定注入四个:

插件作用源码
MockPlugin为 Mock 字段生成提供字段名启发式取值src/plugins/mock-plugin.ts
AliasPlugin将 service 名改写为entries的键(别名)src/plugins/alias.ts
FormatPlugin用 prettier(或自定义formatter)格式化产物src/plugins/formatter.ts
LocalConfigPlugin维护本地 Mock 开关文件api.dev.local.jssrc/plugins/local-config.ts

filter命令还会追加FilterTypesPlugin(见 src/plugins/filter-types-plugin.ts):它在HOOK.PARSE_ENTRY之后按filters白名单裁剪 AST,只保留指定方法及其递归引用的 struct/typedef 定义;遇到枚举字段时将其类型强制改写为number并追加@see<EnumName>注释,同时把涉及的枚举统一收集到产物的enums.ts({ value, label }数组形式)中,供前端下拉等场景消费。

Mock 开发链路:api.dev.local.js 与免重启热更新

本地联调是这套工具链的高频场景,coze-loop 的 Mock 链路分为三步:

  1. 生成 Mock 模块:MockPlugin注册在HOOK.GEN_MOCK_FILED,对基础类型字段按字段名启发式生成随机值(见 src/plugins/mock-plugin.ts):

    • 字符串字段:优先使用 IDL 默认值,否则用 faker 生成;字段名以ID结尾时生成数字字符串,包含Email时生成xxx@foo.com;
    • 数字字段:默认faker.number.int({ min: 0, max: 10000 }),以ID结尾、含TIME/TIMESTAMP(生成时间戳)、以STATUS结尾或含TYPE(生成 0/1)时都有专门策略;字段名为total映射为 1、code映射为 0;
    • 产物中对应的.mock.js文件由生成器按输出目录结构落盘(ClientGenerator.getMockFile将 thrift/proto 路径替换为.mock.js)。
  2. 维护 Mock 开关:LocalConfigPlugin在HOOK.GEN_FILE_AST之后读取(若存在)项目根目录的api.dev.local.js,把其中已有的mock数组合并写回,避免重复生成覆盖本地改动。文件内容形如:

    module.exports = { mock: ["promptManage.CreatePrompt", "dataDataset.GetDataset"], }
  3. 开发代理拦截:src/mock-dev.ts提供createProxy中间件(koa 风格,签名(_, __, req, resp)):当请求头携带x-svc-method(格式为服务名_方法名,如promptManage_CreatePrompt)且命中api.dev.local.js的mock列表时,从对应产物的.mock.js模块中取出data[svc][method].res(req)结果作为响应返回(resp.statusCode = 200,Content-Type: application/json)。其内部requiredWithoutCache通过临时清空Module._cache实现“修改 Mock 数据后无需重启服务”的热更新;找不到api.dev.local.js时会提示 “can not find mock config, please run gen-api command if you want to mock”。

开发与质量保障

idl2ts-cli的开发链路与 monorepo 内其他包保持一致:

  • 构建:build脚本为exit 0,因为 CLI 通过sucrase/register/ts直接运行 TypeScript 源码,无需预编译产物;
  • Lint:eslint ./ --cache,复用@coze-arch/eslint-config;
  • 测试:vitest --run --passWithNoTests,配置见 frontend/infra/idl/idl2ts-cli/vitest.config.ts,覆盖率模式test:cov使用@vitest/coverage-v8。

小结:一条命令打通 IDL 到前端 API 层

在 coze-loop 前端工程里,idl2ts gen ./一条命令完成了 IDL 拉取缓存、Thrift/Protobuf 解析、类型映射、Babel AST 生成、prettier 格式化、Mock 产物生成与本地 Mock 开关维护的全过程;idl2ts filter则提供了按需裁剪的类型子集产出能力。其配置(api.config.js)、插件(MockPlugin/AliasPlugin/FormatPlugin/LocalConfigPlugin/FilterTypesPlugin)与生成器核心(ClientGenerator的 HOOK 管线)全部在本仓库内开源可查,可作为前端工程化“协议驱动开发”的参考实现。

License

@coze-arch/idl2ts-cli与 coze-loop 前端其他 IDL 工具链包一样,采用 Apache-2.0 许可证。

  • 人工智能
  • 大模型
  • AI Agent
  • 提示工程
  • AI 评测
  • 可观测性
  • 后端
  • 前端

【免费下载链接】coze-loop

Next-generation AI Agent Optimization Platform: Cozeloop addresses challenges in AI agent development by providing full-lifecycle management capabilities from development, debugging, and evaluation to monitoring.

项目地址:https://gitcode.com/gh_mirrors/co/coze-loop
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进