use super::history::enrich_thread_history_from_rollout;
use super::*;
impl BridgeState {
pub(super) fn thread_response_payload(
&self,
runtime_id: &str,
result: &Value,
archived: bool,
) -> Result<Value> {
let thread_value = result.get("thread").context("返回缺少 thread 字段")?;
let thread = normalize_thread(runtime_id, thread_value, archived)?;
self.storage.upsert_thread_index(&thread)?;
let _ = self.storage.record_directory_usage(Path::new(&thread.cwd));
let _ = self.emit_directory_state();
let thread = self.storage.get_thread_index(&thread.id)?.unwrap_or(thread);
let effective_cwd = result
.get("cwd")
.and_then(Value::as_str)
.map(ToString::to_string)
.unwrap_or_else(|| thread.cwd.clone());
let runtime_codex_home = self
.storage
.get_runtime(runtime_id)?
.and_then(|runtime| runtime.codex_home);
let mut render_thread = thread_value.clone();
enrich_thread_history_from_rollout(&mut render_thread, runtime_codex_home.as_deref());
let render_snapshot = self.cache_thread_render_snapshot(build_thread_render_snapshot(
runtime_id,
&render_thread,
)?);
let active_turn = extract_active_turn_payload(&render_thread);
Ok(json!({
"runtimeId": runtime_id,
"thread": thread,
"effectiveCwd": effective_cwd,
"renderSnapshot": render_snapshot,
"activeTurn": active_turn,
}))
}
}
fn extract_active_turn_payload(thread: &Value) -> Option<Value> {
let thread_active = thread
.get("status")
.and_then(|status| {
status
.get("type")
.and_then(Value::as_str)
.or_else(|| status.as_str())
})
.is_some_and(|status| status == "active");
if !thread_active {
return None;
}
let turns = thread.get("turns").and_then(Value::as_array)?;
let active_turn = turns.iter().rev().find(|turn| {
turn.get("status")
.and_then(Value::as_str)
.is_some_and(|status| status == "inProgress")
})?;
let turn_id = active_turn.get("id").and_then(Value::as_str)?;
Some(json!({
"turnId": turn_id,
"startedAtMs": active_turn
.get("startedAt")
.and_then(thread_started_at_ms),
}))
}
fn thread_started_at_ms(value: &Value) -> Option<i64> {
let started_at = value
.as_i64()
.or_else(|| value.as_u64().and_then(|raw| i64::try_from(raw).ok()))?;
if started_at >= 10_000_000_000 {
Some(started_at)
} else {
started_at.checked_mul(1_000)
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::extract_active_turn_payload;
#[test]
fn extract_active_turn_payload_converts_seconds_to_millis() {
let thread = json!({
"status": {
"type": "active"
},
"turns": [
{
"id": "turn-complete",
"status": "completed",
"startedAt": 1_710_000_000
},
{
"id": "turn-active",
"status": "inProgress",
"startedAt": 1_710_000_123
}
]
});
assert_eq!(
extract_active_turn_payload(&thread),
Some(json!({
"turnId": "turn-active",
"startedAtMs": 1_710_000_123_000_i64,
})),
);
}
}