feat(agentcompany-rust): scaffold native shell and runtime bridge
This commit is contained in:
1 parent
34814b4dfd
commit
2cddf89717
40 files changed
+11300
-3
No files matched your search
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "runtime"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
pi_bridge.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod service;
|
||||
|
||||
pub use service::{RuntimeConfig, RuntimeService};
|
||||
@@ -0,0 +1,51 @@
|
||||
use anyhow::Result;
|
||||
use pi_bridge::{
|
||||
ClientRequest, FollowUpRequest, PiBridgeClient, RunRequest, RuntimeEvent, RuntimeHandshake,
|
||||
SteerRequest, ToolApprovalDecision,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct RuntimeConfig {
|
||||
pub command: PathBuf,
|
||||
pub args: Vec<String>,
|
||||
pub working_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub struct RuntimeService {
|
||||
client: PiBridgeClient,
|
||||
}
|
||||
|
||||
impl RuntimeService {
|
||||
pub fn connect(config: RuntimeConfig) -> Result<Self> {
|
||||
Ok(Self {
|
||||
client: PiBridgeClient::spawn(config.command, &config.args, config.working_dir)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handshake(&mut self, message: RuntimeHandshake) -> Result<RuntimeEvent> {
|
||||
self.client.send(&ClientRequest::Handshake(message))?;
|
||||
self.client.read_event()
|
||||
}
|
||||
|
||||
pub fn start_run(&mut self, request: RunRequest) -> Result<()> {
|
||||
self.client
|
||||
.send(&ClientRequest::StartRun(Box::new(request)))
|
||||
}
|
||||
|
||||
pub fn decide_tool_call(&mut self, decision: ToolApprovalDecision) -> Result<()> {
|
||||
self.client.send(&ClientRequest::ToolApproval(decision))
|
||||
}
|
||||
|
||||
pub fn steer(&mut self, request: SteerRequest) -> Result<()> {
|
||||
self.client.send(&ClientRequest::Steer(request))
|
||||
}
|
||||
|
||||
pub fn follow_up(&mut self, request: FollowUpRequest) -> Result<()> {
|
||||
self.client.send(&ClientRequest::FollowUp(request))
|
||||
}
|
||||
|
||||
pub fn next_event(&mut self) -> Result<RuntimeEvent> {
|
||||
self.client.read_event()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
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:?}"),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user