antigravity_codes/
client.rs1use std::collections::{HashMap, HashSet};
4
5use crate::error::{Error, Result};
6use crate::handlers::Handlers;
7use crate::process::HarnessOptions;
8use crate::protocol::{
9 InitializeConversationResponse, InputEvent, OutputEventEvent, StepUpdate, ToolConfirmation,
10 TrajectoryStateUpdate, TrajectoryStateUpdateState, UsageMetadata,
11};
12use crate::steps::{Step, StepAssembler};
13use crate::RawClient;
14
15#[derive(Debug)]
45pub struct Client {
46 raw: RawClient,
47 handlers: Handlers,
48 assembler: StepAssembler,
49 usage: Option<UsageMetadata>,
50 trajectory_usage: HashMap<String, UsageMetadata>,
51}
52
53impl Client {
54 pub async fn launch(options: HarnessOptions) -> Result<Self> {
56 Self::launch_with(options, Handlers::new()).await
57 }
58
59 pub async fn launch_with(options: HarnessOptions, handlers: Handlers) -> Result<Self> {
61 let raw = RawClient::launch(options).await?;
62 let mut assembler = StepAssembler::new(raw.cascade_id().map(str::to_string));
63
64 let initialize = raw.initialize_response().clone();
65 for update in initialize.history.iter().cloned() {
69 assembler.ingest(update);
70 }
71
72 Ok(Self {
73 usage: initialize.cumulative_usage.clone(),
74 trajectory_usage: initialize
75 .trajectory_usage
76 .iter()
77 .filter_map(|e| Some((e.trajectory_id.clone()?, e.usage.clone()?)))
78 .collect(),
79 raw,
80 handlers,
81 assembler,
82 })
83 }
84
85 pub fn cascade_id(&self) -> Option<&str> {
90 self.raw.cascade_id()
91 }
92
93 pub fn initialize_response(&self) -> &InitializeConversationResponse {
95 self.raw.initialize_response()
96 }
97
98 pub fn usage(&self) -> Option<&UsageMetadata> {
100 self.usage.as_ref()
101 }
102
103 pub fn trajectory_usage(&self) -> &HashMap<String, UsageMetadata> {
106 &self.trajectory_usage
107 }
108
109 pub fn raw(&mut self) -> &mut RawClient {
111 &mut self.raw
112 }
113
114 pub async fn send(&mut self, prompt: impl Into<String>) -> Result<Turn<'_>> {
116 self.send_event(InputEvent::user(prompt)).await
117 }
118
119 pub async fn send_event(&mut self, event: InputEvent) -> Result<Turn<'_>> {
126 self.raw.send(&event).await?;
127 Ok(Turn {
128 client: self,
129 finished: false,
130 failure: None,
131 answered: HashSet::new(),
132 })
133 }
134
135 pub async fn cancel(&mut self) -> Result<()> {
140 self.raw.send(&InputEvent::halt()).await
141 }
142
143 pub async fn shutdown(self) -> Result<()> {
145 self.raw.shutdown().await
146 }
147
148 fn record_usage(&mut self, update: crate::protocol::UsageUpdate) {
149 if let Some(total) = update.total {
150 self.usage = Some(total);
151 }
152 for entry in update.agents {
153 if let (Some(id), Some(usage)) = (entry.trajectory_id, entry.usage) {
154 self.trajectory_usage.insert(id, usage);
155 }
156 }
157 }
158}
159
160#[derive(Debug)]
168pub struct Turn<'a> {
169 client: &'a mut Client,
170 finished: bool,
171 failure: Option<String>,
172 answered: HashSet<(String, u32, &'static str)>,
175}
176
177impl Turn<'_> {
178 pub async fn next_step(&mut self) -> Result<Option<Step>> {
183 loop {
184 if self.finished {
185 return match self.failure.take() {
186 Some(message) => Err(Error::Turn { message }),
187 None => Ok(None),
188 };
189 }
190
191 let Some(event) = self.client.raw.next_event().await? else {
192 self.finished = true;
193 continue;
194 };
195
196 match event.into_event() {
197 Some(OutputEventEvent::StepUpdate(update)) => {
198 self.answer_in_band_requests(&update).await?;
199 return Ok(Some(self.client.assembler.ingest(update)));
200 }
201 Some(OutputEventEvent::ToolCall(call)) => {
202 let response = self.client.handlers.call_tool(call).await;
203 self.client
204 .raw
205 .send(&InputEvent::tool_response(response))
206 .await?;
207 }
208 Some(OutputEventEvent::CallHookRequest(request)) => {
209 let response = self.client.handlers.call_hook(request).await;
210 self.client
211 .raw
212 .send(&InputEvent::hook_response(response))
213 .await?;
214 }
215 Some(OutputEventEvent::PolicyDecisionRequest(request)) => {
216 let response = self.client.handlers.call_policy(request).await;
217 self.client
218 .raw
219 .send(&InputEvent::policy_response(response))
220 .await?;
221 }
222 Some(OutputEventEvent::UsageUpdate(update)) => self.client.record_usage(update),
223 Some(OutputEventEvent::TrajectoryStateUpdate(update)) => {
224 self.observe_trajectory(&update)
225 }
226 Some(OutputEventEvent::SessionEndResponse(_)) => self.finished = true,
227 Some(OutputEventEvent::InitializeConversationResponse(_)) | None => {}
231 }
232 }
233 }
234
235 pub async fn collect_text(&mut self) -> Result<String> {
237 let mut out = String::new();
238 let mut seen = HashSet::new();
239 while let Some(step) = self.next_step().await? {
240 if step.is_final() && seen.insert(step.id()) {
241 if let Some(text) = step.user_facing_text() {
242 out.push_str(text);
243 }
244 }
245 }
246 Ok(out)
247 }
248
249 pub fn is_finished(&self) -> bool {
251 self.finished
252 }
253
254 async fn answer_in_band_requests(&mut self, update: &StepUpdate) -> Result<()> {
257 let trajectory_id = update.trajectory_id.clone().unwrap_or_default();
258 let step_index = update.step_index.unwrap_or_default();
259
260 if let Some(request) = update.questions_request.clone() {
261 if self
262 .answered
263 .insert((trajectory_id.clone(), step_index, "questions"))
264 {
265 let mut response = self.client.handlers.call_questions(request).await;
266 response.trajectory_id = Some(trajectory_id.clone());
267 response.step_index = Some(step_index);
268 self.client
269 .raw
270 .send(&InputEvent {
271 question_response: Some(response),
272 ..Default::default()
273 })
274 .await?;
275 }
276 }
277
278 if update.tool_confirmation_request.is_some()
279 && self
280 .answered
281 .insert((trajectory_id.clone(), step_index, "confirm"))
282 {
283 let accepted = self.client.handlers.call_confirm(update.clone()).await;
284 self.client
285 .raw
286 .send(&InputEvent {
287 tool_confirmation: Some(ToolConfirmation {
288 trajectory_id: Some(trajectory_id),
289 step_index: Some(step_index),
290 accepted: Some(accepted),
291 }),
292 ..Default::default()
293 })
294 .await?;
295 }
296
297 Ok(())
298 }
299
300 fn observe_trajectory(&mut self, update: &TrajectoryStateUpdate) {
303 let id = update.trajectory_id.as_deref().unwrap_or_default();
304 let error = update.error.clone().filter(|e| !e.is_empty());
305
306 if !self.client.assembler.is_main(id) {
307 if let Some(error) = error {
308 log::info!("subagent trajectory {id} failed: {error}");
309 }
310 return;
311 }
312
313 match update.state {
314 Some(TrajectoryStateUpdateState::FullyIdle) => {
315 self.finished = true;
316 self.failure = error;
317 }
318 Some(TrajectoryStateUpdateState::Cancelled) => {
319 self.finished = true;
320 self.failure = Some(error.unwrap_or_else(|| "turn cancelled".into()));
321 }
322 _ => {}
323 }
324 }
325}