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