aboutsummaryrefslogtreecommitdiff
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/client.rs67
-rw-r--r--src/api/mod.rs1
-rw-r--r--src/api/schema.rs2
-rw-r--r--src/api/schema/agents.rs32
-rw-r--r--src/api/schema/response.rs4
-rw-r--r--src/api/schema/tests.rs35
-rw-r--r--src/api/server.rs1
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",