mobius 0.15.0

A small, modular Rust framework for building coding agents
Documentation
use super::*;
use crate::backend::model::{message_input, tool_output};
use crate::protocol::{ATTACHMENT_CONTEXT_MARKER, MessageAuthor, MessageEvent};

fn message(text: &str, delivery: MessageDelivery) -> Value {
    message_input(&MessageEvent {
        author: MessageAuthor::User,
        delivery,
        text: text.into(),
        attachments: Vec::new(),
        reply: None,
        message_target: None,
    })
    .expect("typed user input")
}

fn call(call_id: &str, name: &str) -> Value {
    serde_json::json!({
        "type": "function_call", "call_id": call_id, "name": name, "arguments": "{}"
    })
}

#[test]
fn fresh_input_preserves_the_active_request_steers_attachments_and_mixed_batch() {
    let active = message("finish the task", MessageDelivery::Turn);
    let attachment = internal_user_message(ATTACHMENT_CONTEXT_MARKER, "active upload");
    let steer = message("preserve this correction", MessageDelivery::Steer);
    let later_steer = message("and verify twice", MessageDelivery::Steer);
    let later_attachment = internal_user_message(ATTACHMENT_CONTEXT_MARKER, "newer upload");
    let batch = vec![
        call("work", "read_file"),
        call("reset", "new_context"),
        tool_output("reset", "reset requested", false),
        internal_user_message(REQUEST, ""),
        tool_output("work", "result that arrived after the checkpoint", false),
    ];
    let mut input = vec![
        message("previous task", MessageDelivery::Turn),
        internal_user_message(ATTACHMENT_CONTEXT_MARKER, "old upload"),
        active.clone(),
        attachment.clone(),
        call("old-work", "read_file"),
        tool_output("old-work", "old output now recoverable from history", false),
        steer.clone(),
        internal_user_message(NOTES, "stale projection"),
        internal_user_message(WARNING, "handoff soon"),
    ];
    input.extend(batch.clone());
    input.extend([later_steer.clone(), later_attachment.clone()]);

    let fresh = fresh_input(&input, "reset").expect("fresh context");
    let mut expected = vec![active, attachment, steer];
    expected.extend(batch.into_iter().filter(|item| !is_control(item)));
    expected.extend([later_steer, later_attachment]);

    assert_eq!(fresh, expected);
    assert_eq!(tool_complete_boundaries(&fresh).last(), Some(&fresh.len()));
}

#[test]
fn fresh_input_uses_the_latest_batch_when_tool_call_ids_repeat() {
    let active = message("current task", MessageDelivery::Turn);
    let current_call = call("reset", "new_context");
    let current_result = tool_output("reset", "current reset", false);
    let input = vec![
        message("previous task", MessageDelivery::Turn),
        call("reset", "new_context"),
        tool_output("reset", "previous reset", false),
        serde_json::json!({"role": "assistant", "content": "old work"}),
        active.clone(),
        serde_json::json!({"role": "assistant", "content": "intervening work"}),
        current_call.clone(),
        current_result.clone(),
        internal_user_message(REQUEST, ""),
    ];

    assert_eq!(
        fresh_input(&input, "reset").expect("latest request"),
        vec![active, current_call, current_result]
    );
}

#[test]
fn fresh_input_rejects_an_incomplete_mixed_tool_batch() {
    let input = vec![
        message("current task", MessageDelivery::Turn),
        call("pending", "read_file"),
        call("reset", "new_context"),
        tool_output("reset", "reset requested", false),
        internal_user_message(REQUEST, ""),
    ];

    assert!(fresh_input(&input, "reset").is_err());
}