1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! Agent Host Protocol SDK — async client, reducers, and pluggable
//! transports.
//!
//! AHP is a JSON-RPC protocol that lets a host (an editor, IDE, shell,
//! or test harness) talk to an agent backend through a small set of
//! subscribe / dispatch / reduce primitives. This crate is the Rust
//! implementation of the **client** side.
//!
//! For a tour of the protocol itself, see the
//! [protocol documentation](https://microsoft.github.io/agent-host-protocol/).
//!
//! # Crate layout
//!
//! | Item | Use it for |
//! |---|---|
//! | [`Client`] | Connect to a server, subscribe to resources, dispatch actions |
//! | [`reducers`] | Apply [`StateAction`](ahp_types::actions::StateAction) to local state in a fully deterministic way |
//! | [`Transport`] | Pluggable trait for any framed message stream |
//! | [`ClientError`] / [`TransportError`] | Error taxonomy |
//!
//! # Companion crates
//!
//! - [`ahp_types`] — wire types only, no I/O
//! - [`ahp-ws`](https://docs.rs/ahp-ws) — WebSocket transport built on `tokio-tungstenite`
//!
//! # Quickstart (WebSocket)
//!
//! Connect over WebSocket, initialize, and stream events from a
//! session. The example below uses the [`ahp-ws`](https://docs.rs/ahp-ws)
//! crate; replace the transport line with any other [`Transport`]
//! implementation if you have one.
//!
//! ```no_run
//! use ahp::{Client, ClientConfig, SubscriptionEvent};
//! use ahp_ws::WebSocketTransport;
//!
//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
//! let transport = WebSocketTransport::connect("ws://localhost:12345").await?;
//! let client = Client::connect(transport, ClientConfig::default()).await?;
//!
//! client.initialize("my-client".into(), vec!["0.1.0".into()], vec![]).await?;
//! let (_snap, mut sub) = client.subscribe("ahp-session:/s1".into()).await?;
//!
//! while let Some(SubscriptionEvent::Action(env)) = sub.recv().await {
//! println!("seq={} action={:?}", env.server_seq, env.action);
//! }
//!
//! client.shutdown().await;
//! # Ok(()) }
//! ```
//!
//! # Quickstart (any transport)
//!
//! The same flow works against any [`Transport`] implementation:
//!
//! ```no_run
//! # async fn run<T: ahp::Transport>(transport: T) -> Result<(), ahp::ClientError> {
//! use ahp::{Client, ClientConfig, SubscriptionEvent};
//!
//! let client = Client::connect(transport, ClientConfig::default()).await?;
//! client.initialize("my-client".into(), vec!["0.1.0".into()], vec!["ahp-root://".into()]).await?;
//!
//! let mut sub = client.attach_subscription("ahp-root://").await;
//! while let Some(ev) = sub.recv().await {
//! match ev {
//! SubscriptionEvent::Action(a) => println!("seq={}", a.server_seq),
//! _ => {}
//! }
//! }
//!
//! client.shutdown().await;
//! # Ok(()) }
//! ```
//!
//! # Subscriptions
//!
//! Subscribe to a URI to receive its [`SubscriptionEvent`] stream:
//!
//! - `ahp-root://` — global agent host state (agents, session index)
//! - `ahp-session:/<id>` — a single chat session
//! - `terminal:/<id>` — a terminal
//!
//! [`Client::subscribe`] sends a `subscribe` request and returns the
//! initial snapshot together with a [`SessionSubscription`] handle.
//! [`Client::attach_subscription`] creates a local handle without an
//! extra round-trip — useful when the URI was already passed to
//! `initialize` via `initialSubscriptions`.
//!
//! Multiple [`SessionSubscription`]s can fan out from a single URI;
//! each handle has its own broadcast cursor. Drop the handle to stop
//! receiving events; call [`Client::unsubscribe`] to release the
//! server-side subscription.
//!
//! # Dispatching actions
//!
//! Local UI mutations are dispatched through [`Client::dispatch`]. The
//! client assigns a monotonically increasing `clientSeq` and sends a
//! `dispatchAction` notification. The server eventually echoes the
//! action back as a normal envelope; reducers can be applied identically
//! to both sources, so write-ahead state is naturally reconciled.
//!
//! # Reducers
//!
//! Reducers are pure functions that translate a [`StateAction`](ahp_types::actions::StateAction)
//! into mutations on [`RootState`](ahp_types::state::RootState),
//! [`SessionState`](ahp_types::state::SessionState), or
//! [`TerminalState`](ahp_types::state::TerminalState).
//!
//! ```
//! use ahp::reducers::{apply_action_to_root, ReduceOutcome};
//! use ahp::ahp_types::actions::{RootActiveSessionsChangedAction, StateAction};
//! use ahp::ahp_types::state::RootState;
//!
//! let mut root = RootState {
//! agents: vec![],
//! active_sessions: None,
//! terminals: None,
//! config: None,
//! };
//!
//! let action = StateAction::RootActiveSessionsChanged(
//! RootActiveSessionsChangedAction { active_sessions: 3 },
//! );
//!
//! assert_eq!(apply_action_to_root(&mut root, &action), ReduceOutcome::Applied);
//! assert_eq!(root.active_sessions, Some(3));
//! ```
//!
//! Each reducer returns a [`ReduceOutcome`] that distinguishes mutations
//! ([`ReduceOutcome::Applied`]), recognized but inert events
//! ([`ReduceOutcome::NoOp`]), and out-of-scope routing
//! ([`ReduceOutcome::OutOfScope`]). A client holding all three state
//! trees can blindly fan every action out to every reducer without
//! special-casing.
//!
//! # Cancellation and shutdown
//!
//! All async client APIs are cancel-safe at await points. The background
//! driver is owned by the [`Client`] and aborted when the last clone is
//! dropped, or when [`Client::shutdown`] is called. In-flight requests
//! resolve with [`ClientError::Shutdown`] in either case.
pub use ahp_types;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;