mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-20 05:04:49 +00:00
support pi
This commit is contained in:
parent
cc5a9e0d73
commit
843498e9db
41 changed files with 2654 additions and 102 deletions
30
server/packages/sandbox-agent/src/http_client.rs
Normal file
30
server/packages/sandbox-agent/src/http_client.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use std::env;
|
||||
|
||||
use reqwest::blocking::ClientBuilder as BlockingClientBuilder;
|
||||
use reqwest::ClientBuilder;
|
||||
|
||||
const NO_SYSTEM_PROXY_ENV: &str = "SANDBOX_AGENT_NO_SYSTEM_PROXY";
|
||||
|
||||
fn disable_system_proxy() -> bool {
|
||||
env::var(NO_SYSTEM_PROXY_ENV)
|
||||
.map(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "YES"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn client_builder() -> ClientBuilder {
|
||||
let builder = reqwest::Client::builder();
|
||||
if disable_system_proxy() {
|
||||
builder.no_proxy()
|
||||
} else {
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blocking_client_builder() -> BlockingClientBuilder {
|
||||
let builder = reqwest::blocking::Client::builder();
|
||||
if disable_system_proxy() {
|
||||
builder.no_proxy()
|
||||
} else {
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
mod agent_server_logs;
|
||||
pub mod credentials;
|
||||
pub mod http_client;
|
||||
pub mod router;
|
||||
pub mod telemetry;
|
||||
pub mod ui;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ mod build_version {
|
|||
}
|
||||
use reqwest::blocking::Client as HttpClient;
|
||||
use reqwest::Method;
|
||||
use sandbox_agent::http_client;
|
||||
use sandbox_agent::router::{build_router_with_state, shutdown_servers};
|
||||
use sandbox_agent::router::{
|
||||
AgentInstallRequest, AppState, AuthConfig, CreateSessionRequest, MessageRequest,
|
||||
|
|
@ -687,6 +688,7 @@ enum CredentialAgent {
|
|||
Codex,
|
||||
Opencode,
|
||||
Amp,
|
||||
Pi,
|
||||
}
|
||||
|
||||
fn credentials_to_output(credentials: ExtractedCredentials, reveal: bool) -> CredentialsOutput {
|
||||
|
|
@ -806,6 +808,31 @@ fn select_token_for_agent(
|
|||
)))
|
||||
}
|
||||
}
|
||||
CredentialAgent::Pi => {
|
||||
if let Some(provider) = provider {
|
||||
return select_token_for_provider(credentials, provider);
|
||||
}
|
||||
if let Some(openai) = credentials.openai.as_ref() {
|
||||
return Ok(openai.api_key.clone());
|
||||
}
|
||||
if let Some(anthropic) = credentials.anthropic.as_ref() {
|
||||
return Ok(anthropic.api_key.clone());
|
||||
}
|
||||
if credentials.other.len() == 1 {
|
||||
if let Some((_, cred)) = credentials.other.iter().next() {
|
||||
return Ok(cred.api_key.clone());
|
||||
}
|
||||
}
|
||||
let available = available_providers(credentials);
|
||||
if available.is_empty() {
|
||||
Err(CliError::Server("no credentials found for pi".to_string()))
|
||||
} else {
|
||||
Err(CliError::Server(format!(
|
||||
"multiple providers available for pi: {} (use --provider)",
|
||||
available.join(", ")
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -919,7 +946,7 @@ impl ClientContext {
|
|||
} else {
|
||||
cli.token.clone()
|
||||
};
|
||||
let client = HttpClient::builder().build()?;
|
||||
let client = http_client::blocking_client_builder().build()?;
|
||||
Ok(Self {
|
||||
endpoint,
|
||||
token,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -10,6 +10,8 @@ use serde::Serialize;
|
|||
use time::OffsetDateTime;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use crate::http_client;
|
||||
|
||||
const TELEMETRY_URL: &str = "https://tc.rivet.dev";
|
||||
const TELEMETRY_ENV_DEBUG: &str = "SANDBOX_AGENT_TELEMETRY_DEBUG";
|
||||
const TELEMETRY_ID_FILE: &str = "telemetry_id";
|
||||
|
|
@ -77,7 +79,7 @@ pub fn log_enabled_message() {
|
|||
|
||||
pub fn spawn_telemetry_task() {
|
||||
tokio::spawn(async move {
|
||||
let client = match Client::builder()
|
||||
let client = match http_client::client_builder()
|
||||
.timeout(Duration::from_millis(TELEMETRY_TIMEOUT_MS))
|
||||
.build()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ mod agent_permission_flow;
|
|||
mod agent_question_flow;
|
||||
mod agent_termination;
|
||||
mod agent_tool_flow;
|
||||
mod pi_rpc_integration;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
// Pi RPC integration tests (gated via SANDBOX_TEST_PI + PATH).
|
||||
include!("../common/http.rs");
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pi_rpc_session_and_stream() {
|
||||
let configs = match test_agents_from_env() {
|
||||
Ok(configs) => configs,
|
||||
Err(err) => {
|
||||
eprintln!("Skipping Pi RPC integration test: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let Some(config) = configs.iter().find(|config| config.agent == AgentId::Pi) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let app = TestApp::new();
|
||||
let _guard = apply_credentials(&config.credentials);
|
||||
install_agent(&app.app, config.agent).await;
|
||||
|
||||
let session_id = "pi-rpc-session".to_string();
|
||||
let (status, payload) = send_json(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
&format!("/v1/sessions/{session_id}"),
|
||||
Some(json!({
|
||||
"agent": "pi",
|
||||
"permissionMode": test_permission_mode(AgentId::Pi),
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "create pi session");
|
||||
let native_session_id = payload
|
||||
.get("native_session_id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
assert!(
|
||||
!native_session_id.is_empty(),
|
||||
"expected native_session_id for pi session"
|
||||
);
|
||||
|
||||
let events = read_turn_stream_events(&app.app, &session_id, Duration::from_secs(120)).await;
|
||||
assert!(!events.is_empty(), "no events from pi stream");
|
||||
assert!(
|
||||
!events.iter().any(is_unparsed_event),
|
||||
"agent.unparsed event encountered"
|
||||
);
|
||||
|
||||
let mut last_sequence = 0u64;
|
||||
for event in events {
|
||||
let sequence = event
|
||||
.get("sequence")
|
||||
.and_then(Value::as_u64)
|
||||
.expect("missing sequence");
|
||||
assert!(
|
||||
sequence > last_sequence,
|
||||
"sequence did not increase (prev {last_sequence}, next {sequence})"
|
||||
);
|
||||
last_sequence = sequence;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
|
||||
use sandbox_agent_agent_management::agents::{
|
||||
AgentError, AgentId, AgentManager, InstallOptions, SpawnOptions,
|
||||
|
|
@ -29,6 +30,29 @@ fn prompt_ok(label: &str) -> String {
|
|||
format!("Respond with exactly the text {label} and nothing else.")
|
||||
}
|
||||
|
||||
fn pi_tests_enabled() -> bool {
|
||||
env::var("SANDBOX_TEST_PI")
|
||||
.map(|value| {
|
||||
let value = value.trim().to_ascii_lowercase();
|
||||
value == "1" || value == "true" || value == "yes"
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn pi_on_path() -> bool {
|
||||
let binary = AgentId::Pi.binary_name();
|
||||
let path_var = match env::var_os("PATH") {
|
||||
Some(path) => path,
|
||||
None => return false,
|
||||
};
|
||||
for path in env::split_paths(&path_var) {
|
||||
if path.join(binary).exists() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_agents_install_version_spawn() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
|
|
@ -36,12 +60,15 @@ fn test_agents_install_version_spawn() -> Result<(), Box<dyn std::error::Error>>
|
|||
let env = build_env();
|
||||
assert!(!env.is_empty(), "expected credentials to be available");
|
||||
|
||||
let agents = [
|
||||
let mut agents = vec![
|
||||
AgentId::Claude,
|
||||
AgentId::Codex,
|
||||
AgentId::Opencode,
|
||||
AgentId::Amp,
|
||||
];
|
||||
if pi_tests_enabled() && pi_on_path() {
|
||||
agents.push(AgentId::Pi);
|
||||
}
|
||||
for agent in agents {
|
||||
let install = manager.install(agent, InstallOptions::default())?;
|
||||
assert!(install.path.exists(), "expected install for {agent}");
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ async fn install_agent(app: &Router, agent: AgentId) {
|
|||
/// while other agents support "bypass" which skips tool approval.
|
||||
fn test_permission_mode(agent: AgentId) -> &'static str {
|
||||
match agent {
|
||||
AgentId::Opencode => "default",
|
||||
AgentId::Opencode | AgentId::Pi => "default",
|
||||
_ => "bypass",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ pub async fn create_session_with_mode(
|
|||
|
||||
pub fn test_permission_mode(agent: AgentId) -> &'static str {
|
||||
match agent {
|
||||
AgentId::Opencode => "default",
|
||||
AgentId::Opencode | AgentId::Pi => "default",
|
||||
_ => "bypass",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue