Skip to main content

agent_engine/runtime/
types.rs

1use serde::Deserialize;
2
3// Stream event types now live in agent-core so rpc_dispatch can use them
4// without an upward dependency on the engine layer.
5pub use agent_core::{AgentEvent, LlmEvent, SessionEvent, StreamEvent};
6
7/// Per-turn signals shared by the host and the engine.
8///
9/// Engine → host: whether the turn reached its NORMAL end — the final
10/// response came back un-cancelled and asked for no tools (nothing left to
11/// do). Set before the engine publishes that turn's final `MessageHistory`.
12/// `Done` cannot say this: several cancelled paths also end `Ok` + `Done`.
13/// A host that cancels a turn reads it once the stream is drained: a turn
14/// that finished just before the cancel reached it (Esc a moment too late)
15/// is complete, not interrupted.
16///
17/// Host → engine: WHY the host cancelled the turn, noted before it cancels
18/// the token, so a canceled `tool_result` says so ("Canceled by user" only
19/// when it was the user). Nothing noted = the turn's default cause: the
20/// user (the historical wording), or what the host chose at turn start
21/// (`with_default_cause`: a driver turn whose token something else cancels,
22/// e.g. the grant's deadline, was cut by the driver ending).
23#[derive(Debug, Clone, Default)]
24pub struct TurnCompletion {
25    completed: std::sync::Arc<std::sync::atomic::AtomicBool>,
26    /// `0` = unset, else `CancelCause as u8`.
27    cancel_cause: std::sync::Arc<std::sync::atomic::AtomicU8>,
28    /// Used when nothing was noted; `None` = the user.
29    default_cause: Option<CancelCause>,
30}
31
32/// Why a host cancelled a turn, as far as a canceled tool result says.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[repr(u8)]
35pub enum CancelCause {
36    User = 1,
37    CostCap = 2,
38    Restart = 3,
39    Host = 4,
40    Driver = 5,
41}
42
43impl CancelCause {
44    /// Leading words of a canceled `tool_result`. Every variant starts with
45    /// "Canceled" (the phase-4 bound tests key on it); the user's is the
46    /// historical, byte-identical "Canceled by user".
47    pub fn phrase(self) -> &'static str {
48        match self {
49            CancelCause::User => "Canceled by user",
50            CancelCause::CostCap => "Canceled (session cost cap reached)",
51            CancelCause::Restart => "Canceled (Synaps restarted)",
52            CancelCause::Host => "Canceled (session stopped by the host)",
53            CancelCause::Driver => "Canceled (session driver revoked)",
54        }
55    }
56
57    fn from_u8(v: u8) -> Option<Self> {
58        Some(match v {
59            1 => CancelCause::User,
60            2 => CancelCause::CostCap,
61            3 => CancelCause::Restart,
62            4 => CancelCause::Host,
63            5 => CancelCause::Driver,
64            _ => return None,
65        })
66    }
67}
68
69impl TurnCompletion {
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// A turn whose un-noted cancels are `cause`'s (see the type docs).
75    pub fn with_default_cause(cause: CancelCause) -> Self {
76        Self {
77            default_cause: Some(cause),
78            ..Self::default()
79        }
80    }
81
82    pub fn completed(&self) -> bool {
83        self.completed.load(std::sync::atomic::Ordering::Acquire)
84    }
85
86    pub(crate) fn mark_completed(&self) {
87        self.completed
88            .store(true, std::sync::atomic::Ordering::Release);
89    }
90
91    /// Note why the host is cancelling this turn. First cause wins: a
92    /// teardown that revokes a driver and then cancels keeps its own cause.
93    pub fn note_cancel_cause(&self, cause: CancelCause) {
94        let _ = self.cancel_cause.compare_exchange(
95            0,
96            cause as u8,
97            std::sync::atomic::Ordering::AcqRel,
98            std::sync::atomic::Ordering::Acquire,
99        );
100    }
101
102    /// Leading words of this turn's canceled tool results.
103    pub(crate) fn cancel_phrase(&self) -> &'static str {
104        CancelCause::from_u8(self.cancel_cause.load(std::sync::atomic::Ordering::Acquire))
105            .or(self.default_cause)
106            .unwrap_or(CancelCause::User)
107            .phrase()
108    }
109}
110
111/// Shared mutable auth state. Lives behind `Arc<RwLock<_>>` so the spawned
112/// streaming task and the parent Runtime always see the same (freshest) token.
113#[derive(Debug, Clone)]
114pub(super) struct AuthState {
115    pub(super) auth_token: String,
116    pub(super) auth_type: String,
117    pub(super) refresh_token: Option<String>,
118    pub(super) token_expires: Option<u64>,
119}
120
121#[allow(dead_code)]
122#[derive(Debug, Deserialize)]
123pub(super) struct AnthropicAuth {
124    #[serde(rename = "type")]
125    pub(super) auth_type: String,
126    pub(super) refresh: Option<String>,
127    pub(super) access: Option<String>,
128    pub(super) expires: Option<u64>,
129    pub(super) key: Option<String>,
130}