1mod runtime;
4
5use std::{
6 collections::{HashMap, HashSet},
7 fmt,
8 sync::{
9 Arc, Mutex as StdMutex,
10 atomic::{AtomicUsize, Ordering},
11 },
12 time::Duration,
13};
14
15use crate::{
16 AgentUsageParams, AgentUsageResult, ApprovalListParams, ApprovalListResult,
17 ApprovalRespondParams, ApprovalRespondResult, ClientHello, EventPayload,
18 EventSubscriptionMessage, EventsSubscribeParams, EventsSubscribeResult, GlobalUsageParams,
19 GlobalUsageResult, JsonRpcError, JsonRpcId, McpAuthBeginParams, McpAuthBeginResult,
20 McpAuthCancelParams, McpAuthCancelResult, McpServerAddParams, McpServerEditParams,
21 McpServerListParams, McpServerListResult, McpServerMutationResult, McpServerNameParams,
22 McpServerPersistParams, McpServerSetEnabledParams, MessageFrame, Notification, OutputDelta,
23 OutputGap, OutputSnapshotEnvelope, OutputStream, ProtocolVersion, ProviderConnectParams,
24 ProviderConnectResult, ProviderDisconnectParams, ProviderDisconnectResult, Response,
25 RunCancelParams, RunCancelResult, RunRecallSteerParams, RunRecallSteerResult, RunStartParams,
26 RunStartResult, RunSteerParams, RunSteerResult, RunToolStdinParams, RunToolStdinResult,
27 RuntimeSnapshotResult, ServerHello, SessionChildrenParams, SessionChildrenResult,
28 SessionCompactParams, SessionCompactResult, SessionCreateParams, SessionCreateResult,
29 SessionForkParams, SessionForkResult, SessionGetParams, SessionGetResult, SessionGoalGetParams,
30 SessionGoalGetResult, SessionGoalLifecycleParams, SessionGoalLifecycleResult,
31 SessionGoalSetParams, SessionGoalSetResult, SessionId, SessionListParams, SessionListResult,
32 SessionPermissionClearParams, SessionPermissionGetParams, SessionPermissionGetResult,
33 SessionPermissionMutationResult, SessionPermissionSetParams, SessionProducersParams,
34 SessionProducersResult, SessionRenameParams, SessionRenameResult, SessionResumeParams,
35 SessionResumeResult, SessionRevertParams, SessionRevertResult, SessionSetPermissionModeParams,
36 SessionSetPermissionModeResult, SessionTreeParams, SessionTreeResult, SessionTreeUsageResult,
37 SessionUsageParams, SessionUsageResult, SkillsGetParams, SkillsGetResult, SkillsListParams,
38 SkillsListResult, StoredEvent, ToolCallId, Transport, TransportError,
39};
40use serde::{Serialize, de::DeserializeOwned};
41use serde_json::Value;
42use thiserror::Error;
43use tokio::sync::{Mutex, mpsc, oneshot};
44use tokio::time::Instant;
45use zeroize::{Zeroize, Zeroizing};
46
47const COMMAND_QUEUE_CAPACITY: usize = 128;
48const RECOVERY_ATTEMPTS: usize = 6;
49const REPLAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);
50
51#[derive(Clone, Debug)]
56pub enum ClientDelivery {
57 Live {
58 message: Box<EventSubscriptionMessage>,
59 generation: u64,
60 },
61 ReplayStart {
62 session_id: SessionId,
63 generation: u64,
64 final_seq: u64,
65 rebuild: bool,
66 },
67 ReplayEvent {
68 session_id: SessionId,
69 generation: u64,
70 final_seq: u64,
71 event: Box<StoredEvent>,
72 },
73 ReplayEnd {
74 session_id: SessionId,
75 generation: u64,
76 final_seq: u64,
77 },
78 OutputSnapshot(OutputSnapshotEnvelope),
79 OutputDelta(OutputDelta),
80 OutputGap(OutputGap),
81 PluginEvent(crate::ExtensionBusEventParams),
82 RecoveryFailed {
83 session_id: Option<SessionId>,
84 error: String,
85 },
86 Disconnected {
87 error: String,
88 },
89 RuntimeChanged(Box<crate::RuntimeChangedNotification>),
90}
91
92pub trait ClientEventSink: Send + Sync + 'static {
94 fn deliver(&self, delivery: ClientDelivery);
95
96 fn wants_raw_tool_output(&self) -> bool {
99 true
100 }
101}
102
103struct DisplayOnlySink(mpsc::UnboundedSender<ClientDelivery>);
104
105impl ClientEventSink for DisplayOnlySink {
106 fn deliver(&self, delivery: ClientDelivery) {
107 self.0.deliver(delivery);
108 }
109
110 fn wants_raw_tool_output(&self) -> bool {
111 false
112 }
113}
114
115#[async_trait::async_trait]
117pub trait ClientProtocol: Send + Sync {
118 async fn handshake(&self) -> Result<ServerHello, ClientError>;
119 async fn create_session(
120 &self,
121 params: SessionCreateParams,
122 ) -> Result<SessionCreateResult, ClientError>;
123 async fn list_sessions(
124 &self,
125 params: SessionListParams,
126 ) -> Result<SessionListResult, ClientError>;
127 async fn get_session(&self, params: SessionGetParams) -> Result<SessionGetResult, ClientError>;
128 async fn get_session_goal(
129 &self,
130 params: SessionGoalGetParams,
131 ) -> Result<SessionGoalGetResult, ClientError>;
132 async fn set_session_goal(
133 &self,
134 params: SessionGoalSetParams,
135 ) -> Result<SessionGoalSetResult, ClientError>;
136 async fn change_session_goal_lifecycle(
137 &self,
138 params: SessionGoalLifecycleParams,
139 ) -> Result<SessionGoalLifecycleResult, ClientError>;
140 async fn session_producers(
141 &self,
142 params: SessionProducersParams,
143 ) -> Result<SessionProducersResult, ClientError>;
144 async fn session_usage(
145 &self,
146 params: SessionUsageParams,
147 ) -> Result<SessionUsageResult, ClientError>;
148 async fn session_tree_usage(
149 &self,
150 params: SessionUsageParams,
151 ) -> Result<SessionTreeUsageResult, ClientError>;
152 async fn agent_usage(&self, params: AgentUsageParams) -> Result<AgentUsageResult, ClientError>;
153 async fn global_usage(&self) -> Result<GlobalUsageResult, ClientError>;
154 async fn session_children(
155 &self,
156 params: SessionChildrenParams,
157 ) -> Result<SessionChildrenResult, ClientError>;
158 async fn session_tree(
159 &self,
160 params: SessionTreeParams,
161 ) -> Result<SessionTreeResult, ClientError>;
162 async fn resume_session(
163 &self,
164 params: SessionResumeParams,
165 ) -> Result<SessionResumeResult, ClientError>;
166 async fn rename_session(
167 &self,
168 params: SessionRenameParams,
169 ) -> Result<SessionRenameResult, ClientError>;
170 async fn set_permission_mode(
171 &self,
172 params: SessionSetPermissionModeParams,
173 ) -> Result<SessionSetPermissionModeResult, ClientError>;
174 async fn get_session_permissions(
175 &self,
176 params: SessionPermissionGetParams,
177 ) -> Result<SessionPermissionGetResult, ClientError>;
178 async fn set_session_permission(
179 &self,
180 params: SessionPermissionSetParams,
181 ) -> Result<SessionPermissionMutationResult, ClientError>;
182 async fn clear_session_permission(
183 &self,
184 params: SessionPermissionClearParams,
185 ) -> Result<SessionPermissionMutationResult, ClientError>;
186 async fn list_skills(&self, params: SkillsListParams) -> Result<SkillsListResult, ClientError>;
187 async fn get_skill(&self, params: SkillsGetParams) -> Result<SkillsGetResult, ClientError>;
188 async fn compact_session(
189 &self,
190 params: SessionCompactParams,
191 ) -> Result<SessionCompactResult, ClientError>;
192 async fn revert_session(
193 &self,
194 params: SessionRevertParams,
195 ) -> Result<SessionRevertResult, ClientError>;
196 async fn fork_session(
197 &self,
198 params: SessionForkParams,
199 ) -> Result<SessionForkResult, ClientError>;
200 async fn start_run(&self, params: RunStartParams) -> Result<RunStartResult, ClientError>;
201 async fn steer_run(&self, params: RunSteerParams) -> Result<RunSteerResult, ClientError>;
202 async fn recall_steer(
203 &self,
204 params: RunRecallSteerParams,
205 ) -> Result<RunRecallSteerResult, ClientError>;
206 async fn cancel_run(&self, params: RunCancelParams) -> Result<RunCancelResult, ClientError>;
207 async fn tool_stdin(
208 &self,
209 params: RunToolStdinParams,
210 ) -> Result<RunToolStdinResult, ClientError>;
211 async fn respond_approval(
212 &self,
213 params: ApprovalRespondParams,
214 ) -> Result<ApprovalRespondResult, ClientError>;
215 async fn list_approvals(
216 &self,
217 params: ApprovalListParams,
218 ) -> Result<ApprovalListResult, ClientError>;
219 async fn begin_mcp_auth(
220 &self,
221 params: McpAuthBeginParams,
222 ) -> Result<McpAuthBeginResult, ClientError>;
223 async fn cancel_mcp_auth(
224 &self,
225 params: McpAuthCancelParams,
226 ) -> Result<McpAuthCancelResult, ClientError>;
227 async fn list_mcp_servers(&self) -> Result<McpServerListResult, ClientError>;
228 async fn add_mcp_server(
229 &self,
230 params: McpServerAddParams,
231 ) -> Result<McpServerMutationResult, ClientError>;
232 async fn edit_mcp_server(
233 &self,
234 params: McpServerEditParams,
235 ) -> Result<McpServerMutationResult, ClientError>;
236 async fn remove_mcp_server(
237 &self,
238 params: McpServerNameParams,
239 ) -> Result<McpServerMutationResult, ClientError>;
240 async fn set_mcp_server_enabled(
241 &self,
242 params: McpServerSetEnabledParams,
243 ) -> Result<McpServerMutationResult, ClientError>;
244 async fn reconnect_mcp_server(
245 &self,
246 params: McpServerNameParams,
247 ) -> Result<McpServerMutationResult, ClientError>;
248 async fn persist_mcp_server(
249 &self,
250 params: McpServerPersistParams,
251 ) -> Result<McpServerMutationResult, ClientError>;
252 async fn subscribe_events(
253 &self,
254 session_id: SessionId,
255 cursor: Option<u64>,
256 ) -> Result<(), ClientError>;
257 async fn runtime_snapshot(&self) -> Result<RuntimeSnapshotResult, ClientError>;
258 async fn connect_provider(
259 &self,
260 params: ProviderConnectParams,
261 ) -> Result<ProviderConnectResult, ClientError>;
262 async fn disconnect_provider(
263 &self,
264 params: ProviderDisconnectParams,
265 ) -> Result<ProviderDisconnectResult, ClientError>;
266 fn recover_session(&self, session_id: SessionId, full_replay: bool);
267 fn shutdown(&self);
268}
269
270impl ClientEventSink for mpsc::UnboundedSender<ClientDelivery> {
271 fn deliver(&self, delivery: ClientDelivery) {
272 let _ = self.send(delivery);
273 }
274}
275
276#[derive(Debug, Error)]
278pub enum ClientError {
279 #[error("transport closed")]
280 Closed,
281 #[error("transport failed: {0}")]
282 Transport(String),
283 #[error("invalid JSON-RPC frame: {0}")]
284 InvalidFrame(#[from] serde_json::Error),
285 #[error("{}", crate::diagnostics::rpc(.0))]
286 Rpc(JsonRpcError),
287 #[error("websocket error: {0}")]
288 WebSocket(String),
289 #[error("daemon authentication token is unavailable")]
290 TokenUnavailable,
291 #[error("daemon authentication token path is unsafe")]
292 UnsafeToken,
293 #[error("a subscription replay is already in progress")]
294 ReplayInProgress,
295 #[error("subscription replay response timed out")]
296 ReplayTimedOut,
297}
298
299struct ReplayRequest {
300 session_id: SessionId,
301 generation: u64,
302 rebuild: bool,
303 attempt: u64,
304}
305
306struct Command {
307 id: i64,
308 method: String,
309 params: SerializedParams,
310 replay: Option<ReplayRequest>,
311 response: oneshot::Sender<Result<Value, ClientError>>,
312}
313
314struct SerializedParams {
315 value: Value,
316 sensitive: bool,
317}
318
319impl Drop for SerializedParams {
320 fn drop(&mut self) {
321 if self.sensitive {
322 zeroize_json(&mut self.value);
323 record_sensitive_serialized_wipe();
324 }
325 }
326}
327
328pub(in crate::session::client) struct SensitiveJson(Value);
329
330impl SensitiveJson {
331 pub(in crate::session::client) fn object() -> Self {
332 Self(Value::Object(serde_json::Map::new()))
333 }
334
335 pub(in crate::session::client) fn object_mut(&mut self) -> &mut serde_json::Map<String, Value> {
336 self.0
337 .as_object_mut()
338 .expect("sensitive JSON owner was created as an object")
339 }
340
341 fn take(&mut self) -> Value {
342 std::mem::replace(&mut self.0, Value::Null)
343 }
344}
345
346impl Drop for SensitiveJson {
347 fn drop(&mut self) {
348 if self.0 != Value::Null {
349 zeroize_json(&mut self.0);
350 record_sensitive_serialized_wipe();
351 }
352 }
353}
354
355#[derive(Serialize)]
356struct BorrowedRequest<'a> {
357 jsonrpc: &'static str,
358 id: i64,
359 method: &'a str,
360 params: &'a Value,
361}
362
363enum OutboundFrame {
364 Public(MessageFrame),
365 Sensitive(SensitiveFrame),
366}
367
368struct SensitiveFrame {
373 text: Zeroizing<String>,
374}
375
376impl SensitiveFrame {
377 fn new(text: String) -> Self {
378 Self {
379 text: Zeroizing::new(text),
380 }
381 }
382}
383
384impl fmt::Debug for SensitiveFrame {
385 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
386 formatter.write_str("SensitiveFrame(<redacted>)")
387 }
388}
389
390impl Drop for SensitiveFrame {
391 fn drop(&mut self) {
392 if !self.text.is_empty() {
393 self.text.zeroize();
394 record_sensitive_frame_wipe();
395 }
396 }
397}
398
399struct PendingCommand {
400 replay: Option<ReplayRequest>,
401 response: oneshot::Sender<Result<Value, ClientError>>,
402}
403
404#[derive(Default)]
405struct Subscription {
406 cursor: u64,
407 rollback_cursor: u64,
408 generation: u64,
409 next_attempt: u64,
410 active_attempt: u64,
411 fetching: bool,
412 rebuild: bool,
413 final_seq: u64,
414 replay_tools: HashSet<ToolCallId>,
415 awaiting_snapshots: HashSet<(ToolCallId, String)>,
416 snapshot_deadline: Option<Instant>,
417 buffered: Vec<ClientDelivery>,
418 recovery_requested: Option<bool>,
419}
420
421struct RecoveryQueue {
422 sender: mpsc::UnboundedSender<(bool, Option<SessionId>)>,
423 state: StdMutex<RecoveryQueueState>,
424}
425
426#[derive(Default)]
427struct RecoveryQueueState {
428 queued: bool,
429 follow_up: bool,
430 follow_up_full: bool,
431 follow_up_session: Option<SessionId>,
432}
433
434enum ConnectionControl {
435 RecoveryFailed {
436 session_id: Option<SessionId>,
437 error: String,
438 },
439}
440
441#[derive(Clone)]
444pub struct Client {
445 commands: mpsc::Sender<Command>,
446 deliveries: Arc<StdMutex<Option<mpsc::UnboundedReceiver<ClientDelivery>>>>,
447 subscriptions: Arc<Mutex<HashMap<SessionId, Subscription>>>,
448 recovery: Arc<RecoveryQueue>,
449 shutdown: Arc<ClientShutdown>,
450 #[cfg(test)]
451 pending_command_count: Arc<AtomicUsize>,
452}
453
454struct ClientShutdown(tokio_util::sync::CancellationToken);
455
456impl Drop for ClientShutdown {
457 fn drop(&mut self) {
458 self.0.cancel();
459 }
460}
461
462impl Client {
463 pub fn connect_stream<T>(transport: T) -> Self
465 where
466 T: Transport + 'static,
467 {
468 let (delivery_sender, delivery_receiver) = mpsc::unbounded_channel();
472 Self::connect_stream_with_sink(transport, delivery_sender, Some(delivery_receiver))
473 }
474
475 pub fn connect_display_stream<T>(transport: T) -> Self
479 where
480 T: Transport + 'static,
481 {
482 let (sender, receiver) = mpsc::unbounded_channel();
483 Self::connect_stream_with_sink(transport, DisplayOnlySink(sender), Some(receiver))
484 }
485
486 pub fn connect_with_event_sink<T, E>(transport: T, sink: E) -> Self
488 where
489 T: Transport + 'static,
490 E: ClientEventSink,
491 {
492 Self::connect_stream_with_sink(transport, sink, None)
493 }
494
495 fn connect_stream_with_sink<T, E>(
496 transport: T,
497 sink: E,
498 delivery_receiver: Option<mpsc::UnboundedReceiver<ClientDelivery>>,
499 ) -> Self
500 where
501 T: Transport + 'static,
502 E: ClientEventSink,
503 {
504 let (commands, command_rx) = mpsc::channel(COMMAND_QUEUE_CAPACITY);
505 let deliveries = Arc::new(StdMutex::new(delivery_receiver));
506 let subscriptions = Arc::new(Mutex::new(HashMap::new()));
507 let (recovery_sender, recovery_receiver) = mpsc::unbounded_channel();
508 let recovery = Arc::new(RecoveryQueue {
509 sender: recovery_sender,
510 state: StdMutex::new(RecoveryQueueState::default()),
511 });
512 let (control_sender, control_rx) = mpsc::unbounded_channel();
513 let pending_command_count = Arc::new(AtomicUsize::new(0));
514 let shutdown = Arc::new(ClientShutdown(tokio_util::sync::CancellationToken::new()));
515 tokio::spawn(connection_task(
516 transport,
517 ConnectionTask {
518 commands: command_rx,
519 controls: control_rx,
520 deliveries: Arc::new(sink),
521 subscriptions: subscriptions.clone(),
522 recovery: recovery.clone(),
523 pending_command_count: pending_command_count.clone(),
524 shutdown: shutdown.0.clone(),
525 },
526 ));
527 let client = Self {
528 commands,
529 deliveries,
530 subscriptions,
531 recovery: recovery.clone(),
532 shutdown,
533 #[cfg(test)]
534 pending_command_count,
535 };
536 spawn_recovery_worker(
537 client.commands.clone(),
538 client.subscriptions.clone(),
539 recovery_receiver,
540 recovery,
541 control_sender,
542 REPLAY_RESPONSE_TIMEOUT,
543 );
544 client
545 }
546
547 pub async fn handshake(&self) -> Result<ServerHello, ClientError> {
549 let hello: ServerHello = self
550 .call(
551 "handshake",
552 &ClientHello {
553 protocol_version: ProtocolVersion::current(),
554 },
555 )
556 .await?;
557 Ok(hello)
558 }
559
560 #[must_use]
563 pub fn subscribe_deliveries(&self) -> Option<mpsc::UnboundedReceiver<ClientDelivery>> {
564 self.deliveries
565 .lock()
566 .expect("delivery receiver lock")
567 .take()
568 }
569
570 pub fn shutdown(&self) {
572 self.shutdown.0.cancel();
573 }
574
575 pub async fn create_session(
576 &self,
577 params: SessionCreateParams,
578 ) -> Result<SessionCreateResult, ClientError> {
579 self.call("session.create", ¶ms).await
580 }
581
582 pub async fn list_sessions(
583 &self,
584 params: SessionListParams,
585 ) -> Result<SessionListResult, ClientError> {
586 self.call("session.list", ¶ms).await
587 }
588
589 pub async fn get_session(
590 &self,
591 params: SessionGetParams,
592 ) -> Result<SessionGetResult, ClientError> {
593 self.call("session.get", ¶ms).await
594 }
595
596 pub async fn get_session_goal(
597 &self,
598 params: SessionGoalGetParams,
599 ) -> Result<SessionGoalGetResult, ClientError> {
600 self.call(crate::SESSION_GOAL_GET_METHOD, ¶ms).await
601 }
602
603 pub async fn set_session_goal(
604 &self,
605 params: SessionGoalSetParams,
606 ) -> Result<SessionGoalSetResult, ClientError> {
607 self.call(crate::SESSION_GOAL_SET_METHOD, ¶ms).await
608 }
609
610 pub async fn change_session_goal_lifecycle(
611 &self,
612 params: SessionGoalLifecycleParams,
613 ) -> Result<SessionGoalLifecycleResult, ClientError> {
614 self.call(crate::SESSION_GOAL_LIFECYCLE_METHOD, ¶ms)
615 .await
616 }
617
618 pub async fn session_producers(
619 &self,
620 params: SessionProducersParams,
621 ) -> Result<SessionProducersResult, ClientError> {
622 self.call(crate::SESSION_PRODUCERS_METHOD, ¶ms).await
623 }
624
625 pub async fn session_usage(
626 &self,
627 params: SessionUsageParams,
628 ) -> Result<SessionUsageResult, ClientError> {
629 self.call("session.usage", ¶ms).await
630 }
631
632 pub async fn session_tree_usage(
633 &self,
634 params: SessionUsageParams,
635 ) -> Result<SessionTreeUsageResult, ClientError> {
636 self.call("session.tree_usage", ¶ms).await
637 }
638
639 pub async fn agent_usage(
640 &self,
641 params: AgentUsageParams,
642 ) -> Result<AgentUsageResult, ClientError> {
643 self.call("agent.usage", ¶ms).await
644 }
645
646 pub async fn global_usage(&self) -> Result<GlobalUsageResult, ClientError> {
647 self.call("usage.global", &GlobalUsageParams {}).await
648 }
649
650 pub async fn session_children(
651 &self,
652 params: SessionChildrenParams,
653 ) -> Result<SessionChildrenResult, ClientError> {
654 self.call("session.children", ¶ms).await
655 }
656
657 pub async fn session_tree(
658 &self,
659 params: SessionTreeParams,
660 ) -> Result<SessionTreeResult, ClientError> {
661 self.call("session.tree", ¶ms).await
662 }
663
664 pub async fn resume_session(
665 &self,
666 params: SessionResumeParams,
667 ) -> Result<SessionResumeResult, ClientError> {
668 self.call("session.resume", ¶ms).await
669 }
670
671 pub async fn rename_session(
672 &self,
673 params: SessionRenameParams,
674 ) -> Result<SessionRenameResult, ClientError> {
675 self.call("session.rename", ¶ms).await
676 }
677
678 pub async fn set_permission_mode(
679 &self,
680 params: SessionSetPermissionModeParams,
681 ) -> Result<SessionSetPermissionModeResult, ClientError> {
682 self.call("session.set_permission_mode", ¶ms).await
683 }
684
685 pub async fn get_session_permissions(
686 &self,
687 params: SessionPermissionGetParams,
688 ) -> Result<SessionPermissionGetResult, ClientError> {
689 self.call("session.permission.get", ¶ms).await
690 }
691
692 pub async fn set_session_permission(
693 &self,
694 params: SessionPermissionSetParams,
695 ) -> Result<SessionPermissionMutationResult, ClientError> {
696 self.call("session.permission.set", ¶ms).await
697 }
698
699 pub async fn clear_session_permission(
700 &self,
701 params: SessionPermissionClearParams,
702 ) -> Result<SessionPermissionMutationResult, ClientError> {
703 self.call("session.permission.clear", ¶ms).await
704 }
705
706 pub async fn list_skills(
707 &self,
708 params: SkillsListParams,
709 ) -> Result<SkillsListResult, ClientError> {
710 self.call("skills.list", ¶ms).await
711 }
712
713 pub async fn get_skill(&self, params: SkillsGetParams) -> Result<SkillsGetResult, ClientError> {
714 self.call("skills.get", ¶ms).await
715 }
716
717 pub async fn compact_session(
718 &self,
719 params: SessionCompactParams,
720 ) -> Result<SessionCompactResult, ClientError> {
721 self.call("session.compact", ¶ms).await
722 }
723
724 pub async fn revert_session(
725 &self,
726 params: SessionRevertParams,
727 ) -> Result<SessionRevertResult, ClientError> {
728 self.call("session.revert", ¶ms).await
729 }
730
731 pub async fn fork_session(
732 &self,
733 params: SessionForkParams,
734 ) -> Result<SessionForkResult, ClientError> {
735 self.call("session.fork", ¶ms).await
736 }
737
738 pub async fn start_run(&self, params: RunStartParams) -> Result<RunStartResult, ClientError> {
739 self.call("run.start", ¶ms).await
740 }
741
742 pub async fn steer_run(&self, params: RunSteerParams) -> Result<RunSteerResult, ClientError> {
743 self.call("run.steer", ¶ms).await
744 }
745
746 pub async fn recall_steer(
747 &self,
748 params: RunRecallSteerParams,
749 ) -> Result<RunRecallSteerResult, ClientError> {
750 self.call("run.recall_steer", ¶ms).await
751 }
752
753 pub async fn cancel_run(
754 &self,
755 params: RunCancelParams,
756 ) -> Result<RunCancelResult, ClientError> {
757 self.call("run.cancel", ¶ms).await
758 }
759
760 pub async fn tool_stdin(
761 &self,
762 params: RunToolStdinParams,
763 ) -> Result<RunToolStdinResult, ClientError> {
764 self.call("run.tool_stdin", ¶ms).await
765 }
766
767 pub async fn respond_approval(
768 &self,
769 params: ApprovalRespondParams,
770 ) -> Result<ApprovalRespondResult, ClientError> {
771 self.call("approval.respond", ¶ms).await
772 }
773
774 pub async fn list_approvals(
775 &self,
776 params: ApprovalListParams,
777 ) -> Result<ApprovalListResult, ClientError> {
778 self.call("approval.list", ¶ms).await
779 }
780
781 pub async fn begin_mcp_auth(
782 &self,
783 params: McpAuthBeginParams,
784 ) -> Result<McpAuthBeginResult, ClientError> {
785 self.call("mcp.auth.begin", ¶ms).await
786 }
787
788 pub async fn cancel_mcp_auth(
789 &self,
790 params: McpAuthCancelParams,
791 ) -> Result<McpAuthCancelResult, ClientError> {
792 self.call("mcp.auth.cancel", ¶ms).await
793 }
794
795 pub async fn list_mcp_servers(&self) -> Result<McpServerListResult, ClientError> {
796 self.call("mcp.server.list", &McpServerListParams {}).await
797 }
798
799 pub async fn add_mcp_server(
800 &self,
801 params: McpServerAddParams,
802 ) -> Result<McpServerMutationResult, ClientError> {
803 self.call("mcp.server.add", ¶ms).await
804 }
805
806 pub async fn edit_mcp_server(
807 &self,
808 params: McpServerEditParams,
809 ) -> Result<McpServerMutationResult, ClientError> {
810 self.call("mcp.server.edit", ¶ms).await
811 }
812
813 pub async fn remove_mcp_server(
814 &self,
815 params: McpServerNameParams,
816 ) -> Result<McpServerMutationResult, ClientError> {
817 self.call("mcp.server.remove", ¶ms).await
818 }
819
820 pub async fn set_mcp_server_enabled(
821 &self,
822 params: McpServerSetEnabledParams,
823 ) -> Result<McpServerMutationResult, ClientError> {
824 self.call("mcp.server.set_enabled", ¶ms).await
825 }
826
827 pub async fn reconnect_mcp_server(
828 &self,
829 params: McpServerNameParams,
830 ) -> Result<McpServerMutationResult, ClientError> {
831 self.call("mcp.server.reconnect", ¶ms).await
832 }
833
834 pub async fn persist_mcp_server(
835 &self,
836 params: McpServerPersistParams,
837 ) -> Result<McpServerMutationResult, ClientError> {
838 self.call("mcp.server.persist", ¶ms).await
839 }
840
841 pub async fn subscribe_events(
844 &self,
845 session_id: SessionId,
846 cursor: Option<u64>,
847 ) -> Result<(), ClientError> {
848 let cursor = cursor.unwrap_or(0);
849 let request = self
850 .prepare_subscription(session_id, cursor, false, cursor == 0)
851 .await?;
852 if let Err(error) = request_replay(&self.commands, request, cursor).await {
853 self.abort_replay(session_id).await;
854 Self::schedule_recovery_queue(&self.recovery, cursor == 0, Some(session_id));
855 return Err(error);
856 }
857 Ok(())
858 }
859
860 pub fn recover_session(&self, session_id: SessionId, full_replay: bool) {
863 Self::schedule_recovery_queue(&self.recovery, full_replay, Some(session_id));
864 }
865
866 fn schedule_recovery_queue(
867 recovery: &Arc<RecoveryQueue>,
868 full_replay: bool,
869 session_id: Option<SessionId>,
870 ) {
871 let mut state = recovery.state.lock().expect("recovery queue lock");
872 if state.queued {
873 state.follow_up = true;
874 state.follow_up_full |= full_replay;
875 if state.follow_up_session != session_id {
876 state.follow_up_session = None;
877 }
878 return;
879 }
880 state.queued = true;
881 state.follow_up_session = session_id;
882 if recovery.sender.send((full_replay, session_id)).is_err() {
883 state.queued = false;
884 }
885 }
886
887 async fn prepare_subscription(
888 &self,
889 session_id: SessionId,
890 cursor: u64,
891 recovering: bool,
892 rebuild: bool,
893 ) -> Result<ReplayRequest, ClientError> {
894 prepare_subscription(&self.subscriptions, session_id, cursor, recovering, rebuild).await
895 }
896
897 async fn abort_replay(&self, session_id: SessionId) {
898 let mut subscriptions = self.subscriptions.lock().await;
899 if let Some(subscription) = subscriptions.get_mut(&session_id) {
900 subscription.cursor = subscription.rollback_cursor;
901 subscription.fetching = false;
902 subscription.awaiting_snapshots.clear();
903 subscription.replay_tools.clear();
904 }
905 }
906
907 pub async fn call<P, R>(&self, method: &str, params: &P) -> Result<R, ClientError>
908 where
909 P: Serialize + ?Sized,
910 R: DeserializeOwned,
911 {
912 let value = send_command(&self.commands, method, params, None, false).await?;
913 Ok(serde_json::from_value(value)?)
914 }
915
916 pub async fn call_sensitive<P, R>(&self, method: &str, params: P) -> Result<R, ClientError>
921 where
922 P: Serialize,
923 R: DeserializeOwned,
924 {
925 let value = SensitiveJson(serde_json::to_value(¶ms)?);
926 drop(params);
927 let result = send_sensitive_command(&self.commands, method, value).await?;
928 Ok(serde_json::from_value(result)?)
929 }
930
931 #[cfg(test)]
932 fn pending_command_count(&self) -> usize {
933 self.pending_command_count.load(Ordering::Relaxed)
934 }
935}
936
937#[async_trait::async_trait]
938impl ClientProtocol for Client {
939 async fn handshake(&self) -> Result<ServerHello, ClientError> {
940 Client::handshake(self).await
941 }
942
943 async fn create_session(
944 &self,
945 params: SessionCreateParams,
946 ) -> Result<SessionCreateResult, ClientError> {
947 Client::create_session(self, params).await
948 }
949 async fn list_sessions(
950 &self,
951 params: SessionListParams,
952 ) -> Result<SessionListResult, ClientError> {
953 Client::list_sessions(self, params).await
954 }
955 async fn get_session(&self, params: SessionGetParams) -> Result<SessionGetResult, ClientError> {
956 Client::get_session(self, params).await
957 }
958 async fn get_session_goal(
959 &self,
960 params: SessionGoalGetParams,
961 ) -> Result<SessionGoalGetResult, ClientError> {
962 Client::get_session_goal(self, params).await
963 }
964 async fn set_session_goal(
965 &self,
966 params: SessionGoalSetParams,
967 ) -> Result<SessionGoalSetResult, ClientError> {
968 Client::set_session_goal(self, params).await
969 }
970 async fn change_session_goal_lifecycle(
971 &self,
972 params: SessionGoalLifecycleParams,
973 ) -> Result<SessionGoalLifecycleResult, ClientError> {
974 Client::change_session_goal_lifecycle(self, params).await
975 }
976 async fn session_producers(
977 &self,
978 params: SessionProducersParams,
979 ) -> Result<SessionProducersResult, ClientError> {
980 Client::session_producers(self, params).await
981 }
982 async fn session_usage(
983 &self,
984 params: SessionUsageParams,
985 ) -> Result<SessionUsageResult, ClientError> {
986 Client::session_usage(self, params).await
987 }
988 async fn session_tree_usage(
989 &self,
990 params: SessionUsageParams,
991 ) -> Result<SessionTreeUsageResult, ClientError> {
992 Client::session_tree_usage(self, params).await
993 }
994 async fn agent_usage(&self, params: AgentUsageParams) -> Result<AgentUsageResult, ClientError> {
995 Client::agent_usage(self, params).await
996 }
997 async fn global_usage(&self) -> Result<GlobalUsageResult, ClientError> {
998 Client::global_usage(self).await
999 }
1000 async fn session_children(
1001 &self,
1002 params: SessionChildrenParams,
1003 ) -> Result<SessionChildrenResult, ClientError> {
1004 Client::session_children(self, params).await
1005 }
1006 async fn session_tree(
1007 &self,
1008 params: SessionTreeParams,
1009 ) -> Result<SessionTreeResult, ClientError> {
1010 Client::session_tree(self, params).await
1011 }
1012 async fn resume_session(
1013 &self,
1014 params: SessionResumeParams,
1015 ) -> Result<SessionResumeResult, ClientError> {
1016 Client::resume_session(self, params).await
1017 }
1018 async fn rename_session(
1019 &self,
1020 params: SessionRenameParams,
1021 ) -> Result<SessionRenameResult, ClientError> {
1022 Client::rename_session(self, params).await
1023 }
1024 async fn set_permission_mode(
1025 &self,
1026 params: SessionSetPermissionModeParams,
1027 ) -> Result<SessionSetPermissionModeResult, ClientError> {
1028 Client::set_permission_mode(self, params).await
1029 }
1030 async fn get_session_permissions(
1031 &self,
1032 params: SessionPermissionGetParams,
1033 ) -> Result<SessionPermissionGetResult, ClientError> {
1034 Client::get_session_permissions(self, params).await
1035 }
1036 async fn set_session_permission(
1037 &self,
1038 params: SessionPermissionSetParams,
1039 ) -> Result<SessionPermissionMutationResult, ClientError> {
1040 Client::set_session_permission(self, params).await
1041 }
1042 async fn clear_session_permission(
1043 &self,
1044 params: SessionPermissionClearParams,
1045 ) -> Result<SessionPermissionMutationResult, ClientError> {
1046 Client::clear_session_permission(self, params).await
1047 }
1048 async fn list_skills(&self, params: SkillsListParams) -> Result<SkillsListResult, ClientError> {
1049 Client::list_skills(self, params).await
1050 }
1051 async fn get_skill(&self, params: SkillsGetParams) -> Result<SkillsGetResult, ClientError> {
1052 Client::get_skill(self, params).await
1053 }
1054 async fn compact_session(
1055 &self,
1056 params: SessionCompactParams,
1057 ) -> Result<SessionCompactResult, ClientError> {
1058 Client::compact_session(self, params).await
1059 }
1060 async fn revert_session(
1061 &self,
1062 params: SessionRevertParams,
1063 ) -> Result<SessionRevertResult, ClientError> {
1064 Client::revert_session(self, params).await
1065 }
1066 async fn fork_session(
1067 &self,
1068 params: SessionForkParams,
1069 ) -> Result<SessionForkResult, ClientError> {
1070 Client::fork_session(self, params).await
1071 }
1072 async fn start_run(&self, params: RunStartParams) -> Result<RunStartResult, ClientError> {
1073 Client::start_run(self, params).await
1074 }
1075 async fn steer_run(&self, params: RunSteerParams) -> Result<RunSteerResult, ClientError> {
1076 Client::steer_run(self, params).await
1077 }
1078 async fn recall_steer(
1079 &self,
1080 params: RunRecallSteerParams,
1081 ) -> Result<RunRecallSteerResult, ClientError> {
1082 Client::recall_steer(self, params).await
1083 }
1084 async fn cancel_run(&self, params: RunCancelParams) -> Result<RunCancelResult, ClientError> {
1085 Client::cancel_run(self, params).await
1086 }
1087 async fn tool_stdin(
1088 &self,
1089 params: RunToolStdinParams,
1090 ) -> Result<RunToolStdinResult, ClientError> {
1091 Client::tool_stdin(self, params).await
1092 }
1093 async fn respond_approval(
1094 &self,
1095 params: ApprovalRespondParams,
1096 ) -> Result<ApprovalRespondResult, ClientError> {
1097 Client::respond_approval(self, params).await
1098 }
1099 async fn list_approvals(
1100 &self,
1101 params: ApprovalListParams,
1102 ) -> Result<ApprovalListResult, ClientError> {
1103 Client::list_approvals(self, params).await
1104 }
1105 async fn begin_mcp_auth(
1106 &self,
1107 params: McpAuthBeginParams,
1108 ) -> Result<McpAuthBeginResult, ClientError> {
1109 Client::begin_mcp_auth(self, params).await
1110 }
1111 async fn cancel_mcp_auth(
1112 &self,
1113 params: McpAuthCancelParams,
1114 ) -> Result<McpAuthCancelResult, ClientError> {
1115 Client::cancel_mcp_auth(self, params).await
1116 }
1117 async fn list_mcp_servers(&self) -> Result<McpServerListResult, ClientError> {
1118 Client::list_mcp_servers(self).await
1119 }
1120 async fn add_mcp_server(
1121 &self,
1122 params: McpServerAddParams,
1123 ) -> Result<McpServerMutationResult, ClientError> {
1124 Client::add_mcp_server(self, params).await
1125 }
1126 async fn edit_mcp_server(
1127 &self,
1128 params: McpServerEditParams,
1129 ) -> Result<McpServerMutationResult, ClientError> {
1130 Client::edit_mcp_server(self, params).await
1131 }
1132 async fn remove_mcp_server(
1133 &self,
1134 params: McpServerNameParams,
1135 ) -> Result<McpServerMutationResult, ClientError> {
1136 Client::remove_mcp_server(self, params).await
1137 }
1138 async fn set_mcp_server_enabled(
1139 &self,
1140 params: McpServerSetEnabledParams,
1141 ) -> Result<McpServerMutationResult, ClientError> {
1142 Client::set_mcp_server_enabled(self, params).await
1143 }
1144 async fn reconnect_mcp_server(
1145 &self,
1146 params: McpServerNameParams,
1147 ) -> Result<McpServerMutationResult, ClientError> {
1148 Client::reconnect_mcp_server(self, params).await
1149 }
1150 async fn persist_mcp_server(
1151 &self,
1152 params: McpServerPersistParams,
1153 ) -> Result<McpServerMutationResult, ClientError> {
1154 Client::persist_mcp_server(self, params).await
1155 }
1156 async fn subscribe_events(
1157 &self,
1158 session_id: SessionId,
1159 cursor: Option<u64>,
1160 ) -> Result<(), ClientError> {
1161 Client::subscribe_events(self, session_id, cursor).await
1162 }
1163 async fn runtime_snapshot(&self) -> Result<RuntimeSnapshotResult, ClientError> {
1164 Client::runtime_snapshot(self).await
1165 }
1166 async fn connect_provider(
1167 &self,
1168 params: ProviderConnectParams,
1169 ) -> Result<ProviderConnectResult, ClientError> {
1170 Client::connect_provider(self, params).await
1171 }
1172 async fn disconnect_provider(
1173 &self,
1174 params: ProviderDisconnectParams,
1175 ) -> Result<ProviderDisconnectResult, ClientError> {
1176 Client::disconnect_provider(self, params).await
1177 }
1178
1179 fn recover_session(&self, session_id: SessionId, full_replay: bool) {
1180 Client::recover_session(self, session_id, full_replay);
1181 }
1182
1183 fn shutdown(&self) {
1184 Client::shutdown(self);
1185 }
1186}
1187
1188#[async_trait::async_trait]
1189impl<T> ClientProtocol for T
1190where
1191 T: std::ops::Deref<Target = Client> + Send + Sync,
1192{
1193 async fn handshake(&self) -> Result<ServerHello, ClientError> {
1194 self.deref().handshake().await
1195 }
1196 async fn create_session(
1197 &self,
1198 params: SessionCreateParams,
1199 ) -> Result<SessionCreateResult, ClientError> {
1200 self.deref().create_session(params).await
1201 }
1202 async fn list_sessions(
1203 &self,
1204 params: SessionListParams,
1205 ) -> Result<SessionListResult, ClientError> {
1206 self.deref().list_sessions(params).await
1207 }
1208 async fn get_session(&self, params: SessionGetParams) -> Result<SessionGetResult, ClientError> {
1209 self.deref().get_session(params).await
1210 }
1211 async fn get_session_goal(
1212 &self,
1213 params: SessionGoalGetParams,
1214 ) -> Result<SessionGoalGetResult, ClientError> {
1215 self.deref().get_session_goal(params).await
1216 }
1217 async fn set_session_goal(
1218 &self,
1219 params: SessionGoalSetParams,
1220 ) -> Result<SessionGoalSetResult, ClientError> {
1221 self.deref().set_session_goal(params).await
1222 }
1223 async fn change_session_goal_lifecycle(
1224 &self,
1225 params: SessionGoalLifecycleParams,
1226 ) -> Result<SessionGoalLifecycleResult, ClientError> {
1227 self.deref().change_session_goal_lifecycle(params).await
1228 }
1229 async fn session_producers(
1230 &self,
1231 params: SessionProducersParams,
1232 ) -> Result<SessionProducersResult, ClientError> {
1233 self.deref().session_producers(params).await
1234 }
1235 async fn session_usage(
1236 &self,
1237 params: SessionUsageParams,
1238 ) -> Result<SessionUsageResult, ClientError> {
1239 self.deref().session_usage(params).await
1240 }
1241 async fn session_tree_usage(
1242 &self,
1243 params: SessionUsageParams,
1244 ) -> Result<SessionTreeUsageResult, ClientError> {
1245 self.deref().session_tree_usage(params).await
1246 }
1247 async fn agent_usage(&self, params: AgentUsageParams) -> Result<AgentUsageResult, ClientError> {
1248 self.deref().agent_usage(params).await
1249 }
1250 async fn global_usage(&self) -> Result<GlobalUsageResult, ClientError> {
1251 self.deref().global_usage().await
1252 }
1253 async fn session_children(
1254 &self,
1255 params: SessionChildrenParams,
1256 ) -> Result<SessionChildrenResult, ClientError> {
1257 self.deref().session_children(params).await
1258 }
1259 async fn session_tree(
1260 &self,
1261 params: SessionTreeParams,
1262 ) -> Result<SessionTreeResult, ClientError> {
1263 self.deref().session_tree(params).await
1264 }
1265 async fn resume_session(
1266 &self,
1267 params: SessionResumeParams,
1268 ) -> Result<SessionResumeResult, ClientError> {
1269 self.deref().resume_session(params).await
1270 }
1271 async fn rename_session(
1272 &self,
1273 params: SessionRenameParams,
1274 ) -> Result<SessionRenameResult, ClientError> {
1275 self.deref().rename_session(params).await
1276 }
1277 async fn set_permission_mode(
1278 &self,
1279 params: SessionSetPermissionModeParams,
1280 ) -> Result<SessionSetPermissionModeResult, ClientError> {
1281 self.deref().set_permission_mode(params).await
1282 }
1283 async fn get_session_permissions(
1284 &self,
1285 params: SessionPermissionGetParams,
1286 ) -> Result<SessionPermissionGetResult, ClientError> {
1287 self.deref().get_session_permissions(params).await
1288 }
1289 async fn set_session_permission(
1290 &self,
1291 params: SessionPermissionSetParams,
1292 ) -> Result<SessionPermissionMutationResult, ClientError> {
1293 self.deref().set_session_permission(params).await
1294 }
1295 async fn clear_session_permission(
1296 &self,
1297 params: SessionPermissionClearParams,
1298 ) -> Result<SessionPermissionMutationResult, ClientError> {
1299 self.deref().clear_session_permission(params).await
1300 }
1301 async fn list_skills(&self, params: SkillsListParams) -> Result<SkillsListResult, ClientError> {
1302 self.deref().list_skills(params).await
1303 }
1304 async fn get_skill(&self, params: SkillsGetParams) -> Result<SkillsGetResult, ClientError> {
1305 self.deref().get_skill(params).await
1306 }
1307 async fn compact_session(
1308 &self,
1309 params: SessionCompactParams,
1310 ) -> Result<SessionCompactResult, ClientError> {
1311 self.deref().compact_session(params).await
1312 }
1313 async fn revert_session(
1314 &self,
1315 params: SessionRevertParams,
1316 ) -> Result<SessionRevertResult, ClientError> {
1317 self.deref().revert_session(params).await
1318 }
1319 async fn fork_session(
1320 &self,
1321 params: SessionForkParams,
1322 ) -> Result<SessionForkResult, ClientError> {
1323 self.deref().fork_session(params).await
1324 }
1325 async fn start_run(&self, params: RunStartParams) -> Result<RunStartResult, ClientError> {
1326 self.deref().start_run(params).await
1327 }
1328 async fn steer_run(&self, params: RunSteerParams) -> Result<RunSteerResult, ClientError> {
1329 self.deref().steer_run(params).await
1330 }
1331 async fn recall_steer(
1332 &self,
1333 params: RunRecallSteerParams,
1334 ) -> Result<RunRecallSteerResult, ClientError> {
1335 self.deref().recall_steer(params).await
1336 }
1337 async fn cancel_run(&self, params: RunCancelParams) -> Result<RunCancelResult, ClientError> {
1338 self.deref().cancel_run(params).await
1339 }
1340 async fn tool_stdin(
1341 &self,
1342 params: RunToolStdinParams,
1343 ) -> Result<RunToolStdinResult, ClientError> {
1344 self.deref().tool_stdin(params).await
1345 }
1346 async fn respond_approval(
1347 &self,
1348 params: ApprovalRespondParams,
1349 ) -> Result<ApprovalRespondResult, ClientError> {
1350 self.deref().respond_approval(params).await
1351 }
1352 async fn list_approvals(
1353 &self,
1354 params: ApprovalListParams,
1355 ) -> Result<ApprovalListResult, ClientError> {
1356 self.deref().list_approvals(params).await
1357 }
1358 async fn begin_mcp_auth(
1359 &self,
1360 params: McpAuthBeginParams,
1361 ) -> Result<McpAuthBeginResult, ClientError> {
1362 self.deref().begin_mcp_auth(params).await
1363 }
1364 async fn cancel_mcp_auth(
1365 &self,
1366 params: McpAuthCancelParams,
1367 ) -> Result<McpAuthCancelResult, ClientError> {
1368 self.deref().cancel_mcp_auth(params).await
1369 }
1370 async fn list_mcp_servers(&self) -> Result<McpServerListResult, ClientError> {
1371 self.deref().list_mcp_servers().await
1372 }
1373 async fn add_mcp_server(
1374 &self,
1375 params: McpServerAddParams,
1376 ) -> Result<McpServerMutationResult, ClientError> {
1377 self.deref().add_mcp_server(params).await
1378 }
1379 async fn edit_mcp_server(
1380 &self,
1381 params: McpServerEditParams,
1382 ) -> Result<McpServerMutationResult, ClientError> {
1383 self.deref().edit_mcp_server(params).await
1384 }
1385 async fn remove_mcp_server(
1386 &self,
1387 params: McpServerNameParams,
1388 ) -> Result<McpServerMutationResult, ClientError> {
1389 self.deref().remove_mcp_server(params).await
1390 }
1391 async fn set_mcp_server_enabled(
1392 &self,
1393 params: McpServerSetEnabledParams,
1394 ) -> Result<McpServerMutationResult, ClientError> {
1395 self.deref().set_mcp_server_enabled(params).await
1396 }
1397 async fn reconnect_mcp_server(
1398 &self,
1399 params: McpServerNameParams,
1400 ) -> Result<McpServerMutationResult, ClientError> {
1401 self.deref().reconnect_mcp_server(params).await
1402 }
1403 async fn persist_mcp_server(
1404 &self,
1405 params: McpServerPersistParams,
1406 ) -> Result<McpServerMutationResult, ClientError> {
1407 self.deref().persist_mcp_server(params).await
1408 }
1409 async fn subscribe_events(
1410 &self,
1411 session_id: SessionId,
1412 cursor: Option<u64>,
1413 ) -> Result<(), ClientError> {
1414 self.deref().subscribe_events(session_id, cursor).await
1415 }
1416 async fn runtime_snapshot(&self) -> Result<RuntimeSnapshotResult, ClientError> {
1417 self.deref().runtime_snapshot().await
1418 }
1419 async fn connect_provider(
1420 &self,
1421 params: ProviderConnectParams,
1422 ) -> Result<ProviderConnectResult, ClientError> {
1423 self.deref().connect_provider(params).await
1424 }
1425 async fn disconnect_provider(
1426 &self,
1427 params: ProviderDisconnectParams,
1428 ) -> Result<ProviderDisconnectResult, ClientError> {
1429 self.deref().disconnect_provider(params).await
1430 }
1431 fn recover_session(&self, session_id: SessionId, full_replay: bool) {
1432 self.deref().recover_session(session_id, full_replay);
1433 }
1434 fn shutdown(&self) {
1435 self.deref().shutdown();
1436 }
1437}
1438
1439async fn prepare_subscription(
1440 subscriptions: &Arc<Mutex<HashMap<SessionId, Subscription>>>,
1441 session_id: SessionId,
1442 cursor: u64,
1443 recovering: bool,
1444 rebuild: bool,
1445) -> Result<ReplayRequest, ClientError> {
1446 let mut subscriptions = subscriptions.lock().await;
1447 let subscription = subscriptions.entry(session_id).or_default();
1448 if subscription.fetching {
1449 if recovering {
1450 subscription.recovery_requested =
1451 Some(subscription.recovery_requested.unwrap_or(false) || rebuild);
1452 }
1453 return Err(ClientError::ReplayInProgress);
1454 }
1455 subscription.rollback_cursor = subscription.cursor;
1456 subscription.cursor = cursor;
1457 subscription.fetching = true;
1458 subscription.rebuild = rebuild;
1459 if rebuild && recovering {
1460 subscription.generation += 1;
1461 }
1462 subscription.next_attempt += 1;
1463 subscription.active_attempt = subscription.next_attempt;
1464 Ok(ReplayRequest {
1465 session_id,
1466 generation: subscription.generation,
1467 rebuild,
1468 attempt: subscription.active_attempt,
1469 })
1470}
1471
1472async fn request_replay(
1473 commands: &mpsc::Sender<Command>,
1474 replay: ReplayRequest,
1475 cursor: u64,
1476) -> Result<(), ClientError> {
1477 request_replay_with_timeout(commands, replay, cursor, REPLAY_RESPONSE_TIMEOUT).await
1478}
1479
1480async fn request_replay_with_timeout(
1481 commands: &mpsc::Sender<Command>,
1482 replay: ReplayRequest,
1483 cursor: u64,
1484 timeout: Duration,
1485) -> Result<(), ClientError> {
1486 let params = EventsSubscribeParams {
1487 session_id: replay.session_id,
1488 cursor: Some(cursor),
1489 };
1490 let _ = tokio::time::timeout(
1491 timeout,
1492 send_command(commands, "events.subscribe", ¶ms, Some(replay), false),
1493 )
1494 .await
1495 .map_err(|_| ClientError::ReplayTimedOut)??;
1496 Ok(())
1497}
1498
1499async fn send_command<P>(
1500 commands: &mpsc::Sender<Command>,
1501 method: &str,
1502 params: &P,
1503 replay: Option<ReplayRequest>,
1504 sensitive: bool,
1505) -> Result<Value, ClientError>
1506where
1507 P: Serialize + ?Sized,
1508{
1509 let (response, receiver) = oneshot::channel();
1510 let id = NEXT_REQUEST_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1511 let params = SerializedParams {
1512 value: serde_json::to_value(params)?,
1513 sensitive,
1514 };
1515 commands
1516 .send(Command {
1517 id,
1518 method: method.to_owned(),
1519 params,
1520 replay,
1521 response,
1522 })
1523 .await
1524 .map_err(|_| ClientError::Closed)?;
1525 receiver.await.map_err(|_| ClientError::Closed)?
1526}
1527
1528pub(in crate::session::client) async fn send_sensitive_command(
1529 commands: &mpsc::Sender<Command>,
1530 method: &str,
1531 mut params: SensitiveJson,
1532) -> Result<Value, ClientError> {
1533 let (response, receiver) = oneshot::channel();
1534 let id = NEXT_REQUEST_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1535 let params = SerializedParams {
1536 value: params.take(),
1537 sensitive: true,
1538 };
1539 commands
1540 .send(Command {
1541 id,
1542 method: method.to_owned(),
1543 params,
1544 replay: None,
1545 response,
1546 })
1547 .await
1548 .map_err(|_| ClientError::Closed)?;
1549 receiver.await.map_err(|_| ClientError::Closed)?
1550}
1551
1552fn zeroize_json(value: &mut Value) {
1553 match value {
1554 Value::String(value) => value.zeroize(),
1555 Value::Array(values) => values.iter_mut().for_each(zeroize_json),
1556 Value::Object(values) => values.values_mut().for_each(zeroize_json),
1557 Value::Null | Value::Bool(_) | Value::Number(_) => {}
1558 }
1559}
1560
1561fn spawn_recovery_worker(
1562 commands: mpsc::Sender<Command>,
1563 subscriptions: Arc<Mutex<HashMap<SessionId, Subscription>>>,
1564 mut receiver: mpsc::UnboundedReceiver<(bool, Option<SessionId>)>,
1565 recovery: Arc<RecoveryQueue>,
1566 controls: mpsc::UnboundedSender<ConnectionControl>,
1567 replay_timeout: Duration,
1568) {
1569 let weak_recovery = Arc::downgrade(&recovery);
1570 tokio::spawn(async move {
1571 while let Some((mut full_replay, mut only_session)) = receiver.recv().await {
1572 loop {
1573 for attempt in 0..RECOVERY_ATTEMPTS {
1574 if weak_recovery.upgrade().is_none() {
1575 return;
1576 }
1577 match recover_all(
1578 &commands,
1579 &subscriptions,
1580 full_replay,
1581 only_session,
1582 replay_timeout,
1583 )
1584 .await
1585 {
1586 Ok(()) => break,
1587 Err(error) => {
1588 if attempt + 1 == RECOVERY_ATTEMPTS {
1589 let _ = controls.send(ConnectionControl::RecoveryFailed {
1590 session_id: only_session,
1591 error: error.to_string(),
1592 });
1593 break;
1594 }
1595 let delay = 50_u64.saturating_mul(1_u64 << attempt).min(5_000);
1596 tokio::time::sleep(Duration::from_millis(delay)).await;
1597 }
1598 }
1599 }
1600 let next = {
1601 let Some(recovery) = weak_recovery.upgrade() else {
1602 return;
1603 };
1604 let mut state = recovery.state.lock().expect("recovery queue lock");
1605 if state.follow_up {
1606 state.follow_up = false;
1607 Some((
1608 std::mem::take(&mut state.follow_up_full),
1609 state.follow_up_session.take(),
1610 ))
1611 } else {
1612 state.queued = false;
1613 None
1614 }
1615 };
1616 let Some(next) = next else { break };
1617 (full_replay, only_session) = next;
1618 }
1619 }
1620 });
1621}
1622
1623async fn recover_all(
1624 commands: &mpsc::Sender<Command>,
1625 subscriptions: &Arc<Mutex<HashMap<SessionId, Subscription>>>,
1626 full_replay: bool,
1627 only_session: Option<SessionId>,
1628 replay_timeout: Duration,
1629) -> Result<(), ClientError> {
1630 let targets = {
1631 let mut subscriptions = subscriptions.lock().await;
1632 subscriptions
1633 .iter_mut()
1634 .filter_map(|(session_id, subscription)| {
1635 if only_session.is_some_and(|wanted| wanted != *session_id) {
1636 return None;
1637 }
1638 if subscription.fetching {
1639 subscription.recovery_requested =
1640 Some(subscription.recovery_requested.unwrap_or(false) || full_replay);
1641 return None;
1642 }
1643 subscription.rollback_cursor = subscription.cursor;
1644 let rebuild = full_replay;
1645 let cursor = if rebuild { 0 } else { subscription.cursor };
1646 subscription.cursor = cursor;
1647 subscription.fetching = true;
1648 subscription.rebuild = rebuild;
1649 if rebuild {
1650 subscription.generation += 1;
1651 }
1652 subscription.next_attempt += 1;
1653 subscription.active_attempt = subscription.next_attempt;
1654 Some((
1655 ReplayRequest {
1656 session_id: *session_id,
1657 generation: subscription.generation,
1658 rebuild,
1659 attempt: subscription.active_attempt,
1660 },
1661 cursor,
1662 ))
1663 })
1664 .collect::<Vec<_>>()
1665 };
1666 for (request, cursor) in targets {
1667 let session_id = request.session_id;
1668 if let Err(error) =
1669 request_replay_with_timeout(commands, request, cursor, replay_timeout).await
1670 {
1671 let mut subscriptions = subscriptions.lock().await;
1672 if let Some(subscription) = subscriptions.get_mut(&session_id) {
1673 subscription.cursor = subscription.rollback_cursor;
1674 subscription.fetching = false;
1675 subscription.awaiting_snapshots.clear();
1676 subscription.replay_tools.clear();
1677 }
1678 return Err(error);
1679 }
1680 }
1681 Ok(())
1682}
1683
1684static NEXT_REQUEST_ID: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(1);
1685
1686struct ConnectionTask {
1687 commands: mpsc::Receiver<Command>,
1688 controls: mpsc::UnboundedReceiver<ConnectionControl>,
1689 deliveries: Arc<dyn ClientEventSink>,
1690 subscriptions: Arc<Mutex<HashMap<SessionId, Subscription>>>,
1691 recovery: Arc<RecoveryQueue>,
1692 pending_command_count: Arc<AtomicUsize>,
1693 shutdown: tokio_util::sync::CancellationToken,
1694}
1695
1696async fn connection_task<S>(mut stream: S, mut task: ConnectionTask)
1697where
1698 S: Transport,
1699{
1700 let mut pending = HashMap::new();
1701 let mut tool_sessions = HashMap::new();
1702 let mut failure = None;
1703 let mut replay_timeout = tokio::time::interval(Duration::from_millis(25));
1704 loop {
1705 tokio::select! {
1706 () = task.shutdown.cancelled() => break,
1707 Some(command) = task.commands.recv() => {
1708 prune_cancelled_commands(&mut pending);
1709 if !command.response.is_closed() {
1710 let request = BorrowedRequest {
1711 jsonrpc: "2.0",
1712 id: command.id,
1713 method: &command.method,
1714 params: &command.params.value,
1715 };
1716 match serialize_outbound_frame(request, command.params.sensitive) {
1717 Ok(frame) => match send_outbound_frame(&mut stream, frame).await {
1718 Ok(()) => { pending.insert(command.id, PendingCommand { replay: command.replay, response: command.response }); }
1719 Err(error) => {
1720 let message = format!("sending {}: {}", command.method, crate::diagnostics::error_chain(&error));
1721 let _ = command.response.send(Err(ClientError::Transport(message.clone())));
1722 failure = Some(message);
1723 break;
1724 }
1725 },
1726 Err(error) => { let _ = command.response.send(Err(ClientError::InvalidFrame(error))); }
1727 }
1728 }
1729 }
1730 Some(control) = task.controls.recv() => match control {
1731 ConnectionControl::RecoveryFailed { session_id, error } => {
1732 task.deliveries.deliver(ClientDelivery::RecoveryFailed { session_id, error });
1733 }
1734 },
1735 _ = replay_timeout.tick() => {
1736 prune_cancelled_commands(&mut pending);
1737 release_expired_replays(&task.subscriptions, task.deliveries.as_ref(), &task.recovery, &mut tool_sessions).await;
1738 }
1739 incoming = stream.recv() => {
1740 let frame = match incoming {
1741 Ok(Some(frame)) => frame,
1742 Ok(None) => break,
1743 Err(error) => {
1744 failure = Some(format!("receiving response: {}", crate::diagnostics::error_chain(&error)));
1745 break;
1746 }
1747 };
1748 prune_cancelled_commands(&mut pending);
1749 if let Err(error) = handle_frame(
1750 frame,
1751 &mut pending,
1752 task.deliveries.as_ref(),
1753 &task.subscriptions,
1754 &task.recovery,
1755 &mut tool_sessions,
1756 ).await {
1757 resolve_malformed_response(error, &mut pending);
1758 }
1759 }
1760 else => break,
1761 }
1762 task.pending_command_count
1763 .store(pending.len(), Ordering::Relaxed);
1764 }
1765 for (_, pending) in pending {
1766 let error = failure.as_ref().map_or(ClientError::Closed, |message| {
1767 ClientError::Transport(message.clone())
1768 });
1769 let _ = pending.response.send(Err(error));
1770 }
1771 if let Some(error) = failure {
1772 task.deliveries
1773 .deliver(ClientDelivery::Disconnected { error });
1774 }
1775 task.pending_command_count.store(0, Ordering::Relaxed);
1776}
1777
1778fn serialize_outbound_frame(
1779 request: BorrowedRequest<'_>,
1780 sensitive: bool,
1781) -> Result<OutboundFrame, serde_json::Error> {
1782 if sensitive {
1783 serde_json::to_string(&request)
1784 .map(SensitiveFrame::new)
1785 .map(OutboundFrame::Sensitive)
1786 } else {
1787 serde_json::to_value(request)
1788 .map(MessageFrame::Value)
1789 .map(OutboundFrame::Public)
1790 }
1791}
1792
1793async fn send_outbound_frame<S>(stream: &mut S, frame: OutboundFrame) -> Result<(), TransportError>
1794where
1795 S: Transport,
1796{
1797 match frame {
1798 OutboundFrame::Public(frame) => stream.send(frame).await,
1799 OutboundFrame::Sensitive(mut frame) => {
1800 let text = std::mem::take(&mut *frame.text);
1804 stream.send(MessageFrame::Text(text)).await
1805 }
1806 }
1807}
1808
1809#[cfg(test)]
1810static PROVIDER_CONNECT_WIPE_COUNT: AtomicUsize = AtomicUsize::new(0);
1811
1812#[cfg(test)]
1813static SENSITIVE_SERIALIZED_WIPE_COUNT: AtomicUsize = AtomicUsize::new(0);
1814
1815#[cfg(test)]
1816static SENSITIVE_FRAME_WIPE_COUNT: AtomicUsize = AtomicUsize::new(0);
1817
1818#[cfg(test)]
1819fn record_provider_connect_wipe() {
1820 PROVIDER_CONNECT_WIPE_COUNT.fetch_add(1, Ordering::Relaxed);
1821}
1822
1823#[cfg(not(test))]
1824fn record_provider_connect_wipe() {}
1825
1826#[cfg(test)]
1827fn record_sensitive_serialized_wipe() {
1828 SENSITIVE_SERIALIZED_WIPE_COUNT.fetch_add(1, Ordering::Relaxed);
1829}
1830
1831#[cfg(not(test))]
1832fn record_sensitive_serialized_wipe() {}
1833
1834#[cfg(test)]
1835fn record_sensitive_frame_wipe() {
1836 SENSITIVE_FRAME_WIPE_COUNT.fetch_add(1, Ordering::Relaxed);
1837}
1838
1839#[cfg(not(test))]
1840fn record_sensitive_frame_wipe() {}
1841
1842fn prune_cancelled_commands(pending: &mut HashMap<i64, PendingCommand>) {
1843 pending.retain(|_, command| !command.response.is_closed());
1844}
1845
1846fn resolve_malformed_response(error: ClientError, pending: &mut HashMap<i64, PendingCommand>) {
1847 let detail = error.to_string();
1848 let mut pending = std::mem::take(pending).into_iter();
1849 if let Some((_, command)) = pending.next() {
1850 let _ = command.response.send(Err(error));
1851 }
1852 for (_, command) in pending {
1853 let error = serde_json::Error::io(std::io::Error::other(detail.clone()));
1854 let _ = command.response.send(Err(ClientError::InvalidFrame(error)));
1855 }
1856}
1857
1858async fn handle_frame(
1859 frame: MessageFrame,
1860 pending: &mut HashMap<i64, PendingCommand>,
1861 deliveries: &dyn ClientEventSink,
1862 subscriptions: &Arc<Mutex<HashMap<SessionId, Subscription>>>,
1863 recovery: &Arc<RecoveryQueue>,
1864 tool_sessions: &mut HashMap<ToolCallId, SessionId>,
1865) -> Result<(), ClientError> {
1866 let value = match frame {
1867 MessageFrame::Value(value) => value,
1868 MessageFrame::Text(text) => serde_json::from_str(&text)?,
1869 };
1870 if value.get("id").is_some() {
1871 let request_id = value
1872 .get("id")
1873 .cloned()
1874 .and_then(|id| serde_json::from_value::<JsonRpcId>(id).ok());
1875 let response: Response = match serde_json::from_value(value) {
1876 Ok(response) => response,
1877 Err(error) => {
1878 if let Some(JsonRpcId::Number(id)) = request_id
1879 && let Some(command) = pending.remove(&id)
1880 {
1881 let _ = command.response.send(Err(ClientError::InvalidFrame(error)));
1882 return Ok(());
1883 }
1884 return Err(ClientError::InvalidFrame(error));
1885 }
1886 };
1887 let (id, result) = match response {
1888 Response::Success(response) => (response.id, Ok(response.result)),
1889 Response::Error(response) => (response.id, Err(ClientError::Rpc(response.error))),
1890 };
1891 if let JsonRpcId::Number(id) = id
1892 && let Some(command) = pending.remove(&id)
1893 {
1894 match (command.replay, result) {
1895 (Some(replay), Ok(value)) => {
1896 match serde_json::from_value::<EventsSubscribeResult>(value) {
1897 Ok(result) => {
1898 let _ = command.response.send(Ok(Value::Null));
1902 begin_replay(
1903 replay,
1904 result.events,
1905 subscriptions,
1906 deliveries,
1907 recovery,
1908 tool_sessions,
1909 )
1910 .await;
1911 }
1912 Err(error) => {
1913 let _ = command.response.send(Err(ClientError::InvalidFrame(error)));
1914 }
1915 }
1916 }
1917 (_, result) => {
1918 let _ = command.response.send(result);
1919 }
1920 }
1921 }
1922 return Ok(());
1923 }
1924 let notification: Notification = serde_json::from_value(value)?;
1925 let Some(params) = notification.params else {
1926 return Ok(());
1927 };
1928 match notification.method.as_str() {
1929 "events.subscription" => {
1930 let message: EventSubscriptionMessage = serde_json::from_value(params)?;
1931 route_live(message, deliveries, subscriptions, recovery, tool_sessions).await;
1932 }
1933 "events.tool_output_snapshot" => {
1934 let snapshot = serde_json::from_value(params)?;
1935 route_output(
1936 ClientDelivery::OutputSnapshot(snapshot),
1937 deliveries,
1938 subscriptions,
1939 recovery,
1940 tool_sessions,
1941 )
1942 .await;
1943 }
1944 "events.tool_output_delta" => {
1945 let delta = serde_json::from_value(params)?;
1946 route_output(
1947 ClientDelivery::OutputDelta(delta),
1948 deliveries,
1949 subscriptions,
1950 recovery,
1951 tool_sessions,
1952 )
1953 .await;
1954 }
1955 "events.tool_output_gap" => {
1956 let gap = serde_json::from_value(params)?;
1957 route_output(
1958 ClientDelivery::OutputGap(gap),
1959 deliveries,
1960 subscriptions,
1961 recovery,
1962 tool_sessions,
1963 )
1964 .await;
1965 }
1966 "events.plugin" => {
1967 let event = serde_json::from_value(params)?;
1968 deliveries.deliver(ClientDelivery::PluginEvent(event));
1969 }
1970 crate::RUNTIME_CHANGED_METHOD => {
1971 let changed = serde_json::from_value(params)?;
1972 deliveries.deliver(ClientDelivery::RuntimeChanged(Box::new(changed)));
1973 }
1974 _ => {}
1975 }
1976 Ok(())
1977}
1978
1979async fn begin_replay(
1980 replay: ReplayRequest,
1981 events: Vec<StoredEvent>,
1982 subscriptions: &Arc<Mutex<HashMap<SessionId, Subscription>>>,
1983 deliveries: &dyn ClientEventSink,
1984 recovery: &Arc<RecoveryQueue>,
1985 tool_sessions: &mut HashMap<ToolCallId, SessionId>,
1986) {
1987 let active_tools = active_tools(&events);
1988 let final_seq = {
1989 let mut subscriptions = subscriptions.lock().await;
1990 let Some(subscription) = subscriptions.get_mut(&replay.session_id) else {
1991 return;
1992 };
1993 if !subscription.fetching
1994 || subscription.generation != replay.generation
1995 || subscription.active_attempt != replay.attempt
1996 {
1997 return;
1998 }
1999 let final_seq = events
2000 .iter()
2001 .map(|event| event.seq)
2002 .max()
2003 .unwrap_or(subscription.cursor);
2004 subscription.final_seq = final_seq;
2005 subscription.rebuild = replay.rebuild;
2006 subscription.replay_tools = active_tools.iter().copied().collect();
2007 subscription.awaiting_snapshots = events
2008 .iter()
2009 .filter_map(|event| match &event.payload {
2010 EventPayload::ToolCallStarted { start }
2011 if active_tools.contains(&start.tool_call_id) =>
2012 {
2013 Some(start)
2014 }
2015 _ => None,
2016 })
2017 .flat_map(|start| {
2018 start
2019 .output
2020 .channels()
2021 .into_iter()
2022 .map(|name| (start.tool_call_id, name.unwrap_or_default()))
2023 })
2024 .collect();
2025 subscription.snapshot_deadline = (!subscription.awaiting_snapshots.is_empty())
2026 .then(|| Instant::now() + Duration::from_millis(250));
2027 final_seq
2028 };
2029 deliveries.deliver(ClientDelivery::ReplayStart {
2030 session_id: replay.session_id,
2031 generation: replay.generation,
2032 final_seq,
2033 rebuild: replay.rebuild,
2034 });
2035 for event in events {
2036 if let EventPayload::ToolCallStarted { start } = &event.payload {
2037 tool_sessions.insert(start.tool_call_id, event.session_id);
2038 }
2039 deliveries.deliver(ClientDelivery::ReplayEvent {
2040 session_id: replay.session_id,
2041 generation: replay.generation,
2042 final_seq,
2043 event: Box::new(event),
2044 });
2045 }
2046 finish_ready_replay(
2047 replay.session_id,
2048 subscriptions,
2049 deliveries,
2050 recovery,
2051 tool_sessions,
2052 )
2053 .await;
2054}
2055
2056fn active_tools(events: &[StoredEvent]) -> HashSet<ToolCallId> {
2057 let mut tools = HashSet::new();
2058 for event in events {
2059 match &event.payload {
2060 EventPayload::ToolCallStarted { start } => {
2061 tools.insert(start.tool_call_id);
2062 }
2063 EventPayload::ToolCallTerminated { termination } => {
2064 tools.remove(&termination.tool_call_id);
2065 }
2066 _ => {}
2067 }
2068 }
2069 tools
2070}
2071
2072async fn route_live(
2073 message: EventSubscriptionMessage,
2074 deliveries: &dyn ClientEventSink,
2075 subscriptions: &Arc<Mutex<HashMap<SessionId, Subscription>>>,
2076 recovery: &Arc<RecoveryQueue>,
2077 tool_sessions: &mut HashMap<ToolCallId, SessionId>,
2078) {
2079 let session_id = match &message {
2080 EventSubscriptionMessage::Event { event } => {
2081 if let EventPayload::ToolCallStarted { start } = &event.payload {
2082 tool_sessions.insert(start.tool_call_id, event.session_id);
2083 }
2084 event.session_id
2085 }
2086 EventSubscriptionMessage::Gap { session_id, .. } => *session_id,
2087 };
2088 let publish;
2089 let mut recover = None;
2090 {
2091 let mut subscriptions = subscriptions.lock().await;
2092 let subscription = subscriptions.entry(session_id).or_default();
2093 let generation = subscription.generation;
2094 if subscription.fetching {
2095 if matches!(message, EventSubscriptionMessage::Gap { .. }) {
2096 subscription.recovery_requested =
2097 Some(subscription.recovery_requested.unwrap_or(false));
2098 }
2099 subscription.buffered.push(ClientDelivery::Live {
2100 message: Box::new(message),
2101 generation,
2102 });
2103 return;
2104 }
2105 match &message {
2106 EventSubscriptionMessage::Event { event } if event.seq > subscription.cursor + 1 => {
2107 recover = Some((false, Some(session_id)));
2108 }
2109 EventSubscriptionMessage::Event { event } if event.seq == subscription.cursor + 1 => {
2110 subscription.cursor = event.seq;
2111 }
2112 EventSubscriptionMessage::Gap {
2113 last_delivered_seq, ..
2114 } => {
2115 subscription.cursor = *last_delivered_seq;
2116 recover = Some((false, Some(session_id)));
2117 }
2118 _ => {}
2119 }
2120 publish = ClientDelivery::Live {
2121 message: Box::new(message),
2122 generation,
2123 };
2124 }
2125 deliveries.deliver(publish);
2126 if let Some((full, session)) = recover {
2127 Client::schedule_recovery_queue(recovery, full, session);
2128 }
2129}
2130
2131async fn route_output(
2132 delivery: ClientDelivery,
2133 deliveries: &dyn ClientEventSink,
2134 subscriptions: &Arc<Mutex<HashMap<SessionId, Subscription>>>,
2135 recovery: &Arc<RecoveryQueue>,
2136 tool_sessions: &mut HashMap<ToolCallId, SessionId>,
2137) {
2138 let retain_output = deliveries.wants_raw_tool_output();
2139 let (call_id, stream) = match &delivery {
2140 ClientDelivery::OutputSnapshot(snapshot) => {
2141 (snapshot.snapshot.call_id, stream_key(&snapshot.stream))
2142 }
2143 ClientDelivery::OutputDelta(delta) => (delta.call_id, stream_key(&delta.stream)),
2144 ClientDelivery::OutputGap(gap) => (gap.call_id, stream_key(&gap.stream)),
2145 _ => return,
2146 };
2147 let mut buffered = false;
2148 let mut completes_replay = None;
2149 if let Some(session_id) = tool_sessions.get(&call_id).copied() {
2150 let mut subscriptions = subscriptions.lock().await;
2151 if let Some(subscription) = subscriptions.get_mut(&session_id)
2152 && subscription.fetching
2153 {
2154 let replay_output = subscription.replay_tools.contains(&call_id);
2155 if matches!(&delivery, ClientDelivery::OutputSnapshot(_))
2156 && subscription.awaiting_snapshots.remove(&(call_id, stream))
2157 {
2158 completes_replay = Some(session_id);
2159 } else if !replay_output && retain_output {
2160 subscription.buffered.push(delivery.clone());
2161 buffered = true;
2162 }
2163 }
2164 }
2165 if !buffered && retain_output {
2166 deliveries.deliver(delivery);
2167 }
2168 if let Some(session_id) = completes_replay {
2169 finish_ready_replay(
2170 session_id,
2171 subscriptions,
2172 deliveries,
2173 recovery,
2174 tool_sessions,
2175 )
2176 .await;
2177 }
2178 let _ = recovery;
2179}
2180
2181async fn finish_ready_replay(
2182 session_id: SessionId,
2183 subscriptions: &Arc<Mutex<HashMap<SessionId, Subscription>>>,
2184 deliveries: &dyn ClientEventSink,
2185 recovery: &Arc<RecoveryQueue>,
2186 tool_sessions: &mut HashMap<ToolCallId, SessionId>,
2187) {
2188 let (generation, final_seq, buffered, recovery_requested, tail_has_gap) = {
2189 let mut subscriptions = subscriptions.lock().await;
2190 let Some(subscription) = subscriptions.get_mut(&session_id) else {
2191 return;
2192 };
2193 if !subscription.fetching || !subscription.awaiting_snapshots.is_empty() {
2194 return;
2195 }
2196 subscription.fetching = false;
2197 let (tail_cursor, tail_has_gap) = subscription.buffered.iter().fold(
2198 (subscription.final_seq, false),
2199 |(cursor, has_gap), delivery| match delivery {
2200 ClientDelivery::Live { message, .. } => match message.as_ref() {
2201 EventSubscriptionMessage::Event { event } if event.seq <= cursor => {
2202 (cursor, has_gap)
2203 }
2204 EventSubscriptionMessage::Event { event } if event.seq == cursor + 1 => {
2205 (event.seq, has_gap)
2206 }
2207 EventSubscriptionMessage::Event { .. }
2208 | EventSubscriptionMessage::Gap { .. } => (cursor, true),
2209 },
2210 _ => (cursor, has_gap),
2211 },
2212 );
2213 subscription.cursor = tail_cursor;
2214 subscription.replay_tools.clear();
2215 subscription.snapshot_deadline = None;
2216 (
2217 subscription.generation,
2218 subscription.final_seq,
2219 std::mem::take(&mut subscription.buffered),
2220 subscription.recovery_requested.take(),
2221 tail_has_gap,
2222 )
2223 };
2224 deliveries.deliver(ClientDelivery::ReplayEnd {
2225 session_id,
2226 generation,
2227 final_seq,
2228 });
2229 for delivery in buffered {
2230 if let ClientDelivery::Live { message, .. } = &delivery
2231 && let EventSubscriptionMessage::Event { event } = message.as_ref()
2232 {
2233 if event.seq <= final_seq {
2234 continue;
2235 }
2236 if let EventPayload::ToolCallStarted { start } = &event.payload {
2237 tool_sessions.insert(start.tool_call_id, event.session_id);
2238 }
2239 }
2240 deliveries.deliver(delivery);
2241 }
2242 if let Some(full) = recovery_requested.or(tail_has_gap.then_some(false)) {
2243 Client::schedule_recovery_queue(recovery, full, (!full).then_some(session_id));
2244 }
2245}
2246
2247async fn release_expired_replays(
2248 subscriptions: &Arc<Mutex<HashMap<SessionId, Subscription>>>,
2249 deliveries: &dyn ClientEventSink,
2250 recovery: &Arc<RecoveryQueue>,
2251 tool_sessions: &mut HashMap<ToolCallId, SessionId>,
2252) {
2253 let sessions = {
2254 let subscriptions = subscriptions.lock().await;
2255 subscriptions
2256 .iter()
2257 .filter_map(|(session_id, subscription)| {
2258 (subscription.fetching
2259 && subscription
2260 .snapshot_deadline
2261 .is_some_and(|deadline| deadline <= Instant::now()))
2262 .then_some(*session_id)
2263 })
2264 .collect::<Vec<_>>()
2265 };
2266 for session_id in sessions {
2270 if let Some(subscription) = subscriptions.lock().await.get_mut(&session_id) {
2271 subscription.awaiting_snapshots.clear();
2272 }
2273 finish_ready_replay(
2274 session_id,
2275 subscriptions,
2276 deliveries,
2277 recovery,
2278 tool_sessions,
2279 )
2280 .await;
2281 }
2282 let _ = recovery;
2283}
2284
2285fn stream_key(stream: &OutputStream) -> String {
2286 stream.name().to_owned()
2287}
2288
2289#[cfg(test)]
2290mod tests;