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    CodeBlockStarted {
72        language: String,
73        code: String,
74        #[serde(default, skip_serializing_if = "Option::is_none")]
75        graph_key: Option<String>,
76    },
77    CodeBlockCompleted {
78        language: String,
79        output: String,
80        #[serde(default, skip_serializing_if = "Option::is_none")]
81        error: Option<String>,
82        success: bool,
83        duration_ms: u64,
84        tool_call_ids: Vec<String>,
85        #[serde(default, skip_serializing_if = "Option::is_none")]
86        graph_key: Option<String>,
87    },
88    ToolCallStarted {
89        #[serde(default, skip_serializing_if = "Option::is_none")]
90        call_id: Option<String>,
91        name: String,
92        args: serde_json::Value,
93        #[serde(default, skip_serializing_if = "Option::is_none")]
94        graph_key: Option<String>,
95        #[serde(default, skip_serializing_if = "Option::is_none")]
96        parent_call_id: Option<String>,
97    },
98    ToolCallCompleted {
99        #[serde(default, skip_serializing_if = "Option::is_none")]
100        call_id: Option<String>,
101        name: String,
102        args: serde_json::Value,
103        output: serde_json::Value,
104        duration_ms: u64,
105        #[serde(default, skip_serializing_if = "Option::is_none")]
106        graph_key: Option<String>,
107        #[serde(default, skip_serializing_if = "Option::is_none")]
108        parent_call_id: Option<String>,
109    },
110    FinalValue {
111        value: serde_json::Value,
112    },
113    ToolValue {
114        tool_name: String,
115        value: serde_json::Value,
116    },
117    Usage {
118        protocol_iteration: usize,
119        usage: RemoteUsage,
120        cumulative: RemoteUsage,
121    },
122    ChildUsage {
123        session_id: String,
124        source: String,
125        model: String,
126        protocol_iteration: usize,
127        usage: RemoteUsage,
128        cumulative: RemoteUsage,
129    },
130    RetryStatus {
131        wait_seconds: u64,
132        attempt: usize,
133        max_attempts: usize,
134        reason: String,
135    },
136    RuntimeDiagnostic {
137        kind: String,
138        data: serde_json::Value,
139    },
140    Error {
141        message: String,
142    },
143}