1use crate::canonical::{InterpretationId, ToolActionId, UnitId};
6use crate::id::{ConnectionId, ExternalSessionId, MonoloopRunId};
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct LoopId(String);
12
13impl LoopId {
14 pub fn new(value: impl Into<String>) -> Self {
16 Self(value.into())
17 }
18
19 pub fn generate() -> Self {
21 Self(uuid::Uuid::new_v4().to_string())
22 }
23
24 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28}
29
30#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
32pub struct ToolExecutionId(String);
33
34impl ToolExecutionId {
35 pub fn new(value: impl Into<String>) -> Self {
37 Self(value.into())
38 }
39
40 pub fn generate() -> Self {
42 Self(uuid::Uuid::new_v4().to_string())
43 }
44
45 pub fn as_str(&self) -> &str {
47 &self.0
48 }
49}
50
51#[derive(Clone, Debug)]
53pub struct LoopLimits {
54 pub max_tool_actions: usize,
56 pub max_concurrent_executions: usize,
58 pub max_queued_ready: usize,
60 pub max_output_queue: usize,
62 pub max_dedup_entries: usize,
64}
65
66impl Default for LoopLimits {
67 fn default() -> Self {
68 Self {
69 max_tool_actions: 1024,
70 max_concurrent_executions: 32,
71 max_queued_ready: 256,
72 max_output_queue: 4096,
73 max_dedup_entries: 4096,
74 }
75 }
76}
77
78#[derive(Clone, Debug)]
80pub struct LoopScope {
81 pub monoloop_run_id: MonoloopRunId,
83 pub loop_id: LoopId,
85 pub accepted_interpretation_ids: Vec<InterpretationId>,
88 pub accepted_connection_ids: Vec<ConnectionId>,
90 pub accepted_external_session_ids: Vec<ExternalSessionId>,
92 pub accept_all_in_run: bool,
94}
95
96impl Default for LoopScope {
97 fn default() -> Self {
98 Self {
99 monoloop_run_id: MonoloopRunId::generate(),
100 loop_id: LoopId::generate(),
101 accepted_interpretation_ids: Vec::new(),
102 accepted_connection_ids: Vec::new(),
103 accepted_external_session_ids: Vec::new(),
104 accept_all_in_run: true,
105 }
106 }
107}
108
109impl LoopScope {
110 pub fn single(
112 run_id: MonoloopRunId,
113 loop_id: LoopId,
114 interpretation_id: InterpretationId,
115 connection_id: ConnectionId,
116 external_session_id: Option<ExternalSessionId>,
117 ) -> Self {
118 let mut sessions = Vec::new();
119 if let Some(s) = external_session_id {
120 sessions.push(s);
121 }
122 Self {
123 monoloop_run_id: run_id,
124 loop_id,
125 accepted_interpretation_ids: vec![interpretation_id],
126 accepted_connection_ids: vec![connection_id],
127 accepted_external_session_ids: sessions,
128 accept_all_in_run: false,
129 }
130 }
131}
132
133#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
135pub enum ToolUnavailableReason {
136 NoRegisteredTool,
138 NotFound,
140 Denied,
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
146pub enum OutboundToolOutcome {
147 Success,
149 ToolUnavailable,
151 DispatchRejected,
153 ExecutionFailed,
155 Cancelled,
157 ExecutionLost,
159}
160
161#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
163pub struct OutboundToolResult {
164 pub outbound_result_id: String,
166 pub monoloop_run_id: MonoloopRunId,
168 pub loop_id: LoopId,
170 pub source_interpretation_id: InterpretationId,
172 pub source_connection_id: ConnectionId,
174 pub external_session_id: Option<ExternalSessionId>,
176 pub tool_action_id: ToolActionId,
178 pub request_generation: u64,
180 pub tool_execution_id: Option<ToolExecutionId>,
182 pub outcome: OutboundToolOutcome,
184 pub payload: String,
186 pub source_unit_id: UnitId,
188}
189
190#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
192pub enum LoopOutputEvent {
193 ToolDispatchRequested {
195 tool_action_id: ToolActionId,
197 request_generation: u64,
199 },
200 ToolUnavailable {
202 tool_action_id: ToolActionId,
204 reason: ToolUnavailableReason,
206 },
207 OutboundToolResult(OutboundToolResult),
209 Diagnostic {
211 message: String,
213 },
214 LoopEnded(LoopEnd),
216}
217
218#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
220pub struct LoopEnd {
221 pub monoloop_run_id: MonoloopRunId,
223 pub loop_id: LoopId,
225 pub kind: LoopEndKind,
227 pub delivery_events_received: u64,
229 pub duplicate_events: u64,
231 pub tools_unavailable: u64,
233 pub outbound_results_emitted: u64,
235 pub safe_diagnostics: Vec<String>,
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
241pub enum LoopEndKind {
242 Drained,
244 Cancelled,
246 SubscriptionLost,
248 OutputFailed,
250 InvariantFailed,
252 ConfigurationFailed,
254}
255
256#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258pub enum LoopErrorKind {
259 EventOutOfScope,
261 DeliverySequenceGap,
263 UnitIdentityConflict,
265 ToolRequestIncomplete,
267 LimitExceeded,
269 Cancelled,
271 InvariantViolation,
273 ConfigurationInvalid,
275 OutputBackpressure,
277}
278
279#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
281#[error("{kind:?}: {message}")]
282pub struct LoopError {
283 pub kind: LoopErrorKind,
285 pub message: String,
287}
288
289impl LoopError {
290 pub fn new(kind: LoopErrorKind, message: impl Into<String>) -> Self {
292 Self {
293 kind,
294 message: message.into(),
295 }
296 }
297
298 pub fn cancelled() -> Self {
300 Self::new(LoopErrorKind::Cancelled, "loop cancelled")
301 }
302
303 pub fn gap() -> Self {
305 Self::new(
306 LoopErrorKind::DeliverySequenceGap,
307 "subscription gap detected",
308 )
309 }
310
311 pub fn limit(message: impl Into<String>) -> Self {
313 Self::new(LoopErrorKind::LimitExceeded, message)
314 }
315}