#![allow(dead_code)]
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use rpi_agent::StreamFn;
use rpi_ai::event_stream::create_assistant_message_event_stream;
use rpi_ai::types::{
Api, AssistantMessage, AssistantMessageEvent, Content, DoneReason, ErrorReason, Message,
StopReason, Tool, ToolCall, ToolCallType,
};
pub fn mock_stream_fn(messages: Vec<AssistantMessage>) -> StreamFn {
let queue: Arc<Mutex<VecDeque<AssistantMessage>>> = Arc::new(Mutex::new(messages.into()));
rpi_agent::stream_fn(move |_model, _ctx, _opts| {
let (mut prod, stream) = create_assistant_message_event_stream();
let q = Arc::clone(&queue);
tokio::spawn(async move {
let next = q.lock().expect("mock queue lock").pop_front();
match next {
Some(message) => {
let partial = Arc::new(message.clone());
prod.push(AssistantMessageEvent::Start {
partial: partial.clone(),
});
match message.stop_reason {
StopReason::Error | StopReason::Aborted => {
let reason = matches!(message.stop_reason, StopReason::Aborted)
.then_some(ErrorReason::Aborted)
.unwrap_or(ErrorReason::Error);
prod.push(AssistantMessageEvent::Error {
reason,
error: message,
});
}
_ => {
let reason = done_reason_from_stop(message.stop_reason);
prod.push(AssistantMessageEvent::Done { reason, message });
}
}
}
None => {
let err = AssistantMessage::terminal(
Api::Other("mock".into()),
"mock",
"mock",
StopReason::Error,
"No more mock responses queued",
0,
);
prod.push(AssistantMessageEvent::Error {
reason: ErrorReason::Error,
error: err,
});
}
}
});
stream
})
}
fn done_reason_from_stop(stop: StopReason) -> DoneReason {
match stop {
StopReason::Stop => DoneReason::Stop,
StopReason::Length => DoneReason::Length,
StopReason::ToolUse => DoneReason::ToolUse,
StopReason::Deferred => DoneReason::Deferred,
_ => DoneReason::Stop,
}
}
pub fn mock_model() -> rpi_ai::Model {
rpi_ai::Model::new(
"mock",
"mock",
Api::Other("openai-responses".into()),
"mock",
"https://example.invalid",
)
}
pub fn user_message(text: impl Into<String>) -> rpi_agent::AgentMessage {
rpi_agent::AgentMessage::User(rpi_ai::types::UserMessage::new(
rpi_ai::types::UserContent::Text(text.into()),
0,
))
}
pub fn assistant_text(text: impl Into<String>, stop: StopReason) -> AssistantMessage {
AssistantMessage {
role: rpi_ai::types::AssistantRole,
content: vec![Content::text(text)],
api: Api::Other("openai-responses".into()),
provider: "mock".to_string(),
model: "mock".to_string(),
response_model: None,
response_id: None,
usage: rpi_ai::types::Usage::zero(),
stop_reason: stop,
deferred: None,
error_message: None,
raw_stop_reason: None,
end_turn: None,
timestamp: 0,
}
}
pub fn assistant_tool_calls(
calls: Vec<(&str, &str, serde_json::Value)>,
stop: StopReason,
) -> AssistantMessage {
let content: Vec<Content> = calls
.into_iter()
.map(|(id, name, args)| {
Content::ToolCall(ToolCall {
kind: ToolCallType,
id: id.to_string(),
name: name.to_string(),
arguments: args,
thought_signature: None,
namespace: None,
})
})
.collect();
AssistantMessage {
role: rpi_ai::types::AssistantRole,
content,
api: Api::Other("openai-responses".into()),
provider: "mock".to_string(),
model: "mock".to_string(),
response_model: None,
response_id: None,
usage: rpi_ai::types::Usage::zero(),
stop_reason: stop,
deferred: None,
error_message: None,
raw_stop_reason: None,
end_turn: None,
timestamp: 0,
}
}
pub fn identity_converter() -> Arc<
dyn Fn(Vec<rpi_agent::AgentMessage>) -> futures::future::BoxFuture<'static, Vec<Message>>
+ Send
+ Sync,
> {
Arc::new(|messages: Vec<rpi_agent::AgentMessage>| {
let out: Vec<Message> = messages
.into_iter()
.filter_map(|m| match m {
rpi_agent::AgentMessage::User(u) => Some(Message::User(u)),
rpi_agent::AgentMessage::Assistant(a) => Some(Message::Assistant(a)),
rpi_agent::AgentMessage::ToolResult(t) => Some(Message::ToolResult(t)),
rpi_agent::AgentMessage::Custom(_) => None,
})
.collect();
Box::pin(async move { out })
})
}
pub fn base_config() -> rpi_agent::AgentLoopConfig {
loop_config_with_converter(identity_converter())
}
pub fn loop_config_with_converter(
convert_to_llm: rpi_agent::ConvertToLlm,
) -> rpi_agent::AgentLoopConfig {
rpi_agent::AgentLoopConfig {
model: mock_model(),
convert_to_llm,
transform_context: None,
get_api_key: None,
should_stop_after_turn: None,
prepare_next_turn: None,
after_tool_results: None,
get_steering_messages: None,
get_follow_up_messages: None,
before_tool_call: None,
after_tool_call: None,
tool_execution: rpi_agent::ToolExecutionMode::Parallel,
thinking_level: rpi_ai::types::ThinkingLevel::Off,
api_key: None,
timeout: None,
max_retries: None,
max_retry_delay: None,
cache_retention: rpi_ai::provider::CacheRetention::default(),
session_id: None,
signal: tokio_util::sync::CancellationToken::new(),
}
}
pub fn tool_result_text(trm: &rpi_ai::types::ToolResultMessage) -> String {
trm.content
.iter()
.filter_map(|c| match c {
Content::Text(t) => Some(t.text.clone()),
_ => None,
})
.collect::<Vec<_>>()
.join("")
}
pub async fn run_and_collect(
prompts: Vec<rpi_agent::AgentMessage>,
context: rpi_agent::AgentContext,
config: rpi_agent::AgentLoopConfig,
stream_fn: StreamFn,
) -> (Vec<rpi_agent::AgentEvent>, Vec<rpi_agent::AgentMessage>) {
let (collector, events) = rpi_agent::CollectorEmitter::new();
let emit: Arc<dyn rpi_agent::AgentEmitter> = Arc::new(collector);
let new_messages = rpi_agent::run_agent_loop(prompts, context, config, emit, stream_fn)
.await
.expect("run_agent_loop failed");
let events = events.lock().expect("events lock").clone();
(events, new_messages)
}
pub fn type_tags(events: &[rpi_agent::AgentEvent]) -> Vec<&'static str> {
events.iter().map(|e| e.type_tag()).collect()
}
pub fn tool_execution_end_ids(events: &[rpi_agent::AgentEvent]) -> Vec<String> {
events
.iter()
.filter_map(|e| match e {
rpi_agent::AgentEvent::ToolExecutionEnd { tool_call_id, .. } => {
Some(tool_call_id.clone())
}
_ => None,
})
.collect()
}
pub fn tool_result_message_ids(events: &[rpi_agent::AgentEvent]) -> Vec<String> {
events
.iter()
.filter_map(|e| match e {
rpi_agent::AgentEvent::MessageEnd { message } => match message {
rpi_agent::AgentMessage::ToolResult(t) => Some(t.tool_call_id.clone()),
_ => None,
},
_ => None,
})
.collect()
}
#[allow(unused_imports)]
use rpi_ai::types::Tool as _ToolUnused;
pub fn value_string_schema() -> Tool {
let schema = serde_json::json!({
"type": "object",
"properties": {
"value": { "type": "string" }
},
"required": ["value"],
"additionalProperties": false,
});
Tool {
name: "unused".to_string(),
description: String::new(),
parameters: rpi_ai::types::Schema::new(schema),
constrained_sampling: None,
}
}