atd_sdk/options.rs
1use atd_protocol::{ToolTier, ToolVisibility};
2
3#[derive(Debug, Clone, Default)]
4pub struct DiscoverFilter {
5 pub tier: Option<ToolTier>,
6 pub visibility: Option<ToolVisibility>,
7 pub domain: Option<String>,
8 pub limit: Option<usize>,
9}
10
11#[derive(Debug, Clone, Default)]
12pub struct CallOptions {
13 pub dry_run: bool,
14 pub preferred_binding: Option<atd_protocol::BindingProtocol>,
15}
16
17/// SP-concurrency-baseline §5.3 — controls `AtdClient::connect` retry behaviour.
18///
19/// Defaults are read from env (`ATD_CONNECT_RETRIES`,
20/// `ATD_CONNECT_BACKOFF_BASE_MS`, `ATD_CONNECT_BACKOFF_CAP_MS`,
21/// `ATD_CONNECT_TIMEOUT_MS`) so adopters tune deployments without code
22/// edits. Construct manually for explicit control:
23///
24/// ```no_run
25/// # use atd_sdk::{AtdClient, ConnectOptions, Endpoint};
26/// # async fn ex() {
27/// let opts = ConnectOptions { max_attempts: 3, backoff_base_ms: 100, backoff_cap_ms: 1000, connect_timeout_ms: 5000 };
28/// let _c = AtdClient::connect_with_options(Endpoint::unix("/tmp/x.sock"), opts).await;
29/// # }
30/// ```
31#[derive(Debug, Clone)]
32pub struct ConnectOptions {
33 /// Total connect attempts before giving up. Includes the initial try.
34 pub max_attempts: u32,
35 /// Initial backoff after the first failed attempt, in ms.
36 pub backoff_base_ms: u64,
37 /// Backoff is doubled per failure but capped at this value (ms).
38 pub backoff_cap_ms: u64,
39 /// Per-attempt deadline wrapping `UnixStream::connect` + `ping`.
40 pub connect_timeout_ms: u64,
41}
42
43impl Default for ConnectOptions {
44 fn default() -> Self {
45 Self {
46 max_attempts: env_u32("ATD_CONNECT_RETRIES", 5),
47 backoff_base_ms: env_u64("ATD_CONNECT_BACKOFF_BASE_MS", 50),
48 backoff_cap_ms: env_u64("ATD_CONNECT_BACKOFF_CAP_MS", 800),
49 connect_timeout_ms: env_u64("ATD_CONNECT_TIMEOUT_MS", 10_000),
50 }
51 }
52}
53
54/// SP-pagination-v1 §4.8 — one page of a paginated tool result.
55///
56/// Returned by `AtdClient::call_page`. Clients pass `next_cursor` verbatim
57/// back to the SDK on the follow-up `call_page` call; the SDK doesn't
58/// parse it. `None` means terminal page.
59#[derive(Debug, Clone)]
60pub struct PaginatedSdkResult {
61 pub value: serde_json::Value,
62 pub next_cursor: Option<String>,
63}
64
65/// SP-pagination-v1 §4.8 — controls `AtdClient::call_all`'s auto-loop.
66///
67/// Sanity bounds against runaway loops (misbehaving server keeps issuing
68/// cursors) and against accidentally swallowing more memory than the
69/// caller expected. Either cap triggers `AtdError::PaginationLimitExceeded`
70/// with `pages_fetched` + `bytes_fetched` so callers can decide whether to
71/// treat the partial as success or retry with narrower args.
72#[derive(Debug, Clone)]
73pub struct CallAllOptions {
74 /// Maximum number of pages (including the initial call). Default 100.
75 pub max_pages: u32,
76 /// Maximum cumulative serialized bytes across pages. Default 32 MiB.
77 pub max_total_bytes: usize,
78 /// How to combine multiple pages into one Value. See [`MergePolicy`].
79 pub merge_policy: MergePolicy,
80}
81
82impl Default for CallAllOptions {
83 fn default() -> Self {
84 Self {
85 max_pages: 100,
86 max_total_bytes: 32 * 1024 * 1024,
87 merge_policy: MergePolicy::ConcatArray,
88 }
89 }
90}
91
92/// SP-pagination-v1 §4.8 — strategy for merging pages.
93///
94/// Different paginating tools return different shapes:
95/// - `healthkit:query_observations` returns `[Observation, ...]` per page → `ConcatArray`
96/// - `celia:list_observations` returns `{patient: "x", observations: [...], total: N}` per page → `ConcatField("observations")`
97/// - A summary tool that only honors the first page → `FirstPageOnly`
98#[derive(Debug, Clone)]
99pub enum MergePolicy {
100 /// Each page is a JSON array; concat across pages.
101 ConcatArray,
102 /// Each page is a JSON object; concat the named array field across
103 /// pages and keep the last page's other fields (e.g., metadata totals
104 /// that don't change across pages).
105 ConcatField(String),
106 /// First page wins; subsequent pages are dropped silently.
107 FirstPageOnly,
108}
109
110fn env_u32(key: &str, default: u32) -> u32 {
111 std::env::var(key)
112 .ok()
113 .and_then(|s| s.parse().ok())
114 .unwrap_or(default)
115}
116
117fn env_u64(key: &str, default: u64) -> u64 {
118 std::env::var(key)
119 .ok()
120 .and_then(|s| s.parse().ok())
121 .unwrap_or(default)
122}