mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 19:04:40 +00:00
pi working
This commit is contained in:
parent
9a26604001
commit
e2e7f11b9a
10 changed files with 451 additions and 38 deletions
|
|
@ -1786,6 +1786,8 @@ impl SessionManager {
|
|||
session.native_session_id = Some(thread_id);
|
||||
}
|
||||
if agent_id == AgentId::Pi {
|
||||
// Pi uses one dedicated RPC process per daemon session.
|
||||
// This is the canonical runtime path for Pi sessions.
|
||||
let pi = self
|
||||
.create_pi_session(&session_id, session.model.as_deref())
|
||||
.await?;
|
||||
|
|
@ -1970,6 +1972,8 @@ impl SessionManager {
|
|||
return Ok(());
|
||||
}
|
||||
if session_snapshot.agent == AgentId::Pi {
|
||||
// Pi bypasses generic AgentManager::spawn_streaming and stays on
|
||||
// router-managed per-session RPC runtime for lifecycle isolation.
|
||||
self.send_pi_prompt(&session_snapshot, &message).await?;
|
||||
if !agent_supports_item_started(session_snapshot.agent) {
|
||||
let _ = self
|
||||
|
|
@ -3859,11 +3863,33 @@ impl SessionManager {
|
|||
"message": prompt
|
||||
});
|
||||
|
||||
runtime
|
||||
let response_rx = runtime
|
||||
.send_request(id, &request)
|
||||
.ok_or_else(|| SandboxError::StreamError {
|
||||
message: "failed to send pi prompt request".to_string(),
|
||||
})?;
|
||||
let response = tokio::time::timeout(Duration::from_secs(30), response_rx)
|
||||
.await
|
||||
.map_err(|_| SandboxError::StreamError {
|
||||
message: "pi prompt request timed out".to_string(),
|
||||
})?
|
||||
.map_err(|_| SandboxError::StreamError {
|
||||
message: "pi prompt request cancelled".to_string(),
|
||||
})?;
|
||||
if response
|
||||
.get("success")
|
||||
.and_then(Value::as_bool)
|
||||
.is_some_and(|success| !success)
|
||||
{
|
||||
let detail = response
|
||||
.get("error")
|
||||
.cloned()
|
||||
.or_else(|| response.get("data").and_then(|data| data.get("error")).cloned())
|
||||
.unwrap_or_else(|| response.clone());
|
||||
return Err(SandboxError::InvalidRequest {
|
||||
message: format!("pi prompt failed: {detail}"),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -5718,6 +5744,9 @@ fn map_install_error(agent: AgentId, err: ManagerError) -> SandboxError {
|
|||
ManagerError::ResumeUnsupported { agent } => SandboxError::InvalidRequest {
|
||||
message: format!("resume unsupported for {agent}"),
|
||||
},
|
||||
ManagerError::UnsupportedRuntimePath { .. } => SandboxError::InvalidRequest {
|
||||
message: err.to_string(),
|
||||
},
|
||||
ManagerError::UnsupportedPlatform { .. }
|
||||
| ManagerError::DownloadFailed { .. }
|
||||
| ManagerError::Http(_)
|
||||
|
|
@ -5738,6 +5767,9 @@ fn map_spawn_error(agent: AgentId, err: ManagerError) -> SandboxError {
|
|||
ManagerError::ResumeUnsupported { agent } => SandboxError::InvalidRequest {
|
||||
message: format!("resume unsupported for {agent}"),
|
||||
},
|
||||
ManagerError::UnsupportedRuntimePath { .. } => SandboxError::InvalidRequest {
|
||||
message: err.to_string(),
|
||||
},
|
||||
_ => SandboxError::AgentProcessExited {
|
||||
agent: agent.as_str().to_string(),
|
||||
exit_code: None,
|
||||
|
|
@ -6670,6 +6702,33 @@ mod agent_capabilities_tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod runtime_contract_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn map_spawn_error_maps_unsupported_runtime_path_to_invalid_request() {
|
||||
let error = map_spawn_error(
|
||||
AgentId::Pi,
|
||||
ManagerError::UnsupportedRuntimePath {
|
||||
agent: AgentId::Pi,
|
||||
operation: "spawn_streaming",
|
||||
recommended_path: "router-managed per-session RPC runtime",
|
||||
},
|
||||
);
|
||||
match error {
|
||||
SandboxError::InvalidRequest { message } => {
|
||||
assert!(message.contains("spawn_streaming"), "{message}");
|
||||
assert!(
|
||||
message.contains("router-managed per-session RPC runtime"),
|
||||
"{message}"
|
||||
);
|
||||
}
|
||||
other => panic!("expected InvalidRequest, got {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod pi_runtime_tests {
|
||||
use super::*;
|
||||
|
|
@ -6933,10 +6992,103 @@ mod pi_runtime_tests {
|
|||
prompt_request.get("message").and_then(Value::as_str),
|
||||
Some("Hello")
|
||||
);
|
||||
let prompt_id = prompt_request
|
||||
.get("id")
|
||||
.and_then(Value::as_i64)
|
||||
.expect("prompt id");
|
||||
runtime.complete_request(
|
||||
prompt_id,
|
||||
json!({
|
||||
"type": "response",
|
||||
"id": prompt_id,
|
||||
"success": true
|
||||
}),
|
||||
);
|
||||
|
||||
task.await.expect("join").expect("send_pi_prompt ok");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_pi_prompt_maps_explicit_rpc_error_to_invalid_request() {
|
||||
let (session_manager, runtime, mut stdin_rx, _temp_dir) =
|
||||
setup_pi_session_with_stdin("pi-prompt-error").await;
|
||||
let snapshot = SessionSnapshot {
|
||||
session_id: "pi-prompt-error".to_string(),
|
||||
agent: AgentId::Pi,
|
||||
agent_mode: "build".to_string(),
|
||||
permission_mode: "default".to_string(),
|
||||
model: None,
|
||||
variant: None,
|
||||
native_session_id: Some("native-pi-prompt-error".to_string()),
|
||||
};
|
||||
let manager_for_task = session_manager.clone();
|
||||
let task =
|
||||
tokio::spawn(async move { manager_for_task.send_pi_prompt(&snapshot, "Hello").await });
|
||||
|
||||
let prompt_line = stdin_rx.recv().await.expect("prompt request");
|
||||
let prompt_request: Value = serde_json::from_str(&prompt_line).expect("json request");
|
||||
assert_eq!(prompt_request.get("type").and_then(Value::as_str), Some("prompt"));
|
||||
let prompt_id = prompt_request
|
||||
.get("id")
|
||||
.and_then(Value::as_i64)
|
||||
.expect("prompt id");
|
||||
runtime.complete_request(
|
||||
prompt_id,
|
||||
json!({
|
||||
"type": "response",
|
||||
"id": prompt_id,
|
||||
"success": false,
|
||||
"error": "turn already in progress"
|
||||
}),
|
||||
);
|
||||
|
||||
let err = task
|
||||
.await
|
||||
.expect("join")
|
||||
.expect_err("send_pi_prompt should fail");
|
||||
match err {
|
||||
SandboxError::InvalidRequest { message } => {
|
||||
assert!(message.contains("turn already in progress"), "{message}");
|
||||
}
|
||||
other => panic!("expected InvalidRequest, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_pi_prompt_reports_cancelled_response() {
|
||||
let (session_manager, runtime, mut stdin_rx, _temp_dir) =
|
||||
setup_pi_session_with_stdin("pi-prompt-cancelled").await;
|
||||
let snapshot = SessionSnapshot {
|
||||
session_id: "pi-prompt-cancelled".to_string(),
|
||||
agent: AgentId::Pi,
|
||||
agent_mode: "build".to_string(),
|
||||
permission_mode: "default".to_string(),
|
||||
model: None,
|
||||
variant: None,
|
||||
native_session_id: Some("native-pi-prompt-cancelled".to_string()),
|
||||
};
|
||||
let manager_for_task = session_manager.clone();
|
||||
let task = tokio::spawn(async move {
|
||||
manager_for_task
|
||||
.send_pi_prompt(&snapshot, "This should cancel")
|
||||
.await
|
||||
});
|
||||
|
||||
let _ = stdin_rx.recv().await.expect("prompt request");
|
||||
runtime.clear_pending();
|
||||
|
||||
let err = task
|
||||
.await
|
||||
.expect("join")
|
||||
.expect_err("send_pi_prompt should fail");
|
||||
match err {
|
||||
SandboxError::StreamError { message } => {
|
||||
assert!(message.contains("pi prompt request cancelled"), "{message}");
|
||||
}
|
||||
other => panic!("expected StreamError, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pi_runtime_output_non_json_emits_agent_unparsed() {
|
||||
let (session_manager, runtime, _temp_dir) = setup_pi_session("pi-unparsed").await;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,25 @@ fn pi_on_path() -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pi_spawn_streaming_is_rejected_with_runtime_contract_error(
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let manager = AgentManager::new(temp_dir.path().join("bin"))?;
|
||||
let err = manager
|
||||
.spawn_streaming(AgentId::Pi, SpawnOptions::new(prompt_ok("IGNORED")))
|
||||
.expect_err("expected Pi spawn_streaming to be rejected");
|
||||
assert!(matches!(
|
||||
err,
|
||||
AgentError::UnsupportedRuntimePath {
|
||||
agent: AgentId::Pi,
|
||||
operation: "spawn_streaming",
|
||||
..
|
||||
}
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_agents_install_version_spawn() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 129
|
||||
expression: normalize_agent_list(&agents)
|
||||
---
|
||||
agents:
|
||||
|
|
@ -8,3 +9,4 @@ agents:
|
|||
- id: codex
|
||||
- id: mock
|
||||
- id: opencode
|
||||
- id: pi
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 129
|
||||
expression: normalize_agent_list(&agents)
|
||||
---
|
||||
agents:
|
||||
- id: amp
|
||||
- id: claude
|
||||
- id: codex
|
||||
- id: mock
|
||||
- id: opencode
|
||||
- id: pi
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 59
|
||||
expression: "json!({\n \"status\": status.as_u16(), \"payload\": normalize_agent_list(&payload),\n})"
|
||||
---
|
||||
payload:
|
||||
|
|
@ -9,4 +10,5 @@ payload:
|
|||
- id: codex
|
||||
- id: mock
|
||||
- id: opencode
|
||||
- id: pi
|
||||
status: 200
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 59
|
||||
expression: "json!({\n \"status\": status.as_u16(), \"payload\": normalize_agent_list(&payload),\n})"
|
||||
---
|
||||
payload:
|
||||
agents:
|
||||
- id: amp
|
||||
- id: claude
|
||||
- id: codex
|
||||
- id: mock
|
||||
- id: opencode
|
||||
- id: pi
|
||||
status: 200
|
||||
Loading…
Add table
Add a link
Reference in a new issue