1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::BTreeSet;
4
5use crate::agent_events::{ToolCallErrorCategory, ToolCallStatus, ToolExecutor};
6use crate::tool_annotations::{SideEffectLevel, ToolAnnotations};
7
8use super::manifest::{BindingManifest, BindingManifestEntry};
9
10pub const COMPOSITION_EXECUTION_SCHEMA_VERSION: u32 = 1;
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum CompositionFailureCategory {
18 UnsupportedLanguage,
20 SchemaValidation,
22 PolicyDenied,
25 ChildToolError,
27 ExecutionError,
29 Timeout,
31 Cancelled,
33 Unknown,
35}
36
37impl CompositionFailureCategory {
38 pub const ALL: [Self; 8] = [
39 Self::UnsupportedLanguage,
40 Self::SchemaValidation,
41 Self::PolicyDenied,
42 Self::ChildToolError,
43 Self::ExecutionError,
44 Self::Timeout,
45 Self::Cancelled,
46 Self::Unknown,
47 ];
48
49 pub fn as_str(self) -> &'static str {
50 match self {
51 Self::UnsupportedLanguage => "unsupported_language",
52 Self::SchemaValidation => "schema_validation",
53 Self::PolicyDenied => "policy_denied",
54 Self::ChildToolError => "child_tool_error",
55 Self::ExecutionError => "execution_error",
56 Self::Timeout => "timeout",
57 Self::Cancelled => "cancelled",
58 Self::Unknown => "unknown",
59 }
60 }
61}
62
63#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
65#[serde(default)]
66pub struct CompositionRunEnvelope {
67 pub run_id: String,
69 pub language: String,
71 pub snippet_hash: String,
73 pub binding_manifest_hash: String,
75 pub requested_side_effect_ceiling: SideEffectLevel,
77 pub stdout: Option<String>,
79 pub stderr: Option<String>,
81 pub artifacts: Vec<Value>,
83 pub result: Option<Value>,
85 pub failure_category: Option<CompositionFailureCategory>,
87 pub error: Option<String>,
89 pub duration_ms: Option<u64>,
91 pub metadata: Value,
93}
94
95impl Default for CompositionRunEnvelope {
96 fn default() -> Self {
97 Self {
98 run_id: String::new(),
99 language: String::new(),
100 snippet_hash: String::new(),
101 binding_manifest_hash: String::new(),
102 requested_side_effect_ceiling: SideEffectLevel::ReadOnly,
103 stdout: None,
104 stderr: None,
105 artifacts: Vec::new(),
106 result: None,
107 failure_category: None,
108 error: None,
109 duration_ms: None,
110 metadata: Value::Object(serde_json::Map::new()),
111 }
112 }
113}
114
115impl CompositionRunEnvelope {
116 pub fn read_only(
117 run_id: impl Into<String>,
118 language: impl Into<String>,
119 snippet_hash: impl Into<String>,
120 binding_manifest_hash: impl Into<String>,
121 ) -> Self {
122 Self {
123 run_id: run_id.into(),
124 language: language.into(),
125 snippet_hash: snippet_hash.into(),
126 binding_manifest_hash: binding_manifest_hash.into(),
127 requested_side_effect_ceiling: SideEffectLevel::ReadOnly,
128 ..Self::default()
129 }
130 }
131}
132
133#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
138#[serde(default)]
139pub struct CompositionChildCall {
140 pub run_id: String,
141 pub tool_call_id: String,
142 pub tool_name: String,
143 pub operation_index: u64,
144 pub annotations: Option<ToolAnnotations>,
145 pub requested_side_effect_level: SideEffectLevel,
146 pub policy_context: Value,
147 pub raw_input: Value,
148}
149
150impl Default for CompositionChildCall {
151 fn default() -> Self {
152 Self {
153 run_id: String::new(),
154 tool_call_id: String::new(),
155 tool_name: String::new(),
156 operation_index: 0,
157 annotations: None,
158 requested_side_effect_level: SideEffectLevel::None,
159 policy_context: Value::Object(serde_json::Map::new()),
160 raw_input: Value::Null,
161 }
162 }
163}
164
165#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
169#[serde(default)]
170pub struct CompositionChildResult {
171 pub run_id: String,
172 pub tool_call_id: String,
173 pub tool_name: String,
174 pub operation_index: u64,
175 pub status: ToolCallStatus,
176 pub raw_output: Option<Value>,
177 pub error: Option<String>,
178 pub error_category: Option<ToolCallErrorCategory>,
179 pub executor: Option<ToolExecutor>,
180 pub duration_ms: Option<u64>,
181 pub execution_duration_ms: Option<u64>,
182 pub attempt: u32,
183 pub retry_attempts: u32,
184 pub retry_errors: Vec<String>,
185 pub retry_delays_ms: Vec<u64>,
186}
187
188impl Default for CompositionChildResult {
189 fn default() -> Self {
190 Self {
191 run_id: String::new(),
192 tool_call_id: String::new(),
193 tool_name: String::new(),
194 operation_index: 0,
195 status: ToolCallStatus::Pending,
196 raw_output: None,
197 error: None,
198 error_category: None,
199 executor: None,
200 duration_ms: None,
201 execution_duration_ms: None,
202 attempt: 1,
203 retry_attempts: 0,
204 retry_errors: Vec::new(),
205 retry_delays_ms: Vec::new(),
206 }
207 }
208}
209
210#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
211#[serde(default)]
212pub struct CompositionExecutionLimits {
213 pub max_operations: u64,
214 pub timeout_ms: Option<u64>,
215 pub max_output_bytes: u64,
216 pub max_concurrent_operations: usize,
217 pub max_concurrent_per_server: usize,
218}
219
220impl Default for CompositionExecutionLimits {
221 fn default() -> Self {
222 Self {
223 max_operations: 64,
224 timeout_ms: Some(10_000),
225 max_output_bytes: 64 * 1024,
226 max_concurrent_operations: 16,
227 max_concurrent_per_server: 4,
228 }
229 }
230}
231
232#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
233#[serde(default)]
234pub struct CompositionRetryPolicy {
235 pub max_attempts: u32,
236 pub base_delay_ms: u64,
237 pub max_delay_ms: u64,
238 pub honor_retry_after: bool,
239}
240
241impl Default for CompositionRetryPolicy {
242 fn default() -> Self {
243 Self {
244 max_attempts: 3,
245 base_delay_ms: 100,
246 max_delay_ms: 2_000,
247 honor_retry_after: true,
248 }
249 }
250}
251
252#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
253#[serde(default)]
254pub struct CompositionMcpPolicy {
255 pub trusted_servers: BTreeSet<String>,
256 pub trust_annotations: bool,
257 pub retry: CompositionRetryPolicy,
258 pub call_timeout_ms: Option<u64>,
259}
260
261#[derive(Clone, Debug, Serialize, Deserialize)]
262#[serde(default)]
263pub struct CompositionExecutionRequest {
264 pub session_id: Option<String>,
265 pub run_id: String,
266 pub language: String,
267 pub snippet: String,
268 pub manifest: BindingManifest,
269 pub requested_side_effect_ceiling: SideEffectLevel,
270 pub limits: CompositionExecutionLimits,
271 pub mcp_policy: CompositionMcpPolicy,
272 pub metadata: Value,
273}
274
275impl Default for CompositionExecutionRequest {
276 fn default() -> Self {
277 Self {
278 session_id: None,
279 run_id: String::new(),
280 language: "harn".to_string(),
281 snippet: String::new(),
282 manifest: BindingManifest::default(),
283 requested_side_effect_ceiling: SideEffectLevel::ReadOnly,
284 limits: CompositionExecutionLimits::default(),
285 mcp_policy: CompositionMcpPolicy::default(),
286 metadata: Value::Object(serde_json::Map::new()),
287 }
288 }
289}
290
291#[derive(Clone, Debug, Serialize, Deserialize)]
292pub struct CompositionExecutionReport {
293 pub schema_version: u32,
294 pub ok: bool,
295 pub run: CompositionRunEnvelope,
296 pub child_calls: Vec<CompositionChildCall>,
297 pub child_results: Vec<CompositionChildResult>,
298 pub summary: String,
299}
300
301#[derive(Clone, Debug, Serialize, Deserialize)]
302pub struct CompositionToolOutput {
303 pub value: Option<Value>,
304 pub error: Option<String>,
305 pub error_category: Option<ToolCallErrorCategory>,
306 pub executor: Option<ToolExecutor>,
307}
308
309impl CompositionToolOutput {
310 pub fn ok(value: Value) -> Self {
311 Self {
312 value: Some(value),
313 error: None,
314 error_category: None,
315 executor: Some(ToolExecutor::HarnBuiltin),
316 }
317 }
318
319 pub fn error(message: impl Into<String>, category: ToolCallErrorCategory) -> Self {
320 Self {
321 value: None,
322 error: Some(message.into()),
323 error_category: Some(category),
324 executor: Some(ToolExecutor::HarnBuiltin),
325 }
326 }
327}
328
329#[async_trait::async_trait]
330pub trait CompositionToolHost: Send + Sync {
331 async fn call(&self, binding: &BindingManifestEntry, input: Value) -> CompositionToolOutput;
332}