diff options
Diffstat (limited to 'src/api')
| -rw-r--r-- | src/api/client.rs | 67 | ||||
| -rw-r--r-- | src/api/mod.rs | 1 | ||||
| -rw-r--r-- | src/api/schema.rs | 2 | ||||
| -rw-r--r-- | src/api/schema/agents.rs | 32 | ||||
| -rw-r--r-- | src/api/schema/response.rs | 4 | ||||
| -rw-r--r-- | src/api/schema/tests.rs | 35 | ||||
| -rw-r--r-- | src/api/server.rs | 1 |
7 files changed, 63 insertions, 79 deletions
diff --git a/src/api/client.rs b/src/api/client.rs index 38baf807..326ebc7d 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -7,8 +7,7 @@ use interprocess::local_socket::traits::Stream as _; use serde::de::DeserializeOwned; use crate::api::schema::{ - ErrorResponse, EventsSubscribeParams, Method, PingParams, Request, ResponseResult, - SubscriptionEventEnvelope, SuccessResponse, + ErrorResponse, Method, PingParams, Request, ResponseResult, SuccessResponse, }; use crate::ipc::LocalStream; @@ -75,37 +74,6 @@ impl ApiClient { read_json_line(&mut reader) } - #[allow(dead_code)] // Kept as the typed subscription API; CLI wait paths use subscribe_value to preserve raw ack errors. - pub fn subscribe( - &self, - id: impl Into<String>, - params: EventsSubscribeParams, - read_timeout: Option<Duration>, - ) -> Result<(SuccessResponse, EventStream), ApiClientError> { - let request = Request { - id: id.into(), - method: Method::EventsSubscribe(params), - }; - let (ack, stream) = self.subscribe_value(&request, read_timeout)?; - Ok((parse_response_value(ack)?, stream)) - } - - pub fn subscribe_value( - &self, - request: &Request, - read_timeout: Option<Duration>, - ) -> Result<(serde_json::Value, EventStream), ApiClientError> { - let mut stream = self.connect()?; - write_request(&mut stream, request)?; - if let Some(timeout) = read_timeout { - set_timeout_best_effort(&stream, TimeoutKind::Recv, timeout)?; - } - - let mut reader = BufReader::new(stream); - let ack = read_json_line(&mut reader)?; - Ok((ack, EventStream { reader })) - } - pub fn status(&self) -> Result<crate::api::RuntimeStatus, ApiClientError> { let response = self.request(Request { id: "api-client:status".into(), @@ -152,23 +120,6 @@ fn set_timeout_best_effort( } } -pub struct EventStream { - reader: BufReader<LocalStream>, -} - -impl EventStream { - pub fn next_value(&mut self) -> Result<Option<serde_json::Value>, ApiClientError> { - read_optional_json_line(&mut self.reader) - } - - pub fn next_event(&mut self) -> Result<Option<SubscriptionEventEnvelope>, ApiClientError> { - self.next_value()? - .map(serde_json::from_value) - .transpose() - .map_err(ApiClientError::Json) - } -} - #[derive(Debug)] pub enum ApiClientError { Io(io::Error), @@ -222,22 +173,6 @@ fn read_json_line<T: DeserializeOwned>( serde_json::from_str(&line).map_err(ApiClientError::Json) } -fn read_optional_json_line<T: DeserializeOwned>( - reader: &mut BufReader<LocalStream>, -) -> Result<Option<T>, ApiClientError> { - let mut line = String::new(); - let read = reader.read_line(&mut line)?; - if read == 0 { - return Ok(None); - } - if line.trim().is_empty() { - return Err(ApiClientError::EmptyResponse); - } - serde_json::from_str(&line) - .map(Some) - .map_err(ApiClientError::Json) -} - #[derive(serde::Deserialize)] #[serde(untagged)] enum WireResponse { diff --git a/src/api/mod.rs b/src/api/mod.rs index 364d5fc8..618fa4f9 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -44,6 +44,7 @@ pub(crate) fn request_changes_ui(request: &Request) -> bool { | Method::AgentRename(_) | Method::AgentFocus(_) | Method::AgentStart(_) + | Method::AgentPrompt(_) | Method::PaneSplit(_) | Method::PaneSwap(_) | Method::PaneMove(_) diff --git a/src/api/schema.rs b/src/api/schema.rs index 4f5df34f..41f372e3 100644 --- a/src/api/schema.rs +++ b/src/api/schema.rs @@ -117,6 +117,8 @@ pub enum Method { AgentFocus(AgentTarget), #[serde(rename = "agent.start")] AgentStart(AgentStartParams), + #[serde(rename = "agent.prompt")] + AgentPrompt(AgentPromptParams), #[serde(rename = "pane.split")] PaneSplit(PaneSplitParams), #[serde(rename = "pane.swap")] diff --git a/src/api/schema/agents.rs b/src/api/schema/agents.rs index 969383c4..8592e56b 100644 --- a/src/api/schema/agents.rs +++ b/src/api/schema/agents.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use super::common::{AgentStatus, ReadFormat, ReadSource, SplitDirection}; +use super::common::{AgentStatus, ReadFormat, ReadSource}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct AgentReadParams { @@ -32,19 +32,19 @@ pub struct AgentRenameParams { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct AgentStartParams { pub name: String, + pub kind: String, + pub pane_id: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub args: Vec<String>, + /// Startup timeout in milliseconds. Values must be greater than 3000 and at most 300000. #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workspace_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub tab_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub split: Option<SplitDirection>, - #[serde(default)] - pub focus: bool, - pub argv: Vec<String>, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub env: HashMap<String, String>, + pub timeout_ms: Option<u64>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct AgentPromptParams { + pub target: String, + pub text: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] @@ -76,6 +76,12 @@ pub struct AgentInfo { pub tab_id: String, pub pane_id: String, pub focused: bool, + #[serde(default, skip_serializing_if = "super::is_false")] + pub launch_pending: bool, + #[serde(default, skip_serializing_if = "super::is_false")] + pub interactive_ready: bool, + #[serde(default)] + pub state_change_seq: u64, #[serde(default, skip_serializing_if = "Option::is_none")] pub cwd: Option<String>, #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/src/api/schema/response.rs b/src/api/schema/response.rs index 129c8f91..ad058191 100644 --- a/src/api/schema/response.rs +++ b/src/api/schema/response.rs @@ -101,6 +101,10 @@ pub enum ResponseResult { agent: AgentInfo, argv: Vec<String>, }, + AgentPrompted { + agent: AgentInfo, + baseline_state_change_seq: u64, + }, AgentList { agents: Vec<AgentInfo>, }, diff --git a/src/api/schema/tests.rs b/src/api/schema/tests.rs index bc044ee9..232c2b73 100644 --- a/src/api/schema/tests.rs +++ b/src/api/schema/tests.rs @@ -62,6 +62,41 @@ fn request_uses_dot_method_names() { } #[test] +fn agent_start_and_prompt_requests_round_trip() { + let start = Request { + id: "start".into(), + method: Method::AgentStart(AgentStartParams { + name: "reviewer".into(), + kind: "pi".into(), + pane_id: "w1:p2".into(), + args: vec!["--no-session".into()], + timeout_ms: Some(30_000), + }), + }; + let start_json = serde_json::to_value(&start).unwrap(); + assert_eq!(start_json["method"], "agent.start"); + assert_eq!(start_json["params"]["pane_id"], "w1:p2"); + assert_eq!( + serde_json::from_value::<Request>(start_json).unwrap(), + start + ); + + let prompt = Request { + id: "prompt".into(), + method: Method::AgentPrompt(AgentPromptParams { + target: "reviewer".into(), + text: "review this".into(), + }), + }; + let prompt_json = serde_json::to_value(&prompt).unwrap(); + assert_eq!(prompt_json["method"], "agent.prompt"); + assert_eq!( + serde_json::from_value::<Request>(prompt_json).unwrap(), + prompt + ); +} + +#[test] fn bundled_protocol_schema_refs_resolve_inside_bundle() { fn assert_no_standalone_refs(value: &serde_json::Value) { match value { diff --git a/src/api/server.rs b/src/api/server.rs index d159e1dc..ce538853 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -371,6 +371,7 @@ fn api_method_name(method: &Method) -> &'static str { Method::AgentRename(_) => "agent.rename", Method::AgentFocus(_) => "agent.focus", Method::AgentStart(_) => "agent.start", + Method::AgentPrompt(_) => "agent.prompt", Method::PaneSplit(_) => "pane.split", Method::PaneSwap(_) => "pane.swap", Method::PaneMove(_) => "pane.move", |
