接到什么?
一张“启动 Turn 的申请单”。
主要包括:请求编号 request_id、目标 thread_id、文字/图片等 input,以及工作目录、模型、权限、审批方式等可选设置。
先把 TurnStartParams 理解成“本次任务申请表”,不要背全部字段。
turn_start_inner你已经知道 submit_op 成功不等于任务完成。今天只补上中间这一站:App Server 收到 turn/start 后,怎样检查、整理并把工作交给 Core。
答案:turn_start_inner 在 App Server 层。
TUI 应用层(上游) → App Server / turn_start_inner(今天) → Core / start_or_steer_turn(下游)
它不是 TUI 里的界面组件,也不是 Core 里的任务执行者;它是客户端和 Core 之间的“受理、检查、转换与交接”位置。
params 和 load_thread。后面的 140 多行会继续逐块展开,不用一口气死记全部 Rust 语法。
submit_opturn/startturn_start_innerstart_or_steer_turnsubmit_op 先回到 TUI 应用协调层,是因为 ChatWidget 只是一个子组件;TUI 应用层接住内部命令后,才调用 App Server。到了 turn_start_inner,我们才真正站在 App Server 层。
源码依据:ChatWidget 组装并提交命令 · TUI 调用 turn/start · 组装 TurnStartParams
一张“启动 Turn 的申请单”。
主要包括:请求编号 request_id、目标 thread_id、文字/图片等 input,以及工作目录、模型、权限、审批方式等可选设置。
先把 TurnStartParams 理解成“本次任务申请表”,不要背全部字段。
确认这张申请单能安全、正确地往下交。
它查找目标 Thread,检查是否允许直接输入、文字是否过长,并整理工作目录、环境、模型、权限、审批等设置。外层 turn_start 还会先检查图片网址。
这是“受理检查”,不是模型推理。
把协议输入转换后,交给 Core。
它将 App Server 的 UserInput 映射成 Core 使用的 CoreInputItem,再调用 thread.start_or_steer_turn(...)。是否新开一轮还是追加到活动轮,后面第三轮再研究。
关键词:格式转换 + 调用 Core,不是自己执行任务。
返回 Turn 编号和 InProgress 状态。
返回的 Turn 此刻 items 还是空列表,状态是进行中。这相当于“已经受理,编号是 turn_xxx”,不等于任务已经成功完成。
真正结束要等后续 turn/completed 通知。
// 1. 找到要继续的 Thread
let (thread_id, thread) = self.load_thread(¶ms.thread_id).await?;
// 2. 检查输入,并整理本次设置
validate_input(...)?;
// 3. 把 App Server 输入转换成 Core 输入,再交给 Core
let mapped_items = params.input.map(UserInput::into_core);
let submission = thread.start_or_steer_turn(...).await?;
// 4. 返回“已受理、进行中”
Ok(TurnStartResponse { turn: Turn { status: InProgress, ... } })
这不是原代码逐字复制,只用于先建立地图。下面开始读真实代码;职责压缩图不能代替源码精读。
源码依据:turn_start_inner 完整实现 · 请求与响应类型 · 官方生命周期说明
async fn turn_start_inner(
&self,
request_id: ConnectionRequestId,
params: TurnStartParams,
app_server_client_name: Option<String>,
app_server_client_version: Option<String>,
) -> Result<TurnStartResponse, JSONRPCErrorError> {
| 代码片段 | 逐词含义 | 放在本函数里意味着什么 |
|---|---|---|
async | 异步函数标记 | 函数内部可以使用 .await 等待 Thread 查询等异步操作;调用者也要 .await 它。 |
fn | Rust 的“声明函数”关键字 | 后面紧跟函数名、参数和返回类型。 |
turn_start_inner | 函数名;inner 不是关键字 | 外层 turn_start 先检查图片 URL,再调用这个内部实现。 |
&self | 借用当前对象,不取得所有权 | 当前对象是 TurnRequestProcessor;函数借助它持有的 thread_manager、outgoing 等服务完成工作。 |
名称: 类型 | Rust 参数写法 | 例如 params: TurnStartParams 表示变量名叫 params,编译期类型是 TurnStartParams。 |
Option<String> | 可能有字符串,也可能没有 | 客户端名称和版本不是每次请求都必须提供;有值是 Some(...),无值是 None。 |
-> Result<A, B> | 返回“成功 A 或失败 B” | 成功返回 TurnStartResponse;失败返回可发回客户端的 JSONRPCErrorError。 |
params 到底是什么?params 是 parameters(参数)的缩写,只是程序员取的变量名,不是 Rust 关键字。
客户端发来的 JSON-RPC params 对象,会被 App Server 反序列化成一个 Rust 结构体 TurnStartParams。因此,params.thread_id 就是在读取这张“启动 Turn 申请单”的 thread_id 字段。
| 字段类别 | TurnStartParams 中的字段 | 回答的问题 |
|---|---|---|
| 必填身份 | thread_id: String | 这次输入属于哪个 Thread? |
| 必填输入 | input: Vec<UserInput> | 用户提交了哪些文字、图片、音频、Skill 或 Mention?Vec 表示列表,因此一条提交里可以同时有文字和图片。 |
| 消息与触发来源 | client_user_message_id、turn_trigger | 客户端如何标识这条消息?是谁或什么触发了 Turn? |
| 运行环境 | cwd、runtime_workspace_roots、environments | 在哪个目录、哪些工作区和环境中工作? |
| 安全控制 | approval_policy、approvals_reviewer、sandbox_policy、permissions | 什么操作需要审批?允许访问什么? |
| 模型行为 | model、service_tier、service_tier_for_turn、effort、summary、personality、collaboration_mode | 用哪个模型、速度档位、推理强度和协作方式? |
| 上下文与输出 | additional_context、responsesapi_client_metadata、output_schema、cyber_access_program | 额外带什么上下文或元数据?最终输出是否必须满足 JSON Schema? |
| 已弃用 | multi_agent_mode | 为了协议兼容仍保留,但当前实现说明它已被忽略。 |
只有 thread_id 和 input 是非 Option 字段。源码用 snake_case 写 thread_id,JSON 因 serde(rename_all = "camelCase") 使用 threadId。
{
"threadId": "019...",
"input": [
{ "type": "text", "text": "解释这张图", "textElements": [] },
{ "type": "localImage", "path": "C:\\Temp\\diagram.png" }
],
"cwd": "C:\\Users\\admin\\project",
"model": "gpt-5.6-sol"
}
未出现的可选字段反序列化后通常是 None。这也解释了你之前说的“列表里既有图片也有文字”:它们就是 input: Vec<UserInput> 中的两个元素。
源码依据:TurnStartParams · UserInput 类型
load_thread 这一句let (thread_id, thread) =
self.load_thread(¶ms.thread_id)
.await
.inspect_err(|error| {
self.track_error_response(&request_id, error, None);
})?;
| 代码片段 | 含义 |
|---|---|
let | 创建局部变量绑定。 |
(thread_id, thread) | 元组解构:右边成功返回两个值,分别放进“强类型 ID”和“Thread 对象句柄”。 |
self.load_thread(...) | 调用当前 TurnRequestProcessor 上的辅助方法。 |
¶ms.thread_id | 从申请单取出 thread_id,再借用它。& 避免移动或克隆字符串;Rust 会把 &String 自动借用转换为方法需要的 &str。 |
.await | 异步等待查询结果;等待期间运行时可以调度其他任务,并不等于把整个程序线程卡死。 |
.inspect_err(...) | 如果结果是错误,顺便记录埋点;它观察错误但不改变错误类型。 |
|error| { ... } | 闭包,即匿名函数;error 是传给闭包的错误引用。 |
? | 错误传播:若为 Err,当前 turn_start_inner 立即返回该错误;若为 Ok,取出里面的二元组继续执行。 |
; | 结束这一条语句。 |
load_thread 内部实际做了什么?async fn load_thread(
&self,
thread_id: &str,
) -> Result<(ThreadId, Arc<CodexThread>), JSONRPCErrorError> {
let thread_id = ThreadId::from_string(thread_id)
.map_err(|err| invalid_request(format!("invalid thread id: {err}")))?;
let thread = self.thread_manager
.get_thread(thread_id)
.await
.map_err(|_| invalid_request(format!("thread not found: {thread_id}")))?;
Ok((thread_id, thread))
}
ThreadId::from_string:把普通字符串校验并转换成专门的 ThreadId 类型;格式不合法就返回 invalid thread id。thread_manager.get_thread:拿强类型 ID 去当前内存中的 Thread 表查找;找不到就返回 thread not found。Arc<CodexThread>:返回可在异步代码间安全共享所有权的 Thread 句柄。Arc 是原子引用计数智能指针。Ok((thread_id, thread)):成功时同时返回强类型 ID 和 Thread 句柄,供后续检查、配置和提交 Turn 使用。这里的 load_thread 并不是“读取昨天的 JSONL 历史并恢复 Thread”。它最终调用的是 ThreadManager::get_thread,从当前已经加载的、非内部 Thread 集合中取句柄。真正把冷 Thread 从持久化记录恢复到内存,属于 thread/resume 等更早的生命周期步骤。
源码依据:load_thread · ThreadManager::get_thread
turn/start 返回成功 ≠ Turn 成功完成。
前者表示 App Server 已经把申请交给 Core,并拿到了 Turn 编号;后者必须等执行过程结束,再看 turn/completed 中究竟是 completed、failed 还是 interrupted。
Codex 的 turn_start_inner | 未来 GEODYNA 智能体 |
|---|---|
| 接收 Thread、输入和运行设置 | 接收用户工程目标、当前项目、选择对象和执行约束 |
| 确认 Thread、输入长度、权限与环境 | 确认项目状态、对象是否存在、命令是否允许、是否需要用户确认 |
把协议输入转换成 CoreInputItem | 把自然语言计划转换成正式 ProjectCommand 或原子命令批次 |
调用 start_or_steer_turn | 通过既有 Smart Command / Script API 提交预览或原子事务 |
先返回 InProgress | 先返回“任务已受理/待确认/执行中”,后续持续报告进度与结果 |
只读源码已经显示,GEODYNA 现有 useSmartCommandApplication.ts 会准备上下文、生成预览、校验计划、编译命令并提供执行/撤销入口;domain/project/scriptApi.ts 会通过 runWorkflow 执行正式的原子命令事务。这些是未来 Harness 可以复用的基础,但它们还不等于完整智能体。
GEODYNA 只读依据:D:\GEODAMNEW\features\smartCommand\useSmartCommandApplication.ts:277 · D:\GEODAMNEW\domain\project\scriptApi.ts:221。本课没有修改 GEODYNA。
| 产品问题 | 这一站给出的答案 |
|---|---|
| 用户点发送后,界面能否直接说“已完成”? | 不能。只能先显示“已受理/进行中”,最终状态由后续通知确认。 |
| 为什么不让模型直接调用底层代码? | 中间层要校验身份、项目状态、权限、参数和安全策略,并转换成稳定的正式命令。 |
| 接口成功率应该统计什么? | 至少区分“请求受理成功”和“业务任务最终完成”,否则指标会虚高。 |
| GEODYNA 智能体的价值证据是什么? | 不仅能理解意图,还能安全交接、展示状态、处理失败并给出可审计结果。 |
不展开环境覆盖、Memory 启动、Realtime transcript、Responses API metadata、Start/Steer 的具体判断,也不学习 Rust 的 await、map_err 和所有权。它们分别属于后续数据轮、状态轮和运行机制轮。
turn_start_inner 返回 InProgress,是否说明模型已经完成任务?请不要照抄,按你的理解补完整:
turn_start_inner 位于 ______ 层。它收到 ______,先 ______,再把 ______ 交给 ______,立即返回 ______;这不表示 ______。
你已经通过本课,可以进入第一轮第 3 站:start_or_steer_turn 源码精读。