mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 20:01:27 +00:00
wip: pi working
This commit is contained in:
parent
a6064e7027
commit
bef2e84d0c
9 changed files with 1747 additions and 39 deletions
|
|
@ -2628,8 +2628,12 @@ pub fn build_opencode_router(state: Arc<OpenCodeAppState>) -> Router {
|
|||
responses((status = 200)),
|
||||
tag = "opencode"
|
||||
)]
|
||||
<<<<<<< Updated upstream
|
||||
async fn oc_agent_list(State(state): State<Arc<OpenCodeAppState>>) -> impl IntoResponse {
|
||||
let name = state.inner.branding.product_name();
|
||||
=======
|
||||
async fn oc_agent_list(State(_state): State<Arc<OpenCodeAppState>>) -> impl IntoResponse {
|
||||
>>>>>>> Stashed changes
|
||||
let agent = json!({
|
||||
"name": name,
|
||||
"description": format!("{name} compatibility layer"),
|
||||
|
|
@ -4274,6 +4278,7 @@ async fn oc_file_list() -> impl IntoResponse {
|
|||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_file_content(Query(query): Query<FileContentQuery>) -> impl IntoResponse {
|
||||
let _directory = query.directory.as_deref();
|
||||
if query.path.is_none() {
|
||||
return bad_request("path is required").into_response();
|
||||
}
|
||||
|
|
@ -4304,6 +4309,7 @@ async fn oc_file_status() -> impl IntoResponse {
|
|||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_find_text(Query(query): Query<FindTextQuery>) -> impl IntoResponse {
|
||||
let _directory = query.directory.as_deref();
|
||||
if query.pattern.is_none() {
|
||||
return bad_request("pattern is required").into_response();
|
||||
}
|
||||
|
|
@ -4317,6 +4323,7 @@ async fn oc_find_text(Query(query): Query<FindTextQuery>) -> impl IntoResponse {
|
|||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_find_files(Query(query): Query<FindFilesQuery>) -> impl IntoResponse {
|
||||
let _directory = query.directory.as_deref();
|
||||
if query.query.is_none() {
|
||||
return bad_request("query is required").into_response();
|
||||
}
|
||||
|
|
@ -4330,6 +4337,7 @@ async fn oc_find_files(Query(query): Query<FindFilesQuery>) -> impl IntoResponse
|
|||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_find_symbols(Query(query): Query<FindSymbolsQuery>) -> impl IntoResponse {
|
||||
let _directory = query.directory.as_deref();
|
||||
if query.query.is_none() {
|
||||
return bad_request("query is required").into_response();
|
||||
}
|
||||
|
|
@ -4446,6 +4454,7 @@ async fn oc_tool_ids() -> impl IntoResponse {
|
|||
tag = "opencode"
|
||||
)]
|
||||
async fn oc_tool_list(Query(query): Query<ToolQuery>) -> impl IntoResponse {
|
||||
let _directory = query.directory.as_deref();
|
||||
if query.provider.is_none() || query.model.is_none() {
|
||||
return bad_request("provider and model are required").into_response();
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,32 @@
|
|||
// Pi RPC integration tests (gated via SANDBOX_TEST_PI + PATH).
|
||||
include!("../common/http.rs");
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
struct EnvVarGuard {
|
||||
key: String,
|
||||
previous: Option<String>,
|
||||
}
|
||||
|
||||
impl Drop for EnvVarGuard {
|
||||
fn drop(&mut self) {
|
||||
match &self.previous {
|
||||
Some(value) => std::env::set_var(&self.key, value),
|
||||
None => std::env::remove_var(&self.key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_env_var(key: &str, value: &str) -> EnvVarGuard {
|
||||
let previous = std::env::var(key).ok();
|
||||
std::env::set_var(key, value);
|
||||
EnvVarGuard {
|
||||
key: key.to_string(),
|
||||
previous,
|
||||
}
|
||||
}
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
fn pi_test_config() -> Option<TestAgentConfig> {
|
||||
let configs = match test_agents_from_env() {
|
||||
Ok(configs) => configs,
|
||||
|
|
@ -13,6 +39,83 @@ fn pi_test_config() -> Option<TestAgentConfig> {
|
|||
.into_iter()
|
||||
.find(|config| config.agent == AgentId::Pi)
|
||||
}
|
||||
<<<<<<< Updated upstream
|
||||
=======
|
||||
|
||||
async fn create_pi_session_checked(app: &Router, session_id: &str) -> Value {
|
||||
let (status, payload) = send_json(
|
||||
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 {session_id}");
|
||||
payload
|
||||
}
|
||||
|
||||
async fn poll_events_until_assistant_count(
|
||||
app: &Router,
|
||||
session_id: &str,
|
||||
expected_assistant_messages: usize,
|
||||
timeout: Duration,
|
||||
) -> Vec<Value> {
|
||||
let start = Instant::now();
|
||||
let mut offset = 0u64;
|
||||
let mut events = Vec::new();
|
||||
|
||||
while start.elapsed() < timeout {
|
||||
let path = format!("/v1/sessions/{session_id}/events?offset={offset}&limit=200");
|
||||
let (status, payload) = send_json(app, Method::GET, &path, None).await;
|
||||
assert_eq!(status, StatusCode::OK, "poll events");
|
||||
let new_events = payload
|
||||
.get("events")
|
||||
.and_then(Value::as_array)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
if !new_events.is_empty() {
|
||||
if let Some(last) = new_events
|
||||
.last()
|
||||
.and_then(|event| event.get("sequence"))
|
||||
.and_then(Value::as_u64)
|
||||
{
|
||||
offset = last;
|
||||
}
|
||||
events.extend(new_events);
|
||||
}
|
||||
|
||||
if events.iter().any(is_unparsed_event) {
|
||||
break;
|
||||
}
|
||||
|
||||
let assistant_count = events
|
||||
.iter()
|
||||
.filter(|event| is_assistant_message(event))
|
||||
.count();
|
||||
if assistant_count >= expected_assistant_messages {
|
||||
break;
|
||||
}
|
||||
|
||||
if events.iter().any(is_error_event) {
|
||||
break;
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(800)).await;
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pi_rpc_session_and_stream() {
|
||||
let Some(config) = pi_test_config() else {
|
||||
return;
|
||||
};
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
async fn create_pi_session_with_native(app: &Router, session_id: &str) -> String {
|
||||
let (status, payload) = send_json(
|
||||
|
|
@ -53,6 +156,7 @@ fn assert_strictly_increasing_sequences(events: &[Value], label: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
fn assert_all_events_for_session(events: &[Value], session_id: &str) {
|
||||
for event in events {
|
||||
let event_session_id = event
|
||||
|
|
@ -93,6 +197,11 @@ fn assert_item_started_ids_unique(events: &[Value], label: &str) {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pi_rpc_session_and_stream() {
|
||||
=======
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pi_rpc_multi_session_create_per_session_mode() {
|
||||
let _mode_guard = set_env_var("SANDBOX_AGENT_PI_FORCE_RUNTIME_MODE", "per-session");
|
||||
>>>>>>> Stashed changes
|
||||
let Some(config) = pi_test_config() else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -101,6 +210,7 @@ async fn pi_rpc_session_and_stream() {
|
|||
let _guard = apply_credentials(&config.credentials);
|
||||
install_agent(&app.app, config.agent).await;
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
let session_id = "pi-rpc-session";
|
||||
let _native_session_id = create_pi_session_with_native(&app.app, session_id).await;
|
||||
|
||||
|
|
@ -119,6 +229,33 @@ async fn pi_rpc_session_and_stream() {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pi_parallel_sessions_turns() {
|
||||
=======
|
||||
let first = create_pi_session_checked(&app.app, "pi-multi-a").await;
|
||||
let second = create_pi_session_checked(&app.app, "pi-multi-b").await;
|
||||
|
||||
let first_native = first
|
||||
.get("native_session_id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
let second_native = second
|
||||
.get("native_session_id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
assert!(!first_native.is_empty(), "first native session id missing");
|
||||
assert!(
|
||||
!second_native.is_empty(),
|
||||
"second native session id missing"
|
||||
);
|
||||
assert_ne!(
|
||||
first_native, second_native,
|
||||
"per-session mode should allocate independent native session ids"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pi_rpc_per_session_queue_and_termination_isolation() {
|
||||
let _mode_guard = set_env_var("SANDBOX_AGENT_PI_FORCE_RUNTIME_MODE", "per-session");
|
||||
>>>>>>> Stashed changes
|
||||
let Some(config) = pi_test_config() else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -127,6 +264,7 @@ async fn pi_parallel_sessions_turns() {
|
|||
let _guard = apply_credentials(&config.credentials);
|
||||
install_agent(&app.app, config.agent).await;
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
let session_a = "pi-parallel-a";
|
||||
let session_b = "pi-parallel-b";
|
||||
create_pi_session_with_native(&app.app, session_a).await;
|
||||
|
|
@ -285,3 +423,105 @@ async fn pi_runtime_restart_scope() {
|
|||
);
|
||||
assert_all_events_for_session(&events_b, session_b);
|
||||
}
|
||||
=======
|
||||
create_pi_session_checked(&app.app, "pi-queue-a").await;
|
||||
create_pi_session_checked(&app.app, "pi-queue-b").await;
|
||||
|
||||
let status = send_status(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/pi-queue-a/messages",
|
||||
Some(json!({ "message": "Reply with exactly FIRST." })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::NO_CONTENT, "send first prompt");
|
||||
|
||||
let status = send_status(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/pi-queue-a/messages",
|
||||
Some(json!({ "message": "Reply with exactly SECOND." })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::NO_CONTENT, "enqueue second prompt");
|
||||
|
||||
let status = send_status(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/pi-queue-b/messages",
|
||||
Some(json!({ "message": PROMPT })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
status,
|
||||
StatusCode::NO_CONTENT,
|
||||
"send prompt to sibling session"
|
||||
);
|
||||
|
||||
let events_a =
|
||||
poll_events_until_assistant_count(&app.app, "pi-queue-a", 2, Duration::from_secs(240))
|
||||
.await;
|
||||
let events_b =
|
||||
poll_events_until_assistant_count(&app.app, "pi-queue-b", 1, Duration::from_secs(180))
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
!events_a.iter().any(is_unparsed_event),
|
||||
"session a emitted agent.unparsed"
|
||||
);
|
||||
assert!(
|
||||
!events_b.iter().any(is_unparsed_event),
|
||||
"session b emitted agent.unparsed"
|
||||
);
|
||||
let assistant_count_a = events_a
|
||||
.iter()
|
||||
.filter(|event| is_assistant_message(event))
|
||||
.count();
|
||||
let assistant_count_b = events_b
|
||||
.iter()
|
||||
.filter(|event| is_assistant_message(event))
|
||||
.count();
|
||||
assert!(
|
||||
assistant_count_a >= 2,
|
||||
"expected at least two assistant completions for queued session, got {assistant_count_a}"
|
||||
);
|
||||
assert!(
|
||||
assistant_count_b >= 1,
|
||||
"expected assistant completion for sibling session, got {assistant_count_b}"
|
||||
);
|
||||
|
||||
let status = send_status(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/pi-queue-a/terminate",
|
||||
Some(json!({})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::NO_CONTENT, "terminate first session");
|
||||
|
||||
let status = send_status(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/pi-queue-b/messages",
|
||||
Some(json!({ "message": PROMPT })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
status,
|
||||
StatusCode::NO_CONTENT,
|
||||
"sibling session should continue after termination"
|
||||
);
|
||||
|
||||
let events_b_after =
|
||||
poll_events_until_assistant_count(&app.app, "pi-queue-b", 2, Duration::from_secs(180))
|
||||
.await;
|
||||
let assistant_count_b_after = events_b_after
|
||||
.iter()
|
||||
.filter(|event| is_assistant_message(event))
|
||||
.count();
|
||||
assert!(
|
||||
assistant_count_b_after >= 2,
|
||||
"expected additional assistant completion for sibling session after termination"
|
||||
);
|
||||
}
|
||||
>>>>>>> Stashed changes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue