use super::*;
#[test]
fn timeline_lifecycle_metadata_tracks_waiting_streaming_and_terminal_states() {
let waiting_entry = timeline_entry_from_thread_item(
PRIMARY_RUNTIME_ID,
"thread-1",
Some("turn-1"),
&json!({
"id": "item-reason-1",
"type": "reasoning",
"status": "inProgress",
"summary": [],
"content": []
}),
"stream_event",
true,
false,
)
.expect("应创建 waiting reasoning 条目");
assert_eq!(
Some(4),
waiting_entry
.metadata
.get("schemaVersion")
.and_then(Value::as_i64),
);
assert_eq!(
Some("waiting"),
waiting_entry
.metadata
.get("lifecycle")
.and_then(|value| value.get("stage"))
.and_then(Value::as_str),
);
assert_eq!(
Some(false),
waiting_entry
.metadata
.get("lifecycle")
.and_then(|value| value.get("hasVisibleContent"))
.and_then(Value::as_bool),
);
let streaming_delta = normalize_delta_payload(
PRIMARY_RUNTIME_ID,
json!({
"threadId": "thread-1",
"turnId": "turn-1",
"itemId": "item-agent-1",
"delta": "先分析协议"
}),
"agentMessage",
Some("Codex".to_string()),
Some("inProgress".to_string()),
"item/agentMessage/delta",
json!({
"role": "assistant",
"phase": "commentary"
}),
None,
None,
);
assert_eq!(
Some("streaming"),
streaming_delta
.get("metadata")
.and_then(|value| value.get("lifecycle"))
.and_then(|value| value.get("stage"))
.and_then(Value::as_str),
);
let waiting_delta = normalize_delta_payload(
PRIMARY_RUNTIME_ID,
json!({
"threadId": "thread-1",
"turnId": "turn-1",
"itemId": "item-agent-1",
"delta": ""
}),
"reasoning",
Some("思考过程".to_string()),
Some("inProgress".to_string()),
"item/reasoning/summaryPartAdded",
json!({
"summary": [],
"content": []
}),
Some(0),
None,
);
assert_eq!(
Some("waiting"),
waiting_delta
.get("metadata")
.and_then(|value| value.get("lifecycle"))
.and_then(|value| value.get("stage"))
.and_then(Value::as_str),
);
let completed_entry = timeline_entry_from_thread_item(
PRIMARY_RUNTIME_ID,
"thread-1",
Some("turn-1"),
&json!({
"id": "item-message-3",
"type": "message",
"role": "assistant",
"phase": "final_answer",
"status": "completed",
"content": [
{
"type": "output_text",
"text": "最终内容"
}
]
}),
"thread_item",
false,
true,
)
.expect("应创建 completed message 条目");
assert_eq!(
Some("completed"),
completed_entry
.metadata
.get("lifecycle")
.and_then(|value| value.get("stage"))
.and_then(Value::as_str),
);
let failed_entry = timeline_entry_from_thread_item(
PRIMARY_RUNTIME_ID,
"thread-1",
Some("turn-1"),
&json!({
"id": "item-command-err",
"type": "commandExecution",
"status": "failed",
"command": "cargo test",
"aggregatedOutput": "boom"
}),
"thread_item",
false,
true,
)
.expect("应创建 failed command 条目");
assert_eq!(
Some("failed"),
failed_entry
.metadata
.get("lifecycle")
.and_then(|value| value.get("stage"))
.and_then(Value::as_str),
);
let declined_entry = timeline_entry_from_thread_item(
PRIMARY_RUNTIME_ID,
"thread-1",
Some("turn-1"),
&json!({
"id": "item-file-declined",
"type": "fileChange",
"status": "declined",
"changes": []
}),
"thread_item",
false,
true,
)
.expect("应创建 declined fileChange 条目");
assert_eq!(
Some("declined"),
declined_entry
.metadata
.get("lifecycle")
.and_then(|value| value.get("stage"))
.and_then(Value::as_str),
);
}