S
Codex SDK 教程 TypeScript
第 04 章

ThreadItem 详解

理解 AI 的输出 — ThreadItem 详解

这章把 AI 产出的 7 种"东西"讲透:文字回复、命令执行、文件修改、MCP 调用、Web 搜索、待办事项、错误。

AI 不只是"说话"

跟普通聊天机器人不同,Codex 是一个 Agent——它不光会说话,还会干活。让它"帮你看看项目有什么问题",它可能会:

  1. 先执行 ls 看看有什么文件
  2. 再执行 cat package.json 读配置
  3. 然后执行 npm test 跑测试
  4. 最后给你一段文字总结

这 4 步产出了不同类型的东西:3 个命令执行 + 1 个文字回复。在 SDK 里,这些"东西"统一叫 ThreadItem

7 种 Item 一览

类型 type 字段 大白话 典型场景
文字回复 agent_message AI 说的话 回答问题、给出建议
推理摘要 reasoning AI 脑子里想的 展示思考过程
执行命令 command_execution 跑 shell 命令 ls、cat、npm test
文件修改 file_change 改了哪些文件 创建/修改/删除文件
MCP 工具 mcp_tool_call 调用外部工具 数据库查询、API 调用
Web 搜索 web_search 上网搜资料 查文档、搜 bug
待办事项 todo_list AI 给自己列的计划 复杂任务的工作清单
错误 error 非致命错误 某步失败但整体继续

下面逐个拆解。

AgentMessageItem — AI 说的话

这是最常见的 Item,就是 AI 的文字回复。

typescripttype AgentMessageItem = {
  id: string;
  type: "agent_message";
  text: string;  // 回复内容
};

重点

  • turn.finalResponse 就是从最后一个 agent_messagetext 取的
  • 如果用了 outputSchema(结构化输出),text 里面是 JSON 字符串
typescript// 使用示例
if (item.type === "agent_message") {
  console.log("AI 说:", item.text);
}

ReasoningItem — AI 在想什么

AI 有时候会先"想一想"再回答。想的过程就是 ReasoningItem。

typescripttype ReasoningItem = {
  id: string;
  type: "reasoning";
  text: string;  // 推理摘要
};

不是每次都有这个 Item,取决于模型和 modelReasoningEffort 配置。

typescriptif (item.type === "reasoning") {
  console.log("思考中:", item.text);
}

CommandExecutionItem — 跑命令

AI 需要获取信息或执行操作时,会跑 shell 命令。这是 Codex 最强大的能力之一。

typescripttype CommandExecutionItem = {
  id: string;
  type: "command_execution";
  command: string;             // 执行的命令,如 "ls -la"
  aggregated_output: string;   // stdout + stderr 合并输出
  exit_code?: number;          // 退出码(运行中时没有)
  status: "in_progress" | "completed" | "failed";
};

三个状态

  • in_progress:命令还在跑(item.started 事件时)
  • completed:跑完了,exit_code 为 0
  • failed:跑完了但出错,exit_code 非 0
typescriptif (item.type === "command_execution") {
  if (item.status === "completed") {
    console.log(`命令成功: ${item.command}`);
    console.log(`   输出: ${item.aggregated_output.slice(0, 200)}`);
  } else if (item.status === "failed") {
    console.log(`命令失败: ${item.command} (退出码: ${item.exit_code})`);
  }
}

FileChangeItem — 改文件

AI 创建、修改或删除文件时,会产出这个 Item。

typescripttype FileChangeItem = {
  id: string;
  type: "file_change";
  changes: Array<{
    path: string;                    // 文件路径
    kind: "add" | "update" | "delete"; // 新建 / 修改 / 删除
  }>;
  status: "completed" | "failed";
};

注意:一个 FileChangeItem 可能包含多个文件变更(一次 patch 改了好几个文件)。

typescriptif (item.type === "file_change") {
  for (const change of item.changes) {
    const actionMap = { add: "新建", update: "修改", delete: "删除" };
    console.log(`${actionMap[change.kind]}: ${change.path}`);
  }
}

McpToolCallItem — 调外部工具

MCP(Model Context Protocol)让 AI 能调用外部工具——数据库、API、自定义服务等。

