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 },
100 SessionNode {
101 session_id: String,
102 node_id: String,
103 },
104}
105
106#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
107#[serde(rename_all = "snake_case")]
108pub enum RemoteTurnStatus {
109 #[default]
110 Completed,
111 Failed,
112 Cancelled,
113 InProgress,
114}
115
116#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
117#[serde(tag = "type", rename_all = "snake_case")]
118pub enum RemoteTurnOutcome {
119 Finished { finish: RemoteTurnFinish },
120 AgentFrameSwitch { frame_id: String, task: String },
121 Stopped { stop: RemoteTurnStop },
122}
123
124impl Default for RemoteTurnOutcome {
125 fn default() -> Self {
126 Self::Stopped {
127 stop: RemoteTurnStop::Incomplete,
128 }
129 }
130}
131
132#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
133#[serde(tag = "type", rename_all = "snake_case")]
134pub enum RemoteTurnFinish {
135 AssistantMessage {
136 text: String,
137 },
138 FinalValue {
139 value: serde_json::Value,
140 },
141 ToolValue {
142 tool_name: String,
143 value: serde_json::Value,
144 },
145}
146
147#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
148#[serde(tag = "type", rename_all = "snake_case")]
149pub enum RemoteTurnStop {
150 Cancelled,
151 Incomplete,
152 InvalidInput,
153 MaxTurns,
154 ToolFailure,
155 ProviderError,
156 PluginAbort,
157 RuntimeError,
158 SubmittedError {
159 value: serde_json::Value,
160 },
161 ToolError {
162 tool_name: String,
163 value: serde_json::Value,
164 },
165}
166
167#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
168pub struct RemoteAssistantOutput {
169 #[serde(default)]
170 pub safe_text: String,
171 #[serde(default)]
172 pub raw_text: String,
173 #[serde(default)]
174 pub state: RemoteAssistantOutputState,
175}
176
177#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
178#[serde(rename_all = "snake_case")]
179pub enum RemoteAssistantOutputState {
180 #[default]
181 Usable,
182 EmptyOutput,
183 TracebackOnly,
184 RecoveredFromError,
185}
186
187#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
188pub struct RemoteTurnUsageSummary {
189 #[serde(default)]
190 pub parent: RemoteUsage,
191 #[serde(default, skip_serializing_if = "Vec::is_empty")]
192 pub children: Vec<RemoteTokenLedgerEntry>,
193 #[serde(default)]
194 pub total: RemoteUsage,
195}
196
197#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
198pub struct RemoteExecutionSummary {
199 #[serde(default)]
200 pub had_tool_calls: bool,
201 #[serde(default)]
202 pub had_code_execution: bool,
203 #[serde(default)]
206 pub started_at_ms: u64,
207 #[serde(default)]
210 pub duration_ms: u64,
211}
212
213#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
214pub struct RemoteToolCallSummary {
215 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub call_id: Option<String>,
217 pub tool_name: String,
218 #[serde(default)]
219 pub args: serde_json::Value,
220 pub outcome: RemoteToolCallOutcome,
221 pub duration_ms: u64,
222}
223
224#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
225#[serde(tag = "status", content = "payload", rename_all = "snake_case")]
226pub enum RemoteToolCallOutcome {
227 Success(serde_json::Value),
228 Failure(serde_json::Value),
229 Cancelled(serde_json::Value),
230}
231
232#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
233pub struct RemoteTurnIssue {
234 pub kind: String,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub code: Option<String>,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub terminal_reason: Option<RemoteLlmTerminalReason>,
239 pub message: String,
240 #[serde(default, skip_serializing_if = "Option::is_none")]
241 pub raw: Option<String>,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
244 pub retryable: Option<bool>,
245 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub provider_failure_kind: Option<RemoteProviderFailureKind>,
249}