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