starweaver-runtime 0.10.0

Agent-loop graph and runtime executor primitives for Starweaver
Documentation
#![allow(missing_docs, clippy::unwrap_used)]

use std::sync::Arc;

use starweaver_model::{FunctionModel, ModelMessage, ModelRequestPart, ModelResponse, TestModel};
use starweaver_runtime::{
    Agent, AgentError, AgentRunState, DynamicInstructionError, FunctionDynamicInstruction,
    StaticCapabilityBundle,
};

#[tokio::test]
async fn dynamic_instruction_is_injected_on_first_request() {
    let model = FunctionModel::new(|messages, _settings, _info| {
        let has_dynamic = messages.iter().any(|message| matches!(message, ModelMessage::Request(request) if request.parts.iter().any(|part| matches!(part, ModelRequestPart::Instruction { text, .. } if text == "Dynamic run step 0"))));
        assert!(has_dynamic);
        Ok(ModelResponse::text("ok"))
    });
    let instruction = FunctionDynamicInstruction::new(|state: AgentRunState| async move {
        Ok(format!("Dynamic run step {}", state.run_step))
    });

    let result = Agent::new(Arc::new(model))
        .with_dynamic_instruction(Arc::new(instruction))
        .run("hello")
        .await
        .unwrap();

    assert_eq!(result.output, "ok");
}

#[tokio::test]
async fn dynamic_instruction_error_fails_run() {
    let instruction = FunctionDynamicInstruction::new(|_state: AgentRunState| async move {
        Err(DynamicInstructionError::failed("missing runtime data"))
    });

    let error = Agent::new(Arc::new(TestModel::with_text("ok")))
        .with_dynamic_instruction(Arc::new(instruction))
        .run("hello")
        .await
        .unwrap_err();

    assert!(
        matches!(error, AgentError::DynamicInstruction(message) if message == "missing runtime data")
    );
}

#[tokio::test]
async fn capability_bundle_can_contribute_dynamic_instruction() {
    let model = FunctionModel::new(|messages, _settings, _info| {
        let has_dynamic = messages.iter().any(|message| matches!(message, ModelMessage::Request(request) if request.parts.iter().any(|part| matches!(part, ModelRequestPart::Instruction { text, .. } if text == "Bundle dynamic instruction"))));
        assert!(has_dynamic);
        Ok(ModelResponse::text("ok"))
    });
    let instruction = FunctionDynamicInstruction::new(|_state: AgentRunState| async move {
        Ok("Bundle dynamic instruction".to_string())
    });
    let bundle =
        StaticCapabilityBundle::new("dynamic").with_dynamic_instruction(Arc::new(instruction));

    let result = Agent::new(Arc::new(model))
        .with_capability_bundle(&bundle)
        .run("hello")
        .await
        .unwrap();

    assert_eq!(result.output, "ok");
}