typescripttype McpToolCallItem = {
  id: string;
  type: "mcp_tool_call";
  server: string;      // MCP 服务器名称
  tool: string;        // 工具名称
  arguments: unknown;  // 传给工具的参数
  result?: {           // 工具返回的结果
    content: McpContentBlock[];
    structured_content: unknown;
  };
  error?: { message: string };  // 如果调用失败
  status: "in_progress" | "completed" | "failed";
};
typescriptif (item.type === "mcp_tool_call") {
  console.log(`调用工具: ${item.server}/${item.tool}`);
  if (item.status === "completed" && item.result) {
    console.log("   结果:", JSON.stringify(item.result.structured_content));
  }
  if (item.error) {
    console.log("   错误:", item.error.message);
  }
}

其他 Item

WebSearchItem — 搜索

typescripttype WebSearchItem = { id: string; type: "web_search"; query: string };

AI 上网搜东西时产出。需要在 ThreadOptions 里启用 webSearchMode: "live"

TodoListItem — 待办清单

typescripttype TodoListItem = {
  id: string;
  type: "todo_list";
  items: Array<{ text: string; completed: boolean }>;
};

AI 处理复杂任务时,会先列个计划。这个 Item 会通过 item.updated 事件不断更新(完成一项勾一项)。

typescriptif (item.type === "todo_list") {
  console.log("工作计划:");
  for (const todo of item.items) {
    const mark = todo.completed ? "[x]" : "[ ]";
    console.log(`   ${mark} ${todo.text}`);
  }
}

ErrorItem — 非致命错误

typescripttype ErrorItem = { id: string; type: "error"; message: string };

AI 遇到了问题,但不影响整体执行。比如某个文件读不了,跳过继续。

实战:智能日志记录器

把所有 Item 类型综合起来,做一个能按类型分类记录的日志器:

typescript// item-logger.ts
import { Codex } from "@openai/codex-sdk";
import type { ThreadItem } from "@openai/codex-sdk";
import { appendFileSync, writeFileSync } from "fs";

// 初始化日志文件
writeFileSync("messages.log", "");
writeFileSync("commands.log", "");
writeFileSync("changes.log", "");

function logItem(item: ThreadItem): void {
  const timestamp = new Date().toISOString();

  switch (item.type) {
    case "agent_message":
      appendFileSync("messages.log", `[${timestamp}] ${item.text}\n\n`);
      console.log("已记录 AI 回复");
      break;

    case "command_execution":
      if (item.status !== "in_progress") {
        const log = `[${timestamp}] $ ${item.command}\nExit: ${item.exit_code}\n${item.aggregated_output}\n---\n`;
        appendFileSync("commands.log", log);
        console.log(`已记录命令: ${item.command}`);
      }
      break;

    case "file_change":
      for (const change of item.changes) {
        appendFileSync("changes.log", `[${timestamp}] ${change.kind}: ${change.path}\n`);
      }
      console.log(`已记录 ${item.changes.length} 个文件变更`);
      break;

    case "mcp_tool_call":
      console.log(`MCP 调用: ${item.server}/${item.tool}`);
      break;

    case "todo_list":
      const done = item.items.filter(t => t.completed).length;
      console.log(`计划进度: ${done}/${item.items.length}`);
      break;

    case "reasoning":
      console.log(`推理中...`);
      break;

    case "error":
      console.log(`错误: ${item.message}`);
      break;
  }
}

async function main() {
  const codex = new Codex();
  const thread = codex.startThread();

  const prompt = process.argv[2] || "分析当前项目的结构和代码质量";
  console.log(`任务: ${prompt}\n`);

  const { events } = await thread.runStreamed(prompt);

  for await (const event of events) {
    if (event.type === "item.completed") {
      logItem(event.item);
    } else if (event.type === "item.updated") {
      // todo_list 更新时也记录
      if (event.item.type === "todo_list") logItem(event.item);
    }
  }

  console.log("\n完成!日志已保存到 messages.log、commands.log、changes.log");
}

main().catch(console.error);

运行后会生成三个日志文件,分门别类记录 AI 的所有操作。

小结

  • ThreadItem 是 AI 产出的"东西",有 7 种类型
  • 最常用的三种:agent_message(回复)、command_execution(命令)、file_change(文件)
  • 通过 item.type 判断类型,用 switch-case 分别处理
  • item.status 告诉你这件事做完了没有(in_progress / completed / failed)
  • 流式模式下,同一个 item 会经历 started -> updated -> completed 三个阶段

下一章结构化输出 — 让 AI 乖乖返回 JSON,不再是自由文本。