unified-agent-api-codex 0.3.5

Async wrapper around the Codex CLI for programmatic prompting
Documentation
use codex::{find_rollout_file_by_id, rollout_jsonl_reader, RolloutEvent, RolloutJsonlParser};

#[test]
fn parses_rollout_lines_and_preserves_unknown_types() {
    let jsonl = r#"
{"timestamp":"2026-02-08T03:03:57.000Z","type":"session_meta","payload":{"id":"sess-1","cli_version":"0.99.0","cwd":"/tmp/demo","base_instructions":{"text":"hello"}}}
{"timestamp":"2026-02-08T03:03:57.001Z","type":"event_msg","payload":{"type":"token_count","info":{"total_token_usage":{"total_tokens":123}}}}
{"timestamp":"2026-02-08T03:03:57.002Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"hi"}]}}
{"timestamp":"2026-02-08T03:03:57.003Z","type":"response_item","payload":{"type":"function_call","name":"exec_command","arguments":"{\"cmd\":\"echo hi\"}","call_id":"call_1"}}
{"timestamp":"2026-02-08T03:03:57.004Z","type":"response_item","payload":{"type":"function_call_output","call_id":"call_1","output":"ok"}}
{"timestamp":"2026-02-08T03:03:57.005Z","type":"mystery","payload":{"x":1}}
"#;

    let cursor = std::io::Cursor::new(jsonl);
    let records: Vec<_> = rollout_jsonl_reader(cursor).collect();
    assert_eq!(records.len(), 6);

    let mut ok = 0usize;
    for record in records {
        let event = record.outcome.unwrap();
        ok += 1;
        match event {
            RolloutEvent::SessionMeta(meta) => {
                assert_eq!(meta.payload.id.as_deref(), Some("sess-1"));
            }
            RolloutEvent::EventMsg(msg) => {
                assert_eq!(msg.payload.kind.as_deref(), Some("token_count"));
            }
            RolloutEvent::ResponseItem(item) => {
                assert!(item.payload.kind.is_some());
            }
            RolloutEvent::Unknown(unknown) => {
                assert_eq!(unknown.record_type, "mystery");
            }
        }
    }
    assert_eq!(ok, 6);
}

#[test]
fn parse_line_tolerates_trailing_crlf_carriage_return() {
    let mut parser = RolloutJsonlParser::new();
    let line =
        "{\"type\":\"event_msg\",\"payload\":{\"type\":\"agent_message\",\"message\":\"hi\"}}\r";
    let parsed = parser.parse_line(line).unwrap().unwrap();
    match parsed {
        RolloutEvent::EventMsg(msg) => {
            assert_eq!(msg.payload.kind.as_deref(), Some("agent_message"));
        }
        other => panic!("unexpected {other:?}"),
    }
}

#[test]
fn finds_rollout_file_by_session_meta_id_even_when_filename_does_not_match() {
    let root = tempfile::tempdir().expect("tempdir");
    let sessions = root
        .path()
        .join("sessions")
        .join("2026")
        .join("02")
        .join("08");
    std::fs::create_dir_all(&sessions).expect("create sessions");

    let target_id = "019c363e-14ca-7af0-9639-3a31d57ea5d3";
    let path = sessions.join("rollout-not-containing-id.jsonl");
    std::fs::write(
        &path,
        format!(
            "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{target_id}\",\"cli_version\":\"0.98.0\"}}}}\n"
        ),
    )
    .expect("write");

    let found = find_rollout_file_by_id(root.path(), target_id).expect("find file");
    assert_eq!(found, path);
}