171 lines
5.9 KiB
Rust
171 lines
5.9 KiB
Rust
use pi_bridge::{
|
|
FollowUpRequest, RunBudget, RunRequest, RuntimeEvent, RuntimeHandshake, SteerRequest,
|
|
ToolApprovalDecision,
|
|
};
|
|
use runtime::{RuntimeConfig, RuntimeService};
|
|
use std::path::PathBuf;
|
|
|
|
fn fake_sidecar_script() -> &'static str {
|
|
r#"
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
*'"kind":"handshake"'*)
|
|
printf '%s\n' '{"kind":"handshake_ack","protocol_version":1,"runtime_version":"pi-mono-test"}'
|
|
;;
|
|
*'"kind":"start_run"'*)
|
|
printf '%s\n' '{"kind":"message","run_id":"run-1","role":"assistant","body":"Run queued"}'
|
|
printf '%s\n' '{"kind":"tool_call","run_id":"run-1","tool_call_id":"tool-1","tool_name":"write_file","input_json":{"path":"README.md"}}'
|
|
;;
|
|
*'"kind":"tool_approval"'*)
|
|
printf '%s\n' '{"kind":"tool_result","run_id":"run-1","tool_call_id":"tool-1","output_json":{"status":"approved"}}'
|
|
printf '%s\n' '{"kind":"run_finished","run_id":"run-1","status":"completed","summary":"Run complete","session_id":"session-1","cost_usd":1.25}'
|
|
;;
|
|
*'"kind":"steer"'*)
|
|
printf '%s\n' '{"kind":"message","run_id":"run-1","role":"system","body":"Steer accepted"}'
|
|
;;
|
|
*'"kind":"follow_up"'*)
|
|
printf '%s\n' '{"kind":"message","run_id":"run-1","role":"system","body":"Follow-up accepted"}'
|
|
;;
|
|
esac
|
|
done
|
|
"#
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_service_round_trips_sidecar_contract() {
|
|
let mut service = RuntimeService::connect(RuntimeConfig {
|
|
command: PathBuf::from("/bin/sh"),
|
|
args: vec!["-c".to_string(), fake_sidecar_script().to_string()],
|
|
working_dir: None,
|
|
})
|
|
.expect("connect runtime");
|
|
|
|
let handshake = service
|
|
.handshake(RuntimeHandshake {
|
|
protocol_version: 1,
|
|
app_name: "AgentCompany-Rust".to_string(),
|
|
app_version: "0.1.0".to_string(),
|
|
})
|
|
.expect("handshake");
|
|
match handshake {
|
|
RuntimeEvent::HandshakeAck {
|
|
protocol_version,
|
|
runtime_version,
|
|
} => {
|
|
assert_eq!(protocol_version, 1);
|
|
assert_eq!(runtime_version, "pi-mono-test");
|
|
}
|
|
other => panic!("unexpected handshake event: {other:?}"),
|
|
}
|
|
|
|
service
|
|
.start_run(RunRequest {
|
|
run_id: "run-1".to_string(),
|
|
session_id: Some("session-1".to_string()),
|
|
company_id: "company-1".to_string(),
|
|
task_id: Some("task-1".to_string()),
|
|
agent_id: "agent-1".to_string(),
|
|
workspace_path: Some("/tmp/agentcompany".to_string()),
|
|
connector_id: "anthropic".to_string(),
|
|
model: Some("claude-sonnet".to_string()),
|
|
prompt: "Draft the weekly status update".to_string(),
|
|
context_json: serde_json::json!({ "thread_id": "thread-1" }),
|
|
budget: RunBudget {
|
|
thinking_level: Some("high".to_string()),
|
|
token_budget: Some(4096),
|
|
},
|
|
})
|
|
.expect("start run");
|
|
|
|
let first_event = service.next_event().expect("first event");
|
|
match first_event {
|
|
RuntimeEvent::Message { run_id, role, body } => {
|
|
assert_eq!(run_id, "run-1");
|
|
assert_eq!(role, "assistant");
|
|
assert_eq!(body, "Run queued");
|
|
}
|
|
other => panic!("unexpected first event: {other:?}"),
|
|
}
|
|
|
|
let second_event = service.next_event().expect("second event");
|
|
match second_event {
|
|
RuntimeEvent::ToolCall {
|
|
run_id,
|
|
tool_call_id,
|
|
tool_name,
|
|
input_json,
|
|
} => {
|
|
assert_eq!(run_id, "run-1");
|
|
assert_eq!(tool_call_id, "tool-1");
|
|
assert_eq!(tool_name, "write_file");
|
|
assert_eq!(input_json["path"], "README.md");
|
|
}
|
|
other => panic!("unexpected second event: {other:?}"),
|
|
}
|
|
|
|
service
|
|
.decide_tool_call(ToolApprovalDecision {
|
|
run_id: "run-1".to_string(),
|
|
tool_call_id: "tool-1".to_string(),
|
|
approved: true,
|
|
note: "Approved from test.".to_string(),
|
|
})
|
|
.expect("tool approval");
|
|
|
|
let third_event = service.next_event().expect("third event");
|
|
match third_event {
|
|
RuntimeEvent::ToolResult {
|
|
run_id,
|
|
tool_call_id,
|
|
output_json,
|
|
} => {
|
|
assert_eq!(run_id, "run-1");
|
|
assert_eq!(tool_call_id, "tool-1");
|
|
assert_eq!(output_json["status"], "approved");
|
|
}
|
|
other => panic!("unexpected third event: {other:?}"),
|
|
}
|
|
|
|
let fourth_event = service.next_event().expect("fourth event");
|
|
match fourth_event {
|
|
RuntimeEvent::RunFinished {
|
|
run_id,
|
|
status,
|
|
summary,
|
|
session_id,
|
|
cost_usd,
|
|
} => {
|
|
assert_eq!(run_id, "run-1");
|
|
assert_eq!(status, "completed");
|
|
assert_eq!(summary, "Run complete");
|
|
assert_eq!(session_id.as_deref(), Some("session-1"));
|
|
assert_eq!(cost_usd, Some(1.25));
|
|
}
|
|
other => panic!("unexpected fourth event: {other:?}"),
|
|
}
|
|
|
|
service
|
|
.steer(SteerRequest {
|
|
run_id: "run-1".to_string(),
|
|
body: "Tighten the summary.".to_string(),
|
|
})
|
|
.expect("steer");
|
|
let steer_event = service.next_event().expect("steer event");
|
|
match steer_event {
|
|
RuntimeEvent::Message { body, .. } => assert_eq!(body, "Steer accepted"),
|
|
other => panic!("unexpected steer event: {other:?}"),
|
|
}
|
|
|
|
service
|
|
.follow_up(FollowUpRequest {
|
|
run_id: "run-1".to_string(),
|
|
body: "Add customer risk notes.".to_string(),
|
|
})
|
|
.expect("follow up");
|
|
let follow_up_event = service.next_event().expect("follow up event");
|
|
match follow_up_event {
|
|
RuntimeEvent::Message { body, .. } => assert_eq!(body, "Follow-up accepted"),
|
|
other => panic!("unexpected follow-up event: {other:?}"),
|
|
}
|
|
}
|