Skip to main content

lash_remote_protocol/
usage_activity.rs

1//! Token usage accounting and the streaming turn-activity event vocabulary.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::ensure_protocol_version;
7use crate::registry_errors::{RemoteProtocolError, require_non_empty};
8
9// Wire mirror of the runtime usage counters. This is a deliberately versioned
10// protocol boundary, kept independent of the internal types so the wire format
11// stays stable across internal refactors. The `From` converters in
12// `core_conversions::llm` destructure their `lash_core` source exhaustively
13// (no `..`), so adding a counter upstream is a compile error until it is mirrored
14// here too.
15#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
16pub struct RemoteUsage {
17    pub input_tokens: i64,
18    pub output_tokens: i64,
19    pub cache_read_input_tokens: i64,
20    pub cache_write_input_tokens: i64,
21    pub reasoning_output_tokens: i64,
22}
23
24impl RemoteUsage {
25    pub fn add(&mut self, other: &Self) {
26        self.input_tokens += other.input_tokens;
27        self.output_tokens += other.output_tokens;
28        self.cache_read_input_tokens += other.cache_read_input_tokens;
29        self.cache_write_input_tokens += other.cache_write_input_tokens;
30        self.reasoning_output_tokens += other.reasoning_output_tokens;
31    }
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
35pub struct RemoteTokenLedgerEntry {
36    pub source: String,
37    pub model: String,
38    pub usage: RemoteUsage,
39}
40
41#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
42pub struct RemoteTurnActivity {
43    pub protocol_version: u32,
44    pub sequence: u64,
45    pub id: String,
46    pub correlation_id: String,
47    #[serde(flatten)]
48    pub event: RemoteTurnEvent,
49}
50
51impl RemoteTurnActivity {
52    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
53        ensure_protocol_version(self.protocol_version)?;
54        require_non_empty("RemoteTurnActivity", "id", &self.id)?;
55        require_non_empty("RemoteTurnActivity", "correlation_id", &self.correlation_id)
56    }
57}
58
59#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
60#[serde(tag = "type", rename_all = "snake_case")]
61pub enum RemoteTurnEvent {
62    ModelRequestStarted {
63        protocol_iteration: usize,
64    },
65    AssistantProseDelta {
66        text: String,
67    },
68    ReasoningDelta {
69        text: String,
70    },
71    ModelAttemptReset {
72        assistant_prose_correlation_ids: Vec<String>,
73        reasoning_correlation_ids: Vec<String>,
74    },
75    CodeBlockStarted {
76        language: String,
77        code: String,
78        #[serde(default, skip_serializing_if = "Option::is_none")]
79        graph_key: Option<String>,
80    },
81    CodeBlockCompleted {
82        language: String,
83        output: String,
84        #[serde(default, skip_serializing_if = "Option::is_none")]
85        error: Option<String>,
86        success: bool,
87        duration_ms: u64,
88        tool_call_ids: Vec<String>,
89        #[serde(default, skip_serializing_if = "Option::is_none")]
90        graph_key: Option<String>,
91    },
92    ToolCallStarted {
93        #[serde(default, skip_serializing_if = "Option::is_none")]
94        call_id: Option<String>,
95        name: String,
96        args: serde_json::Value,
97        #[serde(default, skip_serializing_if = "Option::is_none")]
98        graph_key: Option<String>,
99        #[serde(default, skip_serializing_if = "Option::is_none")]
100        parent_call_id: Option<String>,
101    },
102    ToolCallCompleted {
103        #[serde(default, skip_serializing_if = "Option::is_none")]
104        call_id: Option<String>,
105        name: String,
106        args: serde_json::Value,
107        output: serde_json::Value,
108        duration_ms: u64,
109        #[serde(default, skip_serializing_if = "Option::is_none")]
110        graph_key: Option<String>,
111        #[serde(default, skip_serializing_if = "Option::is_none")]
112        parent_call_id: Option<String>,
113    },
114    FinalValue {
115        value: serde_json::Value,
116    },
117    ToolValue {
118        tool_name: String,
119        value: serde_json::Value,
120    },
121    Usage {
122        protocol_iteration: usize,
123        usage: RemoteUsage,
124        cumulative: RemoteUsage,
125    },
126    ChildUsage {
127        session_id: String,
128        source: String,
129        model: String,
130        protocol_iteration: usize,
131        usage: RemoteUsage,
132        cumulative: RemoteUsage,
133    },
134    RetryStatus {
135        wait_seconds: u64,
136        attempt: usize,
137        max_attempts: usize,
138        reason: String,
139    },
140    RuntimeDiagnostic {
141        kind: String,
142        data: serde_json::Value,
143    },
144    Error {
145        message: String,
146    },
147}