agent_sdk/
types.rs

1//! Core types for the agent SDK.
2//!
3//! This module contains the fundamental types used throughout the SDK:
4//!
5//! - [`ThreadId`]: Unique identifier for conversation threads
6//! - [`AgentConfig`]: Configuration for the agent loop
7//! - [`TokenUsage`]: Token consumption statistics
8//! - [`ToolResult`]: Result returned from tool execution
9//! - [`ToolTier`]: Permission tiers for tools
10//! - [`PendingAction`]: Actions awaiting user confirmation
11//! - [`AgentState`]: Checkpointable agent state
12
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use time::OffsetDateTime;
16use uuid::Uuid;
17
18/// Unique identifier for a conversation thread
19#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub struct ThreadId(pub String);
21
22impl ThreadId {
23    #[must_use]
24    pub fn new() -> Self {
25        Self(Uuid::new_v4().to_string())
26    }
27
28    #[must_use]
29    pub fn from_string(s: impl Into<String>) -> Self {
30        Self(s.into())
31    }
32}
33
34impl Default for ThreadId {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl std::fmt::Display for ThreadId {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(f, "{}", self.0)
43    }
44}
45
46/// Configuration for the agent loop
47#[derive(Clone, Debug)]
48pub struct AgentConfig {
49    /// Maximum number of turns (LLM round-trips) before stopping
50    pub max_turns: usize,
51    /// Maximum tokens per response
52    pub max_tokens: u32,
53    /// System prompt for the agent
54    pub system_prompt: String,
55    /// Model identifier
56    pub model: String,
57    /// Retry configuration for transient errors
58    pub retry: RetryConfig,
59}
60
61impl Default for AgentConfig {
62    fn default() -> Self {
63        Self {
64            max_turns: 10,
65            max_tokens: 4096,
66            system_prompt: String::new(),
67            model: String::from("claude-sonnet-4-20250514"),
68            retry: RetryConfig::default(),
69        }
70    }
71}
72
73/// Configuration for retry behavior on transient errors.
74#[derive(Clone, Debug)]
75pub struct RetryConfig {
76    /// Maximum number of retry attempts
77    pub max_retries: u32,
78    /// Base delay in milliseconds for exponential backoff
79    pub base_delay_ms: u64,
80    /// Maximum delay cap in milliseconds
81    pub max_delay_ms: u64,
82}
83
84impl Default for RetryConfig {
85    fn default() -> Self {
86        Self {
87            max_retries: 5,
88            base_delay_ms: 1000,
89            max_delay_ms: 120_000,
90        }
91    }
92}
93
94impl RetryConfig {
95    /// Create a retry config with no retries (for testing)
96    #[must_use]
97    pub const fn no_retry() -> Self {
98        Self {
99            max_retries: 0,
100            base_delay_ms: 0,
101            max_delay_ms: 0,
102        }
103    }
104
105    /// Create a retry config with fast retries (for testing)
106    #[must_use]
107    pub const fn fast() -> Self {
108        Self {
109            max_retries: 5,
110            base_delay_ms: 10,
111            max_delay_ms: 100,
112        }
113    }
114}
115
116/// Token usage statistics
117#[derive(Clone, Debug, Default, Serialize, Deserialize)]
118pub struct TokenUsage {
119    pub input_tokens: u32,
120    pub output_tokens: u32,
121}
122
123impl TokenUsage {
124    pub const fn add(&mut self, other: &Self) {
125        self.input_tokens += other.input_tokens;
126        self.output_tokens += other.output_tokens;
127    }
128}
129
130/// Result of a tool execution
131#[derive(Clone, Debug, Serialize, Deserialize)]
132pub struct ToolResult {
133    /// Whether the tool execution succeeded
134    pub success: bool,
135    /// Output content (displayed to user and fed back to LLM)
136    pub output: String,
137    /// Optional structured data
138    pub data: Option<serde_json::Value>,
139    /// Duration of the tool execution in milliseconds
140    pub duration_ms: Option<u64>,
141}
142
143impl ToolResult {
144    #[must_use]
145    pub fn success(output: impl Into<String>) -> Self {
146        Self {
147            success: true,
148            output: output.into(),
149            data: None,
150            duration_ms: None,
151        }
152    }
153
154    #[must_use]
155    pub fn success_with_data(output: impl Into<String>, data: serde_json::Value) -> Self {
156        Self {
157            success: true,
158            output: output.into(),
159            data: Some(data),
160            duration_ms: None,
161        }
162    }
163
164    #[must_use]
165    pub fn error(message: impl Into<String>) -> Self {
166        Self {
167            success: false,
168            output: message.into(),
169            data: None,
170            duration_ms: None,
171        }
172    }
173
174    #[must_use]
175    pub const fn with_duration(mut self, duration_ms: u64) -> Self {
176        self.duration_ms = Some(duration_ms);
177        self
178    }
179}
180
181/// Permission tier for tools
182#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
183pub enum ToolTier {
184    /// Tier 0: Read-only, always allowed (e.g., `get_balance`)
185    Observe,
186    /// Tier 1: Requires confirmation before execution
187    Confirm,
188    /// Tier 3: Requires PIN verification (e.g., `send_pix`, `buy_btc`)
189    RequiresPin,
190}
191
192/// State of a pending action that requires confirmation or PIN
193#[derive(Clone, Debug, Serialize, Deserialize)]
194pub struct PendingAction {
195    pub id: String,
196    pub tool_name: String,
197    pub tool_input: serde_json::Value,
198    pub tier: ToolTier,
199    #[serde(with = "time::serde::rfc3339")]
200    pub created_at: OffsetDateTime,
201    #[serde(with = "time::serde::rfc3339")]
202    pub expires_at: OffsetDateTime,
203}
204
205/// Snapshot of agent state for checkpointing
206#[derive(Clone, Debug, Serialize, Deserialize)]
207pub struct AgentState {
208    pub thread_id: ThreadId,
209    pub turn_count: usize,
210    pub total_usage: TokenUsage,
211    pub metadata: HashMap<String, serde_json::Value>,
212    #[serde(with = "time::serde::rfc3339")]
213    pub created_at: OffsetDateTime,
214}
215
216impl AgentState {
217    #[must_use]
218    pub fn new(thread_id: ThreadId) -> Self {
219        Self {
220            thread_id,
221            turn_count: 0,
222            total_usage: TokenUsage::default(),
223            metadata: HashMap::new(),
224            created_at: OffsetDateTime::now_utc(),
225        }
226    }
227}