codex-mobile-bridge 0.2.10

Remote bridge and service manager for codex-mobile.
Documentation
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),
    );
}