# codexClient.cjs 说明 ## 文件作用 这个文件是后端与本机 Codex CLI / `@openai/codex-sdk` 的适配层。它负责检查登录状态、启动登录、退出登录、构造 Codex 请求线程、执行普通回复和流式回复,并把这些能力导出给 Express 路由使用。 ## 主要常量和状态 - `PROJECT_ROOT`:项目根目录。 - `CODEX_WORKSPACE`:专门给 Codex SDK 使用的工作目录 `.codex-chat-workspace`。 - `DEFAULT_MODEL`:来自环境变量 `CODEX_MODEL` 的默认模型。 - `DEFAULT_REASONING_EFFORT`:来自环境变量 `CODEX_REASONING_EFFORT` 的默认推理强度。 - `AUTH_STATUS_CACHE_MS`:登录状态缓存时间。 - `sdkPromise`:缓存动态导入的 Codex SDK。 - `authStatusCache`:缓存最近一次登录状态。 - `loginState`:保存当前或最近一次登录命令的运行状态和日志。 文件加载时会创建 `CODEX_WORKSPACE` 目录。 ## 主要函数 ### `getCodexBin()` 查找 Codex CLI 可执行文件。优先使用项目内 `node_modules/.bin/codex`,找不到时回退到全局 `codex` 命令。 ### `getCodexHomeHint()` 返回 Codex 本地配置目录提示。优先使用 `CODEX_HOME` 环境变量,否则使用用户目录下的 `.codex`。 ### `appendLoginLine(chunk)` 把登录命令输出追加到 `loginState.lines`。它会跳过空行,并且只保留最近 120 行,避免日志无限增长。 ### `runCodexCommand(args, options)` 执行 Codex CLI 命令的通用封装: - 使用 `spawn` 启动命令。 - 在 `CODEX_WORKSPACE` 下运行。 - 收集 stdout 和 stderr。 - 支持传入 stdin。 - 支持超时,超时后终止子进程。 - 最终返回退出码、信号、输出和错误对象。 它主要用于登录状态检查和退出登录。 ### `loadCodexSdk()` 动态导入 `@openai/codex-sdk`,并用 `sdkPromise` 缓存导入结果,避免重复加载。 ### `normalizeInput(input)` 清洗前端传入的消息数组: - 只保留对象类型且有字符串 `role` 的项。 - 将 `content` 转为字符串。 - 去掉空内容消息。 ### `roleTitle(role)` 把请求角色转换成写入提示词的标题: - `developer` 或 `system` 显示为开发者指令。 - `assistant` 显示为 assistant。 - 其他角色显示为 user。 ### `buildCodexPrompt(input)` 把结构化消息数组转换成单个 Codex prompt: - 先标准化输入。 - 将每条消息转换成 `### 角色标题` 加正文的文本块。 - 在开头加入本地网页聊天工具的约束说明。 - 要求模型只回复最后一条 user 消息,不混入其他会话上下文。 ### `normalizeModel(model)` 合并请求体模型名和环境变量默认模型,并返回修剪后的字符串。 ### `normalizeReasoningEffort(value)` 校验推理强度,只允许 `minimal`、`low`、`medium`、`high`、`xhigh`。非法值会回退到 `medium`。 ### `createThreadOptions(body)` 创建 Codex SDK thread 运行选项: - 使用只读沙箱。 - 禁用审批。 - 固定工作目录为 `CODEX_WORKSPACE`。 - 跳过 Git 仓库检查。 - 禁用网络和 Web 搜索。 - 透传推理强度和可选模型。 这些设置降低本地聊天代理对用户文件和网络的影响范围。 ### `createCodexClient()` 创建 Codex SDK 客户端,并关闭原始 agent reasoning 输出,让前端只展示面向用户的最终回答。 ### `hasUsableAuth(status)` 判断 Codex 是否可用:既要安装 CLI,也要处于已登录状态。 ### `getAuthStatus(options)` 检查 Codex 登录状态: - 默认使用短期缓存,`force` 为真时强制刷新。 - 执行 `codex login status`。 - 根据退出码判断是否已登录。 - 返回安装状态、登录状态、认证模式输出、提示消息、Codex home 和检查时间。 ### `ensureLoggedIn()` 在执行聊天请求前校验 Codex 可用性。CLI 未安装时抛出 503 错误;未登录时抛出 401 错误。 ### `startLogin(options)` 启动 Codex 登录流程: - 如果已有登录进程在跑,直接返回当前登录状态。 - 默认启动 `codex login`。 - `deviceAuth` 为真时追加 `--device-auth`。 - 保存进程 pid、开始时间、结束时间、退出码、信号和输出日志。 - 登录进程结束后清空认证状态缓存。 ### `getLoginState()` 返回当前登录状态快照,供前端轮询展示。 ### `logoutCodex()` 执行 `codex logout`,清空认证状态缓存,并返回命令结果。 ### `runCodexOnce(body, signal)` 执行一次性聊天请求: - 先确认 Codex 已登录。 - 用 `buildCodexPrompt` 生成 prompt。 - prompt 为空时抛出 400 错误。 - 创建 Codex client 和 thread。 - 调用 `thread.run(prompt, { signal })`。 - 返回最终文本、thread id 和 usage。 ### `runCodexStream(body, signal, handlers)` 执行流式聊天请求: - 校验登录和 prompt。 - 调用 `thread.runStreamed(prompt, { signal })`。 - 遍历 SDK 返回的事件流。 - 对 agent message 计算增量文本,并调用 `handlers.onDelta(delta)`。 - 记录 `turn.completed` 的 usage。 - 遇到 SDK 错误事件时抛出后端错误。 - 返回完整文本、thread id 和 usage。 ## 导出接口 该文件导出: - `getAuthStatus` - `getLoginState` - `logoutCodex` - `runCodexOnce` - `runCodexStream` - `startLogin` 这些函数由 `server/index.cjs` 的 API 路由调用。