codex-mobile-bridge 0.2.11

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

#[test]
fn timeline_entry_from_thread_item_preserves_render_metadata_for_tool_items() {
    let entry = timeline_entry_from_thread_item(
        PRIMARY_RUNTIME_ID,
        "thread-1",
        Some("turn-1"),
        &json!({
            "id": "item-tool-1",
            "type": "mcpToolCall",
            "server": "openai-docs",
            "tool": "search",
            "arguments": {
                "q": "Responses streaming events"
            },
            "result": {
                "hits": 3
            }
        }),
        "thread_item",
        false,
        true,
    )
    .expect("应创建 mcpToolCall 时间线条目");

    assert_eq!("mcpToolCall", entry.entry_type);
    assert_eq!(Some("openai-docs / search"), entry.title.as_deref());
    assert_eq!(
        Some("tool"),
        entry.metadata.get("renderKind").and_then(Value::as_str),
    );
    assert_eq!(
        Some("mcpToolCall"),
        entry.metadata.get("rawType").and_then(Value::as_str),
    );
    assert_eq!(
        Some("search"),
        entry
            .metadata
            .get("payload")
            .and_then(|payload| payload.get("tool"))
            .and_then(Value::as_str),
    );
}

#[test]
fn timeline_entry_from_thread_item_normalizes_responses_message_roles() {
    let assistant_entry = timeline_entry_from_thread_item(
        PRIMARY_RUNTIME_ID,
        "thread-1",
        Some("turn-1"),
        &json!({
            "id": "item-message-1",
            "type": "message",
            "role": "assistant",
            "phase": "final_answer",
            "status": "completed",
            "content": [
                {
                    "type": "output_text",
                    "text": "最终回复正文"
                }
            ]
        }),
        "stream_event",
        false,
        true,
    )
    .expect("应创建 assistant message 时间线条目");

    assert_eq!("agentMessage", assistant_entry.entry_type);
    assert_eq!(Some("最终回复"), assistant_entry.title.as_deref());
    assert_eq!("最终回复正文", assistant_entry.text);
    assert_eq!(
        Some("message"),
        assistant_entry
            .metadata
            .get("rawType")
            .and_then(Value::as_str),
    );
    assert_eq!(
        Some("assistant"),
        assistant_entry
            .metadata
            .get("payload")
            .and_then(|payload| payload.get("role"))
            .and_then(Value::as_str),
    );

    let user_entry = timeline_entry_from_thread_item(
        PRIMARY_RUNTIME_ID,
        "thread-1",
        Some("turn-1"),
        &json!({
            "id": "item-message-2",
            "type": "message",
            "role": "user",
            "content": [
                {
                    "type": "input_text",
                    "text": "用户输入"
                }
            ]
        }),
        "thread_item",
        false,
        true,
    )
    .expect("应创建 user message 时间线条目");

    assert_eq!("userMessage", user_entry.entry_type);
    assert_eq!(Some(""), user_entry.title.as_deref());
    assert_eq!("用户输入", user_entry.text);
}