1use std::collections::HashMap;
6
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use crate::ensure_protocol_version;
11use crate::llm::{RemoteLlmTerminalReason, RemoteProviderFailureKind};
12use crate::registry_errors::{RemoteProtocolError, require_non_empty};
13use crate::turn_control::RemoteTurnCancellationEvidence;
14use crate::usage_activity::{RemoteTokenLedgerEntry, RemoteTurnActivity, RemoteUsage};
15
16#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
17pub struct RemoteTurnResult {
18 pub protocol_version: u32,
19 pub session_id: String,
20 pub turn_id: String,
21 pub status: RemoteTurnStatus,
22 pub outcome: RemoteTurnOutcome,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub cancellation: Option<RemoteTurnCancellationEvidence>,
26 pub assistant_output: RemoteAssistantOutput,
27 #[serde(default)]
28 pub usage: RemoteTurnUsageSummary,
29 #[serde(default)]
30 pub execution: RemoteExecutionSummary,
31 #[serde(default, skip_serializing_if = "Vec::is_empty")]
32 pub tool_calls: Vec<RemoteToolCallSummary>,
33 #[serde(default, skip_serializing_if = "Vec::is_empty")]
34 pub issues: Vec<RemoteTurnIssue>,
35 #[serde(default, skip_serializing_if = "Vec::is_empty")]
36 pub activities: Vec<RemoteTurnActivity>,
37 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
38 pub metadata: HashMap<String, serde_json::Value>,
39}
40
41impl RemoteTurnResult {
42 pub fn validate(&self) -> Result<(), RemoteProtocolError> {
43 ensure_protocol_version(self.protocol_version)?;
44 require_non_empty("RemoteTurnResult", "session_id", &self.session_id)?;
45 require_non_empty("RemoteTurnResult", "turn_id", &self.turn_id)?;
46 if (self.status == RemoteTurnStatus::Cancelled) != self.cancellation.is_some() {
47 return Err(RemoteProtocolError::InvalidEnvelope {
48 type_name: "RemoteTurnResult",
49 message: "cancellation evidence must be present if and only if status is cancelled"
50 .to_string(),
51 });
52 }
53 if let Some(cancellation) = self.cancellation.as_ref() {
54 cancellation.validate()?;
55 }
56 for activity in &self.activities {
57 if activity.protocol_version != self.protocol_version {
58 return Err(RemoteProtocolError::MismatchedNestedProtocolVersion {
59 parent: "RemoteTurnResult",
60 child: "activities",
61 parent_version: self.protocol_version,
62 child_version: activity.protocol_version,
63 });
64 }
65 activity.validate()?;
66 }
67 Ok(())
68 }
69}
70
71#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
72#[serde(tag = "type", rename_all = "snake_case")]
73pub enum RemoteCausalRef {
74 Turn {
75 session_id: String,
76 turn_id: String,
77 },
78 Effect {
79 session_id: String,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 turn_id: Option<String>,
82 effect_id: String,
83 },
84 ToolCall {
85 session_id: String,
86 call_id: String,
87 },
88 Process {
89 process_id: String,
90 },
91 ProcessEvent {
92 process_id: String,
93 sequence: u64,
94 },
95 TriggerOccurrence {
96 occurrence_id: String,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 subscription_id: Option<String>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 subscription_incarnation: Option<String>,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
102 subscription_revision: Option<u64>,
103 },
104 SessionNode {
105 session_id: String,
106 node_id: String,
107 },
108}
109
110#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
111#[serde(rename_all = "snake_case")]
112pub enum RemoteTurnStatus {
113 #[default]
114 Completed,
115 Failed,
116 Cancelled,
117 InProgress,
118}
119
120#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
121#[serde(tag = "type", rename_all = "snake_case")]
122pub enum RemoteTurnOutcome {
123 Finished { finish: RemoteTurnFinish },
124 AgentFrameSwitch { frame_id: String, task: String },
125 Stopped { stop: RemoteTurnStop },
126}
127
128impl Default for RemoteTurnOutcome {
129 fn default() -> Self {
130 Self::Stopped {
131 stop: RemoteTurnStop::Incomplete,
132 }
133 }
134}
135
136#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
137#[serde(tag = "type", rename_all = "snake_case")]
138pub enum RemoteTurnFinish {
139 AssistantMessage {
140 text: String,
141 },
142 FinalValue {
143 value: serde_json::Value,
144 },
145 ToolValue {
146 tool_name: String,
147 value: serde_json::Value,
148 },
149}
150
151#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
152#[serde(tag = "type", rename_all = "snake_case")]
153pub enum RemoteTurnStop {
154 Cancelled,
155 Incomplete,
156 InvalidInput,
157 MaxTurns,
158 ToolFailure,
159 ProviderError,
160 PluginAbort,
161 RuntimeError,
162 SubmittedError {
163 value: serde_json::Value,
164 },
165 ToolError {
166 tool_name: String,
167 value: serde_json::Value,
168 },
169}
170
171#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
172pub struct RemoteAssistantOutput {
173 #[serde(default)]
174 pub safe_text: String,
175 #[serde(default)]
176 pub raw_text: String,
177 #[serde(default)]
178 pub state: RemoteAssistantOutputState,
179}
180
181#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
182#[serde(rename_all = "snake_case")]
183pub enum RemoteAssistantOutputState {
184 #[default]
185 Usable,
186 EmptyOutput,
187 TracebackOnly,
188 RecoveredFromError,
189}
190
191#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
192pub struct RemoteTurnUsageSummary {
193 #[serde(default)]
194 pub parent: RemoteUsage,
195 #[serde(default, skip_serializing_if = "Vec::is_empty")]
196 pub children: Vec<RemoteTokenLedgerEntry>,
197 #[serde(default)]
198 pub total: RemoteUsage,
199}
200
201#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
202pub struct RemoteExecutionSummary {
203 #[serde(default)]
204 pub had_tool_calls: bool,
205 #[serde(default)]
206 pub had_code_execution: bool,
207 #[serde(default)]
210 pub started_at_ms: u64,
211 #[serde(default)]
214 pub duration_ms: u64,
215}
216
217#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
218pub struct RemoteToolCallSummary {
219 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub call_id: Option<String>,
221 pub tool_name: String,
222 #[serde(default)]
223 pub args: serde_json::Value,
224 pub outcome: RemoteToolCallOutcome,
225 pub duration_ms: u64,
226}
227
228#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
229#[serde(tag = "status", content = "payload", rename_all = "snake_case")]
230pub enum RemoteToolCallOutcome {
231 Success(serde_json::Value),
232 Failure(serde_json::Value),
233 Cancelled(serde_json::Value),
234}
235
236#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
237pub struct RemoteTurnIssue {
238 pub kind: String,
239 #[serde(default, skip_serializing_if = "Option::is_none")]
240 pub code: Option<String>,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub terminal_reason: Option<RemoteLlmTerminalReason>,
243 pub message: String,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub raw: Option<String>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub retryable: Option<bool>,
249 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub provider_failure_kind: Option<RemoteProviderFailureKind>,
253}