1use std::collections::BTreeMap;
8
9use runtime_types::{ConversationId, ExecutionId, OperationId, RuntimeInstanceId, WorkspaceId};
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 ApiErrorBody, EventPayload, ExecutionOptions, ExecutionState, ExecutionView,
14 ModelGenerationOptions, ModelToolChoice, RuntimeInput, SubmitInputRequest,
15};
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18#[serde(rename_all = "camelCase", deny_unknown_fields)]
19pub struct CreateAgentTaskRequest {
20 pub runtime_id: RuntimeInstanceId,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub conversation_id: Option<ConversationId>,
23 pub input: AgentTaskInput,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub workspace: Option<AgentTaskWorkspace>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub model: Option<AgentTaskModel>,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub instructions: Option<String>,
30 #[serde(default)]
31 pub limits: AgentTaskLimits,
32 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
33 pub metadata: BTreeMap<String, String>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37#[serde(rename_all = "camelCase", deny_unknown_fields)]
38pub struct AgentTaskInput {
39 pub text: String,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
43#[serde(rename_all = "camelCase", deny_unknown_fields)]
44pub struct AgentTaskWorkspace {
45 pub id: WorkspaceId,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
49#[serde(rename_all = "camelCase", deny_unknown_fields)]
50pub struct AgentTaskModel {
51 pub id: String,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub max_output_tokens: Option<u32>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub temperature: Option<f32>,
56 #[serde(default, skip_serializing_if = "Vec::is_empty")]
57 pub stop_sequences: Vec<String>,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub response_format: Option<serde_json::Value>,
60 #[serde(default)]
61 pub tool_choice: ModelToolChoice,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
65#[serde(rename_all = "camelCase", deny_unknown_fields)]
66pub struct AgentTaskLimits {
67 #[serde(default = "task_default_deadline")]
68 pub deadline_seconds: u64,
69 #[serde(default = "task_default_model_turns")]
70 pub max_model_turns: usize,
71 #[serde(default = "task_default_tool_calls")]
72 pub max_tool_calls: usize,
73}
74
75impl Default for AgentTaskLimits {
76 fn default() -> Self {
77 Self {
78 deadline_seconds: task_default_deadline(),
79 max_model_turns: task_default_model_turns(),
80 max_tool_calls: task_default_tool_calls(),
81 }
82 }
83}
84
85fn task_default_deadline() -> u64 {
86 900
87}
88fn task_default_model_turns() -> usize {
89 64
90}
91fn task_default_tool_calls() -> usize {
92 256
93}
94
95impl CreateAgentTaskRequest {
96 pub fn into_execution(self) -> crate::CreateExecutionRequest {
97 let generation = self
98 .model
99 .as_ref()
100 .map(|model| ModelGenerationOptions {
101 max_output_tokens: model.max_output_tokens,
102 temperature: model.temperature,
103 stop_sequences: model.stop_sequences.clone(),
104 response_format: model.response_format.clone(),
105 tool_choice: model.tool_choice.clone(),
106 })
107 .unwrap_or_default();
108 crate::CreateExecutionRequest {
109 runtime_instance_id: self.runtime_id,
110 conversation_id: self.conversation_id,
111 input: RuntimeInput::UserMessage {
112 text: self.input.text,
113 },
114 workspace_id: self.workspace.map(|workspace| workspace.id),
115 model: self.model.map(|model| model.id),
116 instructions: self.instructions,
117 metadata: self.metadata,
118 generation,
119 options: ExecutionOptions {
120 deadline_seconds: self.limits.deadline_seconds,
121 max_model_turns: self.limits.max_model_turns,
122 max_tool_calls: self.limits.max_tool_calls,
123 },
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129#[serde(rename_all = "camelCase", deny_unknown_fields)]
130pub struct AgentTask {
131 pub id: ExecutionId,
132 pub object: String,
133 pub runtime_id: RuntimeInstanceId,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub conversation_id: Option<ConversationId>,
136 #[serde(default, skip_serializing_if = "Option::is_none")]
137 pub workspace: Option<AgentTaskWorkspace>,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub model: Option<String>,
140 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
141 pub metadata: BTreeMap<String, String>,
142 pub status: AgentTaskStatus,
143 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub output: Option<AgentTaskOutput>,
145 #[serde(default, skip_serializing_if = "Option::is_none")]
146 pub error: Option<ApiErrorBody>,
147 pub created_at_ms: i64,
148 pub updated_at_ms: i64,
149 pub links: AgentTaskLinks,
150}
151
152#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
153#[serde(rename_all = "snake_case")]
154pub enum AgentTaskStatus {
155 Queued,
156 Running,
157 WaitingForInput,
158 Finalizing,
159 Completed,
160 Failed,
161 Canceled,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
165#[serde(rename_all = "camelCase", deny_unknown_fields)]
166pub struct AgentTaskOutput {
167 pub text: String,
168 pub usage: AgentTaskUsage,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
172#[serde(rename_all = "camelCase", deny_unknown_fields)]
173pub struct AgentTaskUsage {
174 pub model_turns: usize,
175 pub tool_calls: usize,
176 pub input_tokens: Option<u64>,
177 pub output_tokens: Option<u64>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
181#[serde(rename_all = "camelCase", deny_unknown_fields)]
182pub struct AgentTaskLinks {
183 pub self_url: String,
184 pub events: String,
185 pub stream: String,
186 pub websocket: String,
187 pub input: String,
188 pub cancel: String,
189}
190
191impl From<ExecutionView> for AgentTask {
192 fn from(view: ExecutionView) -> Self {
193 let base = format!("/v1/agent/tasks/{}", view.id);
194 let output = view.outcome.map(|outcome| AgentTaskOutput {
195 text: outcome.answer,
196 usage: AgentTaskUsage {
197 model_turns: outcome.model_turns,
198 tool_calls: outcome.tool_calls,
199 input_tokens: outcome.input_tokens,
200 output_tokens: outcome.output_tokens,
201 },
202 });
203 let error = view.failure.map(|failure| ApiErrorBody {
204 code: failure.code,
205 message: failure.message,
206 request_id: None,
207 details: Some(serde_json::json!({"retryable": failure.retryable})),
208 });
209 Self {
210 id: view.id,
211 object: "agent.task".into(),
212 runtime_id: view.runtime_instance_id,
213 conversation_id: view.conversation_id,
214 workspace: view.workspace_id.map(|id| AgentTaskWorkspace { id }),
215 model: view.model,
216 metadata: view.metadata,
217 status: view.state.into(),
218 output,
219 error,
220 created_at_ms: view.created_at_ms,
221 updated_at_ms: view.updated_at_ms,
222 links: AgentTaskLinks {
223 self_url: base.clone(),
224 events: format!("{base}/events"),
225 stream: format!("{base}/stream"),
226 websocket: format!("{base}/ws"),
227 input: format!("{base}/inputs"),
228 cancel: format!("{base}/cancel"),
229 },
230 }
231 }
232}
233
234impl From<ExecutionState> for AgentTaskStatus {
235 fn from(value: ExecutionState) -> Self {
236 match value {
237 ExecutionState::Queued => Self::Queued,
238 ExecutionState::Running => Self::Running,
239 ExecutionState::WaitingForInput => Self::WaitingForInput,
240 ExecutionState::Finalizing => Self::Finalizing,
241 ExecutionState::Completed => Self::Completed,
242 ExecutionState::Failed => Self::Failed,
243 ExecutionState::Canceled => Self::Canceled,
244 }
245 }
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
249#[serde(rename_all = "camelCase", deny_unknown_fields)]
250pub struct AgentTaskEvent {
251 pub task_id: ExecutionId,
252 pub sequence: u64,
253 pub created_at_ms: i64,
254 pub kind: String,
255 pub data: EventPayload,
256}
257
258impl From<crate::ExecutionEvent> for AgentTaskEvent {
259 fn from(event: crate::ExecutionEvent) -> Self {
260 let kind = match &event.payload {
261 EventPayload::ExecutionQueued => "task.queued",
262 EventPayload::ExecutionStarted => "task.started",
263 EventPayload::ModelStarted { .. } => "model.started",
264 EventPayload::ModelCompleted { .. } => "model.completed",
265 EventPayload::ToolStarted { .. } => "tool.started",
266 EventPayload::ToolCompleted { .. } => "tool.completed",
267 EventPayload::InteractionRequired { .. } => "task.input_required",
268 EventPayload::InteractionReceived { .. } => "task.input_received",
269 EventPayload::Warning { .. } => "task.warning",
270 EventPayload::ExecutionCompleted { .. } => "task.completed",
271 EventPayload::ExecutionFailed { .. } => "task.failed",
272 EventPayload::ExecutionCanceled => "task.canceled",
273 };
274 Self {
275 task_id: event.execution_id,
276 sequence: event.sequence,
277 created_at_ms: event.created_at_ms,
278 kind: kind.into(),
279 data: event.payload,
280 }
281 }
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
285#[serde(rename_all = "camelCase", deny_unknown_fields)]
286pub struct AgentTaskEventPage {
287 pub items: Vec<AgentTaskEvent>,
288 #[serde(default, skip_serializing_if = "Option::is_none")]
289 pub next_after: Option<u64>,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
293#[serde(
294 tag = "type",
295 rename_all = "snake_case",
296 rename_all_fields = "camelCase",
297 deny_unknown_fields
298)]
299pub enum AgentTaskWebSocketClientMessage {
300 SubmitInput {
301 operation_id: OperationId,
302 input: RuntimeInput,
303 },
304 Cancel {},
305 Ack {
308 sequence: u64,
309 },
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
313#[serde(
314 tag = "type",
315 rename_all = "snake_case",
316 rename_all_fields = "camelCase",
317 deny_unknown_fields
318)]
319pub enum AgentTaskWebSocketServerMessage {
320 Connected {
321 task: AgentTask,
322 #[serde(default, skip_serializing_if = "Option::is_none")]
323 next_after: Option<u64>,
324 heartbeat_seconds: u64,
325 },
326 Event {
327 event: AgentTaskEvent,
328 },
329 Acknowledged {
330 sequence: u64,
331 },
332 Task {
333 task: AgentTask,
334 },
335 Error {
336 error: ApiErrorBody,
337 },
338}
339
340impl AgentTaskWebSocketClientMessage {
341 pub fn into_submit(self) -> Option<SubmitInputRequest> {
342 match self {
343 Self::SubmitInput {
344 operation_id,
345 input,
346 } => Some(SubmitInputRequest {
347 operation_id,
348 input,
349 }),
350 Self::Cancel {} | Self::Ack { .. } => None,
351 }
352 }
353}