1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::agent_events::{ToolCallErrorCategory, ToolCallStatus, ToolExecutor};
5use crate::tool_annotations::{SideEffectLevel, ToolAnnotations};
6
7use super::manifest::{BindingManifest, BindingManifestEntry};
8
9pub const COMPOSITION_EXECUTION_SCHEMA_VERSION: u32 = 1;
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum CompositionFailureCategory {
17 UnsupportedLanguage,
19 SchemaValidation,
21 PolicyDenied,
24 ChildToolError,
26 ExecutionError,
28 Timeout,
30 Cancelled,
32 Unknown,
34}
35
36impl CompositionFailureCategory {
37 pub const ALL: [Self; 8] = [
38 Self::UnsupportedLanguage,
39 Self::SchemaValidation,
40 Self::PolicyDenied,
41 Self::ChildToolError,
42 Self::ExecutionError,
43 Self::Timeout,
44 Self::Cancelled,
45 Self::Unknown,
46 ];
47
48 pub fn as_str(self) -> &'static str {
49 match self {
50 Self::UnsupportedLanguage => "unsupported_language",
51 Self::SchemaValidation => "schema_validation",
52 Self::PolicyDenied => "policy_denied",
53 Self::ChildToolError => "child_tool_error",
54 Self::ExecutionError => "execution_error",
55 Self::Timeout => "timeout",
56 Self::Cancelled => "cancelled",
57 Self::Unknown => "unknown",
58 }
59 }
60}
61
62#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
64#[serde(default)]
65pub struct CompositionRunEnvelope {
66 pub run_id: String,
68 pub language: String,
70 pub snippet_hash: String,
72 pub binding_manifest_hash: String,
74 pub requested_side_effect_ceiling: SideEffectLevel,
76 pub stdout: Option<String>,
78 pub stderr: Option<String>,
80 pub artifacts: Vec<Value>,
82 pub result: Option<Value>,
84 pub failure_category: Option<CompositionFailureCategory>,
86 pub error: Option<String>,
88 pub duration_ms: Option<u64>,
90 pub metadata: Value,
92}
93
94impl Default for CompositionRunEnvelope {
95 fn default() -> Self {
96 Self {
97 run_id: String::new(),
98 language: String::new(),
99 snippet_hash: String::new(),
100 binding_manifest_hash: String::new(),
101 requested_side_effect_ceiling: SideEffectLevel::ReadOnly,
102 stdout: None,
103 stderr: None,
104 artifacts: Vec::new(),
105 result: None,
106 failure_category: None,
107 error: None,
108 duration_ms: None,
109 metadata: Value::Object(serde_json::Map::new()),
110 }
111 }
112}
113
114impl CompositionRunEnvelope {
115 pub fn read_only(
116 run_id: impl Into<String>,
117 language: impl Into<String>,
118 snippet_hash: impl Into<String>,
119 binding_manifest_hash: impl Into<String>,
120 ) -> Self {
121 Self {
122 run_id: run_id.into(),
123 language: language.into(),
124 snippet_hash: snippet_hash.into(),
125 binding_manifest_hash: binding_manifest_hash.into(),
126 requested_side_effect_ceiling: SideEffectLevel::ReadOnly,
127 ..Self::default()
128 }
129 }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
137#[serde(default)]
138pub struct CompositionChildCall {
139 pub run_id: String,
140 pub tool_call_id: String,
141 pub tool_name: String,
142 pub operation_index: u64,
143 pub annotations: Option<ToolAnnotations>,
144 pub requested_side_effect_level: SideEffectLevel,
145 pub policy_context: Value,
146 pub raw_input: Value,
147}
148
149impl Default for CompositionChildCall {
150 fn default() -> Self {
151 Self {
152 run_id: String::new(),
153 tool_call_id: String::new(),
154 tool_name: String::new(),
155 operation_index: 0,
156 annotations: None,
157 requested_side_effect_level: SideEffectLevel::None,
158 policy_context: Value::Object(serde_json::Map::new()),
159 raw_input: Value::Null,
160 }
161 }
162}
163
164#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
168#[serde(default)]
169pub struct CompositionChildResult {
170 pub run_id: String,
171 pub tool_call_id: String,
172 pub tool_name: String,
173 pub operation_index: u64,
174 pub status: ToolCallStatus,
175 pub raw_output: Option<Value>,
176 pub error: Option<String>,
177 pub error_category: Option<ToolCallErrorCategory>,
178 pub executor: Option<ToolExecutor>,
179 pub duration_ms: Option<u64>,
180 pub execution_duration_ms: Option<u64>,
181}
182
183impl Default for CompositionChildResult {
184 fn default() -> Self {
185 Self {
186 run_id: String::new(),
187 tool_call_id: String::new(),
188 tool_name: String::new(),
189 operation_index: 0,
190 status: ToolCallStatus::Pending,
191 raw_output: None,
192 error: None,
193 error_category: None,
194 executor: None,
195 duration_ms: None,
196 execution_duration_ms: None,
197 }
198 }
199}
200
201#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
202#[serde(default)]
203pub struct CompositionExecutionLimits {
204 pub max_operations: u64,
205 pub timeout_ms: Option<u64>,
206 pub max_output_bytes: u64,
207}
208
209impl Default for CompositionExecutionLimits {
210 fn default() -> Self {
211 Self {
212 max_operations: 64,
213 timeout_ms: Some(10_000),
214 max_output_bytes: 64 * 1024,
215 }
216 }
217}
218
219#[derive(Clone, Debug, Serialize, Deserialize)]
220#[serde(default)]
221pub struct CompositionExecutionRequest {
222 pub session_id: Option<String>,
223 pub run_id: String,
224 pub language: String,
225 pub snippet: String,
226 pub manifest: BindingManifest,
227 pub requested_side_effect_ceiling: SideEffectLevel,
228 pub limits: CompositionExecutionLimits,
229 pub metadata: Value,
230}
231
232impl Default for CompositionExecutionRequest {
233 fn default() -> Self {
234 Self {
235 session_id: None,
236 run_id: String::new(),
237 language: "harn".to_string(),
238 snippet: String::new(),
239 manifest: BindingManifest::default(),
240 requested_side_effect_ceiling: SideEffectLevel::ReadOnly,
241 limits: CompositionExecutionLimits::default(),
242 metadata: Value::Object(serde_json::Map::new()),
243 }
244 }
245}
246
247#[derive(Clone, Debug, Serialize, Deserialize)]
248pub struct CompositionExecutionReport {
249 pub schema_version: u32,
250 pub ok: bool,
251 pub run: CompositionRunEnvelope,
252 pub child_calls: Vec<CompositionChildCall>,
253 pub child_results: Vec<CompositionChildResult>,
254 pub summary: String,
255}
256
257#[derive(Clone, Debug, Serialize, Deserialize)]
258pub struct CompositionToolOutput {
259 pub value: Option<Value>,
260 pub error: Option<String>,
261 pub error_category: Option<ToolCallErrorCategory>,
262 pub executor: Option<ToolExecutor>,
263}
264
265impl CompositionToolOutput {
266 pub fn ok(value: Value) -> Self {
267 Self {
268 value: Some(value),
269 error: None,
270 error_category: None,
271 executor: Some(ToolExecutor::HarnBuiltin),
272 }
273 }
274
275 pub fn error(message: impl Into<String>, category: ToolCallErrorCategory) -> Self {
276 Self {
277 value: None,
278 error: Some(message.into()),
279 error_category: Some(category),
280 executor: Some(ToolExecutor::HarnBuiltin),
281 }
282 }
283}
284
285#[async_trait::async_trait(?Send)]
286pub trait CompositionToolHost {
287 async fn call(&self, binding: &BindingManifestEntry, input: Value) -> CompositionToolOutput;
288}