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
//! In-process delegate the embedder (the API) plugs in at
//! [`crate::setup`] time so the proxy can surface client-notification
//! content blocks on tool responses without HTTP round-trips.
//!
//! The proxy is **fully naive** about ban lists, content-row ids, and
//! confirmation state. It hands the trait two things on each call
//! (the per-session agent arguments + the MCP session id) and either
//! gets back a [`QueueRead`] (token + blocks) to splice into the next
//! tool response, or `None` for "nothing to surface right now"
//! (including errors — the proxy never sees a `Result`).
//!
//! The `token` is opaque to the proxy: it gets embedded in the
//! wrapper prefix the agent sees (via
//! [`objectiveai_sdk::mcp::queue_notification::format_prefix`]),
//! round-trips through the agent's tool-message text, and lands
//! back at the API's run-loop, which calls back into its own
//! `ApiQueueDelegate::confirm(token)` to finalize delivery. Tokens
//! the run-loop never sees stay in the delegate's "pending" map
//! until the loop unregisters, at which point those ids re-deliver
//! on the next loop. That's how we avoid claiming delivery for
//! content the agent didn't actually consume.
//!
//! The proxy calls the delegate **sequentially after the tool call
//! succeeds**. Running the read in parallel with the tool call would
//! advance the delegate's pending map even when the call ends up
//! returning a JSON-RPC error (ToolNotFound / Upstream) — those rows
//! would never surface in any tool-message text and would never get
//! confirmed (correct behavior), but the wasted speculative state
//! makes pending bloat unnecessarily. Sequential keeps things tidy.
use Future;
use Pin;
use IndexMap;
use ContentBlock;
/// Successful return shape: a per-row block list plus a
/// confirmation token the delegate has reserved for this batch.
/// The proxy embeds `token` in the wrapper prefix it prepends to
/// the tool response; it does nothing else with the token.
/// Embedder-provided message-queue read path. `None` ⇒ no delegate
/// installed at proxy boot (CLI standalone case); the proxy's
/// tool-call dispatcher short-circuits without invoking anything.
///
/// The trait method returns a boxed-pinned `Future + Send` rather
/// than using `async_trait`: no proc-macro dep, and the trait
/// stays `dyn`-safe so the proxy can hold it as
/// `Arc<dyn QueueDelegate>`. Implementations can still write the
/// body as an `async move { ... }` block wrapped in `Box::pin`.