unified-agent-api-codex 0.3.5

Async wrapper around the Codex CLI for programmatic prompting
Documentation
use codex::{
    EventError, ItemDelta, ItemDeltaPayload, ItemEnvelope, ItemFailure, ItemPayload, ItemSnapshot,
    ItemStatus, TextContent, TextDelta, ThreadEvent, ThreadStarted, TurnCompleted, TurnFailed,
    TurnStarted,
};
use std::collections::BTreeMap;

fn stored_thread_id(event: &ThreadEvent) -> &String {
    match event {
        ThreadEvent::ThreadStarted(inner) => &inner.thread_id,
        ThreadEvent::TurnStarted(inner) => &inner.thread_id,
        ThreadEvent::TurnCompleted(inner) => &inner.thread_id,
        ThreadEvent::TurnFailed(inner) => &inner.thread_id,
        ThreadEvent::ItemStarted(inner) => &inner.thread_id,
        ThreadEvent::ItemDelta(inner) => &inner.thread_id,
        ThreadEvent::ItemCompleted(inner) => &inner.thread_id,
        ThreadEvent::ItemFailed(inner) => &inner.thread_id,
        ThreadEvent::Error(_) => panic!("ThreadEvent::Error has no thread id"),
    }
}

fn assert_thread_id_borrowed(event: &ThreadEvent, expected: &str) {
    let id = event
        .thread_id()
        .expect("expected thread_id() to return Some");
    assert_eq!(id, expected);

    let stored = stored_thread_id(event);
    assert_eq!(id.as_ptr(), stored.as_ptr());
    assert_eq!(id.len(), stored.len());
}

#[test]
fn thread_id_accessor_is_pinned_and_borrowed() {
    let expected = "thread-123";

    let events = vec![
        ThreadEvent::ThreadStarted(ThreadStarted {
            thread_id: expected.to_string(),
            extra: BTreeMap::new(),
        }),
        ThreadEvent::TurnStarted(TurnStarted {
            thread_id: expected.to_string(),
            turn_id: "turn-1".to_string(),
            input_text: None,
            extra: BTreeMap::new(),
        }),
        ThreadEvent::TurnCompleted(TurnCompleted {
            thread_id: expected.to_string(),
            turn_id: "turn-1".to_string(),
            last_item_id: None,
            extra: BTreeMap::new(),
        }),
        ThreadEvent::TurnFailed(TurnFailed {
            thread_id: expected.to_string(),
            turn_id: "turn-1".to_string(),
            error: EventError {
                message: "fail".to_string(),
                code: None,
                extra: BTreeMap::new(),
            },
            extra: BTreeMap::new(),
        }),
        ThreadEvent::ItemStarted(ItemEnvelope {
            thread_id: expected.to_string(),
            turn_id: "turn-1".to_string(),
            item: ItemSnapshot {
                item_id: "item-1".to_string(),
                index: None,
                status: ItemStatus::InProgress,
                payload: ItemPayload::AgentMessage(TextContent {
                    text: "".to_string(),
                    extra: BTreeMap::new(),
                }),
                extra: BTreeMap::new(),
            },
        }),
        ThreadEvent::ItemDelta(ItemDelta {
            thread_id: expected.to_string(),
            turn_id: "turn-1".to_string(),
            item_id: "item-1".to_string(),
            index: None,
            delta: ItemDeltaPayload::AgentMessage(TextDelta {
                text_delta: "".to_string(),
                extra: BTreeMap::new(),
            }),
            extra: BTreeMap::new(),
        }),
        ThreadEvent::ItemCompleted(ItemEnvelope {
            thread_id: expected.to_string(),
            turn_id: "turn-1".to_string(),
            item: ItemSnapshot {
                item_id: "item-1".to_string(),
                index: None,
                status: ItemStatus::Completed,
                payload: ItemPayload::AgentMessage(TextContent {
                    text: "".to_string(),
                    extra: BTreeMap::new(),
                }),
                extra: BTreeMap::new(),
            },
        }),
        ThreadEvent::ItemFailed(ItemEnvelope {
            thread_id: expected.to_string(),
            turn_id: "turn-1".to_string(),
            item: ItemFailure {
                item_id: "item-1".to_string(),
                index: None,
                error: EventError {
                    message: "fail".to_string(),
                    code: None,
                    extra: BTreeMap::new(),
                },
                extra: BTreeMap::new(),
            },
        }),
    ];

    for event in &events {
        assert_thread_id_borrowed(event, expected);
    }
}

#[test]
fn error_event_has_no_thread_id() {
    let event = ThreadEvent::Error(EventError {
        message: "boom".to_string(),
        code: None,
        extra: BTreeMap::new(),
    });

    assert!(event.thread_id().is_none());
}

#[test]
fn accessor_does_not_normalize_thread_id() {
    let raw = "  thread-123  ";
    let event = ThreadEvent::ThreadStarted(ThreadStarted {
        thread_id: raw.to_string(),
        extra: BTreeMap::new(),
    });

    assert_thread_id_borrowed(&event, raw);
}