rho-coding-agent 1.9.0

A lightweight agent harness inspired by Pi
Documentation
use pretty_assertions::assert_eq;
use rho_sdk::{
    HostChoice, HostInputRequest, HostQuestion, Retryability, RunEvent, SelectionMode, ToolCallId,
};

use super::{
    active_run_disposition, begin_provider_switch, state_after_event, ActiveRunCommand,
    ActiveRunDisposition, InteractiveState, RunPhase, RunState, TransitionState,
};

fn questionnaire_event() -> RunEvent {
    let question = HostQuestion::new(
        "q1",
        "continue?",
        vec![HostChoice::new("yes", "Yes")],
        SelectionMode::One,
    )
    .unwrap();
    RunEvent::HostInputRequested {
        request: HostInputRequest::questionnaire("confirm", vec![question]).unwrap(),
    }
}

#[test]
fn scripted_events_cover_model_tool_questionnaire_and_steering_states() {
    let state = state_after_event(InteractiveState::Idle, &RunEvent::StepStarted { step: 1 });
    assert_eq!(
        state,
        InteractiveState::Run(RunState::Running(RunPhase::Model))
    );

    let state = state_after_event(
        state,
        &RunEvent::ToolStarted {
            call_id: ToolCallId::from_string("call-1").unwrap(),
            name: "questionnaire".into(),
            metadata: Default::default(),
        },
    );
    assert_eq!(
        state,
        InteractiveState::Run(RunState::Running(RunPhase::Tool))
    );

    let state = state_after_event(state, &questionnaire_event());
    assert_eq!(state, InteractiveState::Run(RunState::WaitingForHostInput));

    let steering = InteractiveState::Run(RunState::Running(RunPhase::Steering));
    assert_eq!(
        state_after_event(
            steering,
            &RunEvent::AssistantTextDelta {
                text: "still streaming".into(),
            },
        ),
        steering
    );
    assert_eq!(
        state_after_event(steering, &RunEvent::StepStarted { step: 2 }),
        InteractiveState::Run(RunState::Running(RunPhase::Model))
    );
}

#[test]
fn cancellation_wins_over_tool_questionnaire_and_compaction_events() {
    let cancelling = InteractiveState::Run(RunState::Cancelling(RunPhase::Tool));
    assert_eq!(
        state_after_event(cancelling, &questionnaire_event()),
        cancelling
    );
    assert_eq!(
        state_after_event(
            cancelling,
            &RunEvent::CompactionStarted {
                trigger: rho_sdk::CompactionTrigger::Automatic,
                message_count: 5,
            },
        ),
        cancelling
    );
    assert_eq!(
        state_after_event(cancelling, &RunEvent::StepStarted { step: 2 }),
        cancelling
    );
    assert_eq!(
        state_after_event(
            cancelling,
            &RunEvent::Cancelled {
                revision: rho_sdk::Revision::INITIAL,
            },
        ),
        InteractiveState::Completed
    );
}

#[test]
fn compaction_provider_switch_and_failure_are_explicit_states() {
    assert_eq!(
        state_after_event(
            InteractiveState::Run(RunState::Running(RunPhase::Model)),
            &RunEvent::CompactionStarted {
                trigger: rho_sdk::CompactionTrigger::Automatic,
                message_count: 8,
            },
        ),
        InteractiveState::Run(RunState::Compacting)
    );
    assert_eq!(
        state_after_event(
            InteractiveState::Run(RunState::Compacting),
            &RunEvent::StepStarted { step: 2 },
        ),
        InteractiveState::Run(RunState::Running(RunPhase::Model))
    );
    assert_eq!(
        begin_provider_switch(InteractiveState::Idle).unwrap(),
        InteractiveState::Transition(TransitionState::SwitchingProvider)
    );
    assert!(
        begin_provider_switch(InteractiveState::Run(RunState::Running(RunPhase::Tool))).is_err()
    );
    assert_eq!(
        state_after_event(
            InteractiveState::Run(RunState::Running(RunPhase::Model)),
            &RunEvent::Failed {
                message: "failed".into(),
                retryability: Retryability::Permanent,
            },
        ),
        InteractiveState::Failed
    );
}

#[test]
fn active_tool_commands_cancel_quit_reject_session_switch_and_defer_provider_replace() {
    assert_eq!(
        active_run_disposition(ActiveRunCommand::Quit),
        ActiveRunDisposition::CancelAndWait
    );
    assert_eq!(
        active_run_disposition(ActiveRunCommand::SwitchSession),
        ActiveRunDisposition::RejectUntilFinished
    );
    assert_eq!(
        active_run_disposition(ActiveRunCommand::ReplaceProvider),
        ActiveRunDisposition::DeferUntilFinished
    );
}