1use crate::canonical::CanonicalUnitEvent;
4use crate::config::{InvocationConfig, SessionConfig};
5use crate::id::ToolId;
6use crate::id::{ChannelId, SessionId, SessionKey, TransactionId};
7use crate::input::CanonicalInput;
8use crate::safe::SafeDiagnostic;
9use crate::tool::ToolLifecycleEvent;
10use serde::{Deserialize, Serialize};
11use std::future::Future;
12use std::pin::Pin;
13use std::sync::Arc;
14use std::time::Duration;
15use thiserror::Error;
16
17pub type EventDelivery =
19 Pin<Box<dyn Future<Output = Result<(), EventDeliveryError>> + Send + 'static>>;
20
21pub trait TransactionEventSink: Send + Sync + 'static {
23 fn deliver(&self, event: TransactionEvent) -> EventDelivery;
25}
26
27pub type CompletionDelivery =
29 Pin<Box<dyn Future<Output = Result<(), CompletionDeliveryError>> + Send + 'static>>;
30
31pub trait CompletionCallback: Send + 'static {
33 fn call(self: Box<Self>, end: TransactionEnd) -> CompletionDelivery;
35}
36
37pub struct FnEventSink<F>(pub F);
39
40impl<F> TransactionEventSink for FnEventSink<F>
41where
42 F: Fn(TransactionEvent) -> EventDelivery + Send + Sync + 'static,
43{
44 fn deliver(&self, event: TransactionEvent) -> EventDelivery {
45 (self.0)(event)
46 }
47}
48
49pub struct FnCompletionCallback<F>(pub F);
51
52impl<F> CompletionCallback for FnCompletionCallback<F>
53where
54 F: FnOnce(TransactionEnd) -> CompletionDelivery + Send + 'static,
55{
56 fn call(self: Box<Self>, end: TransactionEnd) -> CompletionDelivery {
57 (self.0)(end)
58 }
59}
60
61pub struct TransactionRequest {
63 pub channel_id: ChannelId,
65 pub session_id: Option<SessionId>,
67 pub input: CanonicalInput,
69 pub session_config: Option<SessionConfig>,
71 pub invocation_config: InvocationConfig,
73 pub tools: Vec<ToolId>,
75 pub events: Arc<dyn TransactionEventSink>,
77 pub completion: Box<dyn CompletionCallback>,
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct AdmissionReceipt {
84 pub transaction_id: TransactionId,
86 pub session_id: Option<SessionId>,
88}
89
90#[derive(Clone, Debug, PartialEq, Eq, Hash)]
92pub enum TransactionSelector {
93 Transaction(TransactionId),
95 Session(SessionKey),
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
101pub enum TerminationMode {
102 Cancel {
104 reason: CancellationReason,
106 },
107 ForceTerminate {
109 reason: TerminationReason,
111 },
112}
113
114#[derive(Clone, Debug, PartialEq, Eq)]
116pub struct CancellationReason {
117 pub code: CancellationReasonCode,
119 pub detail: Option<SafeDiagnostic>,
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
125pub enum CancellationReasonCode {
126 CallerRequested,
128 RuntimeShutdown,
130}
131
132#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct TerminationReason {
135 pub code: TerminationReasonCode,
137 pub detail: Option<SafeDiagnostic>,
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
143pub enum TerminationReasonCode {
144 CallerRequested,
146 CancellationGraceExpired,
148 RuntimeShutdown,
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
154pub enum TerminationDisposition {
155 Accepted,
157 AlreadyRequested,
159 AlreadyTerminal,
161 NotFound,
163}
164
165pub type Shutdown = Pin<Box<dyn Future<Output = ShutdownDisposition> + Send + 'static>>;
167
168#[derive(Clone, Debug, PartialEq, Eq, Default)]
170pub struct ShutdownDisposition {
171 pub normally_finalized: u64,
173 pub supervisor_finalized: u64,
175 pub callback_failed: u64,
177 pub callback_aborted: u64,
179 pub invariant_failed: u64,
181}
182
183pub trait TransactionRuntime: Send + Sync {
185 fn submit(&self, request: TransactionRequest) -> Result<AdmissionReceipt, AdmissionError>;
187
188 fn terminate(
190 &self,
191 selector: TransactionSelector,
192 mode: TerminationMode,
193 ) -> TerminationDisposition;
194
195 fn shutdown(&self, deadline: Duration) -> Shutdown;
197}
198
199#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
201pub struct TransactionEvent {
202 pub transaction_id: TransactionId,
204 pub channel_id: ChannelId,
206 pub session_id: SessionId,
208 pub sequence: u64,
210 pub payload: TransactionEventPayload,
212}
213
214#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
219pub enum TransactionEventPayload {
220 SessionEstablished {
222 external_session_id: crate::id::ExternalSessionId,
224 },
225 CanonicalUnit(CanonicalUnitEvent),
227 ToolLifecycle(ToolLifecycleEvent),
229 Diagnostic(TransactionDiagnostic),
231 Ended(TransactionEnd),
233}
234
235#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
237pub struct TransactionDiagnostic {
238 pub diagnostic: SafeDiagnostic,
240}
241
242#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
244pub struct TransactionEnd {
245 pub transaction_id: TransactionId,
247 pub session_id: Option<SessionId>,
249 pub channel_id: ChannelId,
251 pub kind: TransactionEndKind,
253 pub prior_terminal_cause: Option<TransactionEndKind>,
255 pub event_delivery: EventDeliveryOutcome,
257 pub emitted_events: u64,
259 pub usage: TransactionUsage,
261 pub diagnostics: Vec<TransactionDiagnostic>,
263}
264
265#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
267pub enum TransactionEndKind {
268 Completed,
270 ContinuationRequired,
272 Cancelled,
274 Terminated,
276 RuntimeShutdown,
278 DeadlineExceeded,
280 ChannelOpenFailed,
282 EncodingFailed,
284 ConnectorFailed,
286 InterpretationFailed,
288 ToolExchangeFailed,
290 EventDeliveryFailed,
292 LimitExceeded,
294 InvariantFailed,
296}
297
298#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
300pub enum EventDeliveryOutcome {
301 Accepted,
303 Failed,
305}
306
307#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
309pub struct TransactionUsage {
310 pub provider_input_tokens: Option<u64>,
312 pub provider_output_tokens: Option<u64>,
314 pub provider_exchanges: u32,
316 pub tools_started: u32,
318 pub tools_completed: u32,
320}
321
322#[derive(Clone, Debug, Error, PartialEq, Eq)]
324pub enum EventDeliveryError {
325 #[error("event delivery failed")]
327 Failed,
328 #[error("event delivery deadline exceeded")]
330 DeadlineExceeded,
331}
332
333#[derive(Clone, Debug, Error, PartialEq, Eq)]
335pub enum CompletionDeliveryError {
336 #[error("completion callback failed")]
338 Failed,
339 #[error("completion callback deadline exceeded")]
341 DeadlineExceeded,
342}
343
344#[derive(Clone, Debug, Error, PartialEq, Eq)]
346#[error("{kind:?}: {message}")]
347pub struct AdmissionError {
348 pub kind: AdmissionErrorKind,
350 pub message: String,
352}
353
354impl AdmissionError {
355 pub fn new(kind: AdmissionErrorKind, message: impl Into<String>) -> Self {
357 Self {
358 kind,
359 message: message.into(),
360 }
361 }
362}
363
364#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
366pub enum AdmissionErrorKind {
367 RuntimeShuttingDown,
369 UnknownChannel,
371 SessionAlreadyActive,
373 UnknownTool,
375 DuplicateTool,
377 InvalidInput,
379 InvalidConfiguration,
381 CapabilityMismatch,
383 CapacityExceeded,
385 SpawnFailed,
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::input::user_text_input;
393
394 #[test]
395 fn end_kind_round_trip() {
396 let kind = TransactionEndKind::Completed;
397 let json = serde_json::to_string(&kind).unwrap();
398 let back: TransactionEndKind = serde_json::from_str(&json).unwrap();
399 assert_eq!(kind, back);
400 }
401
402 #[tokio::test]
403 async fn sink_adapters_return_futures() {
404 let sink = FnEventSink(|_e| Box::pin(async { Ok(()) }) as EventDelivery);
405 let events: Arc<dyn TransactionEventSink> = Arc::new(sink);
406 let end = TransactionEnd {
407 transaction_id: TransactionId::generate(),
408 session_id: None,
409 channel_id: ChannelId::try_new("ch").unwrap(),
410 kind: TransactionEndKind::Completed,
411 prior_terminal_cause: None,
412 event_delivery: EventDeliveryOutcome::Accepted,
413 emitted_events: 1,
414 usage: TransactionUsage::default(),
415 diagnostics: vec![],
416 };
417 let ev = TransactionEvent {
418 transaction_id: end.transaction_id,
419 channel_id: end.channel_id.clone(),
420 session_id: SessionId::try_new("s").unwrap(),
421 sequence: 1,
422 payload: TransactionEventPayload::Ended(end.clone()),
423 };
424 events.deliver(ev).await.unwrap();
425
426 let cb: Box<dyn CompletionCallback> = Box::new(FnCompletionCallback(|_e| {
427 Box::pin(async { Ok(()) }) as CompletionDelivery
428 }));
429 cb.call(end).await.unwrap();
430
431 let _input = user_text_input("hello").unwrap();
432 }
433}