codex-mobile-bridge 0.2.5

Remote bridge and service manager for codex-mobile.
Documentation
use super::*;

#[test]
fn timeline_entries_from_events_merge_reasoning_deltas_and_replace_plan_placeholder() {
    let authoritative_plan = serde_json::to_value(TimelineEntry {
        id: "turn-1:plan-1".to_string(),
        runtime_id: PRIMARY_RUNTIME_ID.to_string(),
        thread_id: "thread-1".to_string(),
        turn_id: Some("turn-1".to_string()),
        item_id: Some("plan-1".to_string()),
        entry_type: "plan".to_string(),
        title: Some("执行计划".to_string()),
        text: "[进行中] 读取线程".to_string(),
        status: Some("inProgress".to_string()),
        metadata: json!({
            "schemaVersion": 1,
            "sourceKind": "stream_event",
            "rawType": "plan",
            "renderKind": "plan",
            "collapseHint": {
                "enabled": true,
                "preferCollapsed": false,
                "lineThreshold": 8,
                "charThreshold": 320
            },
            "stream": {
                "isStreaming": false,
                "authoritative": true
            },
            "payload": {
                "explanation": "先确认协议,再更新 UI。",
                "plan": [
                    {
                        "step": "读取线程",
                        "status": "inProgress"
                    }
                ]
            }
        }),
    })
    .expect("authoritative plan 应可序列化");

    let events = vec![
        PersistedEvent {
            seq: 1,
            event_type: "plan_delta".to_string(),
            runtime_id: Some(PRIMARY_RUNTIME_ID.to_string()),
            thread_id: Some("thread-1".to_string()),
            payload: normalize_delta_payload(
                PRIMARY_RUNTIME_ID,
                json!({
                    "threadId": "thread-1",
                    "turnId": "turn-1",
                    "itemId": "turn-plan:turn-1",
                    "delta": "[进行中] 读取线程"
                }),
                "plan",
                Some("执行计划".to_string()),
                Some("inProgress".to_string()),
                "item/plan/delta",
                json!({
                    "explanation": null,
                    "plan": [],
                }),
                None,
                None,
            ),
            created_at_ms: 1,
        },
        PersistedEvent {
            seq: 2,
            event_type: "turn_plan_updated".to_string(),
            runtime_id: Some(PRIMARY_RUNTIME_ID.to_string()),
            thread_id: Some("thread-1".to_string()),
            payload: authoritative_plan,
            created_at_ms: 2,
        },
        PersistedEvent {
            seq: 3,
            event_type: "reasoning_summary_part_added".to_string(),
            runtime_id: Some(PRIMARY_RUNTIME_ID.to_string()),
            thread_id: Some("thread-1".to_string()),
            payload: normalize_delta_payload(
                PRIMARY_RUNTIME_ID,
                json!({
                    "threadId": "thread-1",
                    "turnId": "turn-1",
                    "itemId": "reason-1",
                    "delta": ""
                }),
                "reasoning",
                Some("思考过程".to_string()),
                Some("inProgress".to_string()),
                "item/reasoning/summaryPartAdded",
                json!({
                    "summary": [],
                    "content": [],
                }),
                Some(0),
                None,
            ),
            created_at_ms: 3,
        },
        PersistedEvent {
            seq: 4,
            event_type: "reasoning_summary_text_delta".to_string(),
            runtime_id: Some(PRIMARY_RUNTIME_ID.to_string()),
            thread_id: Some("thread-1".to_string()),
            payload: normalize_delta_payload(
                PRIMARY_RUNTIME_ID,
                json!({
                    "threadId": "thread-1",
                    "turnId": "turn-1",
                    "itemId": "reason-1",
                    "delta": "先分析"
                }),
                "reasoning",
                Some("思考过程".to_string()),
                Some("inProgress".to_string()),
                "item/reasoning/summaryTextDelta",
                json!({
                    "summary": [],
                    "content": [],
                }),
                Some(0),
                None,
            ),
            created_at_ms: 4,
        },
        PersistedEvent {
            seq: 5,
            event_type: "reasoning_text_delta".to_string(),
            runtime_id: Some(PRIMARY_RUNTIME_ID.to_string()),
            thread_id: Some("thread-1".to_string()),
            payload: normalize_delta_payload(
                PRIMARY_RUNTIME_ID,
                json!({
                    "threadId": "thread-1",
                    "turnId": "turn-1",
                    "itemId": "reason-1",
                    "delta": "检查流式合并"
                }),
                "reasoning",
                Some("思考过程".to_string()),
                Some("inProgress".to_string()),
                "item/reasoning/textDelta",
                json!({
                    "summary": [],
                    "content": [],
                }),
                None,
                Some(0),
            ),
            created_at_ms: 5,
        },
    ];

    let entries = timeline_entries_from_events(&events);
    let plan_entries = entries
        .iter()
        .filter(|entry| entry.entry_type == "plan")
        .collect::<Vec<_>>();
    assert_eq!(1, plan_entries.len());
    assert_eq!(Some("plan-1"), plan_entries[0].item_id.as_deref());

    let reasoning_entry = entries
        .iter()
        .find(|entry| entry.item_id.as_deref() == Some("reason-1"))
        .expect("应合并 reasoning 条目");
    assert!(reasoning_entry.text.contains("思考摘要\n先分析"));
    assert!(reasoning_entry.text.contains("思考内容\n检查流式合并"));
    assert_eq!(
        Some("thinking"),
        reasoning_entry
            .metadata
            .get("renderKind")
            .and_then(Value::as_str),
    );
}