feat(agentcompany-rust): scaffold native shell and runtime bridge

This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 01:02:20 -04:00
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:?}"),
}
}