返回 CodeWhale
lib.rs
根目录 / crates / protocol / src / lib.rs
1 use std::path::PathBuf;
2
3 use serde::{Deserialize, Serialize};
4 use serde_json::Value;
5
6 pub mod agent_run;
7 pub mod fleet;
8 pub mod runtime;
9 pub mod workroom;
10
11 /// Common trait for lifecycle status enums across the protocol layer.
12 ///
13 /// Every status enum — thread, goal, fleet run, worker, and job status —
14 /// implements this trait so generic code can ask three universal questions
15 /// without matching on every variant.
16 pub trait Status {
17 /// Returns `true` when this status represents a final, non-progressable state
18 /// (e.g. Completed, Failed, Cancelled, Archived, Retired).
19 fn is_terminal(&self) -> bool;
20
21 /// Returns `true` when work is currently in-flight
22 /// (e.g. Running, Active, Busy, Queued, Pending).
23 fn is_active(&self) -> bool;
24
25 /// Returns `true` when the item has been explicitly paused by the user
26 /// or system (e.g. Paused).
27 fn is_paused(&self) -> bool;
28 }
29
30 #[derive(Debug, Clone, Serialize, Deserialize)]
31 pub struct Envelope<T> {
32 pub request_id: String,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub thread_id: Option<String>,
35 pub body: T,
36 }
37
38 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
39 #[serde(rename_all = "snake_case")]
40 pub enum ThreadStatus {
41 Running,
42 Idle,
43 Completed,
44 Failed,
45 Paused,
46 Archived,
47 }
48
49 impl Status for ThreadStatus {
50 fn is_terminal(&self) -> bool {
51 matches!(self, Self::Completed | Self::Failed | Self::Archived)
52 }
53 fn is_active(&self) -> bool {
54 matches!(self, Self::Running)
55 }
56 fn is_paused(&self) -> bool {
57 matches!(self, Self::Paused)
58 }
59 }
60
61 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62 #[serde(rename_all = "snake_case")]
63 pub enum SessionSource {
64 Interactive,
65 Resume,
66 Fork,
67 Api,
68 Unknown,
69 }
70
71 #[derive(Debug, Clone, Serialize, Deserialize)]
72 pub struct Thread {
73 pub id: String,
74 pub preview: String,
75 pub ephemeral: bool,
76 pub model_provider: String,
77 pub created_at: i64,
78 pub updated_at: i64,
79 pub status: ThreadStatus,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub path: Option<PathBuf>,
82 pub cwd: PathBuf,
83 pub cli_version: String,
84 pub source: SessionSource,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub name: Option<String>,
87 }
88
89 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
90 #[serde(rename_all = "snake_case")]
91 pub enum ThreadGoalStatus {
92 Active,
93 Paused,
94 Blocked,
95 UsageLimited,
96 BudgetLimited,
97 Complete,
98 }
99
100 impl Status for ThreadGoalStatus {
101 fn is_terminal(&self) -> bool {
102 matches!(self, Self::Complete)
103 }
104 fn is_active(&self) -> bool {
105 matches!(self, Self::Active)
106 }
107 fn is_paused(&self) -> bool {
108 matches!(self, Self::Paused)
109 }
110 }
111
112 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
113 pub struct ThreadGoal {
114 pub thread_id: String,
115 pub goal_id: String,
116 pub objective: String,
117 pub status: ThreadGoalStatus,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub token_budget: Option<i64>,
120 pub tokens_used: i64,
121 pub time_used_seconds: i64,
122 pub continuation_count: i64,
123 pub created_at: i64,
124 pub updated_at: i64,
125 }
126
127 #[derive(Debug, Clone, Serialize, Deserialize)]
128 pub struct ThreadStartParams {
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub model: Option<String>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub model_provider: Option<String>,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub cwd: Option<PathBuf>,
135 #[serde(default)]
136 pub persist_extended_history: bool,
137 }
138
139 #[derive(Debug, Clone, Serialize, Deserialize)]
140 pub struct ThreadResumeParams {
141 pub thread_id: String,
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub history: Option<Vec<Value>>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub path: Option<PathBuf>,
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub model: Option<String>,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub model_provider: Option<String>,
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub cwd: Option<PathBuf>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub approval_policy: Option<String>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub sandbox: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub config: Option<Value>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub base_instructions: Option<String>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub developer_instructions: Option<String>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub personality: Option<String>,
164 #[serde(default)]
165 pub persist_extended_history: bool,
166 }
167
168 #[derive(Debug, Clone, Serialize, Deserialize)]
169 pub struct ThreadForkParams {
170 pub thread_id: String,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub path: Option<PathBuf>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub model: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub model_provider: Option<String>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub cwd: Option<PathBuf>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub approval_policy: Option<String>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub sandbox: Option<String>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub config: Option<Value>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub base_instructions: Option<String>,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub developer_instructions: Option<String>,
189 #[serde(default)]
190 pub persist_extended_history: bool,
191 }
192
193 #[derive(Debug, Clone, Serialize, Deserialize)]
194 pub struct ThreadListParams {
195 #[serde(default)]
196 pub include_archived: bool,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub limit: Option<usize>,
199 }
200
201 #[derive(Debug, Clone, Serialize, Deserialize)]
202 pub struct ThreadReadParams {
203 pub thread_id: String,
204 }
205
206 #[derive(Debug, Clone, Serialize, Deserialize)]
207 pub struct ThreadSetNameParams {
208 pub thread_id: String,
209 pub name: String,
210 }
211
212 #[derive(Debug, Clone, Serialize, Deserialize)]
213 pub struct ThreadGoalSetParams {
214 pub thread_id: String,
215 pub objective: String,
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub token_budget: Option<i64>,
218 }
219
220 #[derive(Debug, Clone, Serialize, Deserialize)]
221 pub struct ThreadGoalGetParams {
222 pub thread_id: String,
223 }
224
225 #[derive(Debug, Clone, Serialize, Deserialize)]
226 pub struct ThreadGoalClearParams {
227 pub thread_id: String,
228 }
229
230 #[derive(Debug, Clone, Serialize, Deserialize)]
231 pub struct ThreadGoalProgressParams {
232 pub thread_id: String,
233 #[serde(default)]
234 pub token_delta: i64,
235 #[serde(default)]
236 pub time_delta_seconds: i64,
237 #[serde(default)]
238 pub record_continuation: bool,
239 }
240
241 #[derive(Debug, Clone, Serialize, Deserialize)]
242 #[serde(tag = "kind", rename_all = "snake_case")]
243 pub enum ThreadRequest {
244 Create {
245 #[serde(default)]
246 metadata: Value,
247 },
248 Start(ThreadStartParams),
249 Resume(ThreadResumeParams),
250 Fork(ThreadForkParams),
251 List(ThreadListParams),
252 Read(ThreadReadParams),
253 SetName(ThreadSetNameParams),
254 GoalSet(ThreadGoalSetParams),
255 GoalGet(ThreadGoalGetParams),
256 GoalClear(ThreadGoalClearParams),
257 GoalRecordProgress(ThreadGoalProgressParams),
258 Archive {
259 thread_id: String,
260 },
261 Unarchive {
262 thread_id: String,
263 },
264 Message {
265 thread_id: String,
266 input: String,
267 },
268 }
269
270 /// Response to a [`ThreadRequest`].
271 #[derive(Debug, Clone, Serialize, Deserialize)]
272 pub struct ThreadResponse {
273 /// The thread this response pertains to.
274 pub thread_id: String,
275 /// Human-readable status string (e.g. `"ok"`, `"error"`).
276 pub status: String,
277 /// The thread details, when a single thread is returned.
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub thread: Option<Thread>,
280 /// List of threads, populated by `List` requests.
281 #[serde(default)]
282 pub threads: Vec<Thread>,
283 /// Thread goal returned by goal get/set requests.
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub goal: Option<ThreadGoal>,
286 /// The model used for the thread, if applicable.
287 #[serde(skip_serializing_if = "Option::is_none")]
288 pub model: Option<String>,
289 /// The model provider used for the thread.
290 #[serde(skip_serializing_if = "Option::is_none")]
291 pub model_provider: Option<String>,
292 /// The working directory of the thread.
293 #[serde(skip_serializing_if = "Option::is_none")]
294 pub cwd: Option<PathBuf>,
295 /// The active approval policy.
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub approval_policy: Option<String>,
298 /// The active sandbox configuration.
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub sandbox: Option<String>,
301 /// Streaming events associated with this response.
302 #[serde(default)]
303 pub events: Vec<EventFrame>,
304 /// Arbitrary additional response data.
305 #[serde(default)]
306 pub data: Value,
307 }
308
309 /// Application-level requests that are not tied to a specific thread.
310 #[derive(Debug, Clone, Serialize, Deserialize)]
311 #[serde(tag = "kind", rename_all = "snake_case")]
312 pub enum AppRequest {
313 /// Query the server's capabilities.
314 Capabilities,
315 /// Read a configuration value by key.
316 ConfigGet { key: String },
317 /// Set a configuration key to a value.
318 ConfigSet { key: String, value: String },
319 /// Remove a configuration key.
320 ConfigUnset { key: String },
321 /// List all configuration entries.
322 ConfigList,
323 /// Reload configuration from disk and apply to the live runtime.
324 ///
325 /// Re-reads both `config.toml` and the sibling `permissions.toml`,
326 /// refreshing the live `Runtime.config` and `Runtime.exec_policy`
327 /// so headless clients can pick up external config-file *and*
328 /// permission-rule edits without restarting.
329 ///
330 /// Mirrors the TUI `reload_runtime_config` codepath for everything
331 /// reachable from the headless `Runtime`. MCP server connections
332 /// are not refreshed — changing `mcp_config_path` or the referenced
333 /// `mcp.json` still requires a headless-runtime restart. The TUI's
334 /// explicit `/mcp reload` operation is not part of this protocol path.
335 ConfigReload,
336 /// List available models.
337 Models,
338 /// List threads that are currently loaded in memory.
339 ThreadLoadedList,
340 /// Submit answers to a prior [`EventFrame::UserInputRequest`].
341 ///
342 /// `request_id` must match a pending clarification request. Headless
343 /// clients use this to return the user's selections back to the runtime.
344 SubmitUserInput {
345 request_id: String,
346 answers: Vec<UserInputAnswerEvent>,
347 },
348 }
349
350 /// Response to an [`AppRequest`].
351 #[derive(Debug, Clone, Serialize, Deserialize)]
352 pub struct AppResponse {
353 /// Whether the request succeeded.
354 pub ok: bool,
355 /// The response payload.
356 pub data: Value,
357 /// Streaming events associated with this response.
358 #[serde(default)]
359 pub events: Vec<EventFrame>,
360 }
361
362 /// A simple prompt request that sends text to the model and returns output.
363 #[derive(Debug, Clone, Serialize, Deserialize)]
364 pub struct PromptRequest {
365 /// Optional thread context for the prompt.
366 #[serde(skip_serializing_if = "Option::is_none")]
367 pub thread_id: Option<String>,
368 /// The prompt text.
369 pub prompt: String,
370 /// Model override, or the default if omitted.
371 #[serde(skip_serializing_if = "Option::is_none")]
372 pub model: Option<String>,
373 }
374
375 /// Response to a [`PromptRequest`].
376 #[derive(Debug, Clone, Serialize, Deserialize)]
377 pub struct PromptResponse {
378 /// The model's output text.
379 pub output: String,
380 /// The model that produced the output.
381 pub model: String,
382 /// Streaming events associated with this response.
383 #[serde(default)]
384 pub events: Vec<EventFrame>,
385 }
386
387 /// Policy controlling when the agent must ask the user for approval before acting.
388 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
389 #[serde(rename_all = "snake_case")]
390 pub enum AskForApproval {
391 /// Ask for approval unless the action is on a trusted path/resource.
392 UnlessTrusted,
393 /// Only ask after a tool call fails.
394 OnFailure,
395 /// Ask every time a tool call is requested.
396 OnRequest,
397 /// Reject the action without asking, with details on which categories are blocked.
398 Reject {
399 sandbox_approval: bool,
400 rules: bool,
401 mcp_elicitations: bool,
402 },
403 /// Never ask; auto-approve all actions.
404 Never,
405 }
406
407 /// Classification of tool invocation origin.
408 #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
409 #[serde(rename_all = "snake_case")]
410 pub enum ToolKind {
411 /// A built-in function tool.
412 Function,
413 /// An MCP (Model Context Protocol) tool.
414 Mcp,
415 }
416
417 /// Parameters for executing a local shell command.
418 #[derive(Debug, Clone, Serialize, Deserialize)]
419 pub struct LocalShellParams {
420 /// The shell command to execute.
421 pub command: String,
422 /// Working directory for the command.
423 #[serde(skip_serializing_if = "Option::is_none")]
424 pub cwd: Option<String>,
425 /// Timeout in milliseconds.
426 #[serde(skip_serializing_if = "Option::is_none")]
427 pub timeout_ms: Option<u64>,
428 }
429
430 /// The payload of a tool call, discriminated by tool type.
431 #[derive(Debug, Clone, Serialize, Deserialize)]
432 #[serde(tag = "type", rename_all = "snake_case")]
433 pub enum ToolPayload {
434 /// A built-in function call with JSON-encoded arguments.
435 Function { arguments: String },
436 /// A custom tool invocation with a free-form input string.
437 Custom { input: String },
438 /// A local shell command execution.
439 LocalShell { params: LocalShellParams },
440 /// An MCP tool invocation targeting a specific server and tool.
441 Mcp {
442 server: String,
443 tool: String,
444 raw_arguments: Value,
445 #[serde(skip_serializing_if = "Option::is_none")]
446 raw_tool_call_id: Option<String>,
447 },
448 }
449
450 /// The result of a tool call, discriminated by tool type.
451 #[derive(Debug, Clone, Serialize, Deserialize)]
452 #[serde(tag = "type", rename_all = "snake_case")]
453 pub enum ToolOutput {
454 /// Result of a built-in function call.
455 Function {
456 /// The output body, if any.
457 #[serde(skip_serializing_if = "Option::is_none")]
458 body: Option<Value>,
459 /// Whether the call succeeded.
460 success: bool,
461 },
462 /// Result of an MCP tool call.
463 Mcp {
464 /// The result value returned by the MCP server.
465 result: Value,
466 },
467 }
468
469 impl ToolOutput {
470 /// Returns the tool's application-level success independently of transport.
471 ///
472 /// MCP success requires the top-level `isError` field to be omitted or the
473 /// literal boolean `false`; malformed present metadata fails closed.
474 pub fn success(&self) -> bool {
475 match self {
476 Self::Function { success, .. } => *success,
477 Self::Mcp { result } => {
478 matches!(result.get("isError"), None | Some(Value::Bool(false)))
479 }
480 }
481 }
482 }
483
484 /// Action to take for a network policy rule.
485 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
486 #[serde(rename_all = "snake_case")]
487 pub enum NetworkPolicyRuleAction {
488 /// Allow network access to the host.
489 Allow,
490 /// Deny network access to the host.
491 Deny,
492 }
493
494 /// A proposed amendment to the network access policy for a specific host.
495 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
496 pub struct NetworkPolicyAmendment {
497 /// The host to amend the policy for.
498 pub host: String,
499 /// The action to apply.
500 pub action: NetworkPolicyRuleAction,
501 }
502
503 /// A user's decision on an approval request.
504 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
505 #[serde(tag = "type", rename_all = "snake_case")]
506 pub enum ReviewDecision {
507 /// Approve the action.
508 Approved,
509 /// Approve and also amend the execution policy.
510 ApprovedExecpolicyAmendment,
511 /// Approve for the remainder of this session only.
512 ApprovedForSession,
513 /// Approve with a network policy amendment.
514 NetworkPolicyAmendment {
515 host: String,
516 action: NetworkPolicyRuleAction,
517 },
518 /// Deny the action.
519 Denied,
520 /// Abort the entire turn.
521 Abort,
522 }
523
524 /// Status of an MCP server during startup.
525 #[derive(Debug, Clone, Serialize, Deserialize)]
526 #[serde(rename_all = "snake_case")]
527 pub enum McpStartupStatus {
528 /// The server is in the process of starting.
529 Starting,
530 /// The server is ready to accept requests.
531 Ready,
532 /// The server failed to start.
533 Failed { error: String },
534 /// Startup was cancelled.
535 Cancelled,
536 }
537
538 /// A progress update for a single MCP server's startup.
539 #[derive(Debug, Clone, Serialize, Deserialize)]
540 pub struct McpStartupUpdateEvent {
541 /// Name of the MCP server.
542 pub server_name: String,
543 /// Current startup status.
544 pub status: McpStartupStatus,
545 }
546
547 /// Details of an MCP server that failed to start.
548 #[derive(Debug, Clone, Serialize, Deserialize)]
549 pub struct McpStartupFailure {
550 /// Name of the MCP server that failed.
551 pub server_name: String,
552 /// Error description.
553 pub error: String,
554 }
555
556 /// Summary event emitted once all MCP servers have finished starting.
557 #[derive(Debug, Clone, Serialize, Deserialize)]
558 pub struct McpStartupCompleteEvent {
559 /// Servers that started successfully.
560 pub ready: Vec<String>,
561 /// Servers that failed to start.
562 pub failed: Vec<McpStartupFailure>,
563 /// Servers whose startup was cancelled.
564 pub cancelled: Vec<String>,
565 }
566
567 /// Context about a network access request that requires approval.
568 #[derive(Debug, Clone, Serialize, Deserialize)]
569 pub struct NetworkApprovalContext {
570 /// The host being accessed.
571 pub host: String,
572 /// The network protocol (e.g. `"https"`, `"tcp"`).
573 pub protocol: String,
574 }
575
576 /// A selectable option presented to the user in a clarification question.
577 ///
578 /// Headless serialization shape for the `request_user_input` model tool,
579 /// mirrored after the TUI's `UserInputOption`. Shared by the
580 /// [`EventFrame::UserInputRequest`] frame and the [`AppRequest::SubmitUserInput`]
581 /// reply path so both surfaces agree on the question schema.
582 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
583 pub struct UserInputOptionEvent {
584 /// Short label for the option (also the value submitted when picked).
585 pub label: String,
586 /// Longer description shown alongside the label.
587 pub description: String,
588 }
589
590 /// A single clarification question posed to the user.
591 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
592 pub struct UserInputQuestionEvent {
593 /// Compact header shown as the question title.
594 pub header: String,
595 /// Stable identifier used to correlate answers back to this question.
596 pub id: String,
597 /// The question body.
598 pub question: String,
599 /// 2-4 suggested answers.
600 pub options: Vec<UserInputOptionEvent>,
601 /// When `true`, the client should also offer a free-text response.
602 #[serde(default)]
603 pub allow_free_text: bool,
604 /// When `true`, the user may select more than one option.
605 #[serde(default)]
606 pub multi_select: bool,
607 }
608
609 /// An event requesting structured user input via a model-tool call.
610 ///
611 /// Sibling of [`ExecApprovalRequestEvent`] for the clarification-question
612 /// flow. Emitted fire-and-return by `Runtime::invoke_tool` when the model
613 /// invokes `request_user_input` in a headless context.
614 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
615 pub struct UserInputRequestEvent {
616 /// Identifier of the tool call requesting input.
617 pub call_id: String,
618 /// The turn during which the request was made.
619 pub turn_id: String,
620 /// Unique identifier for this user-input request (clients reply with it).
621 pub request_id: String,
622 /// 1-3 questions to present.
623 pub questions: Vec<UserInputQuestionEvent>,
624 }
625
626 /// One answer to a clarification question.
627 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
628 pub struct UserInputAnswerEvent {
629 /// The `id` of the question this answer corresponds to.
630 pub id: String,
631 /// The selected option's label, or `"Other"` for a free-text response.
632 pub label: String,
633 /// The resolved value (option label, or the typed free-text).
634 pub value: String,
635 }
636
637 /// An event requesting user approval for a command execution or patch application.
638 #[derive(Debug, Clone, Serialize, Deserialize)]
639 pub struct ExecApprovalRequestEvent {
640 /// Identifier of the tool call requesting approval.
641 pub call_id: String,
642 /// Unique identifier for this approval request.
643 pub approval_id: String,
644 /// The turn during which the request was made.
645 pub turn_id: String,
646 /// The command that would be executed.
647 pub command: String,
648 /// The working directory for the command.
649 pub cwd: String,
650 /// Human-readable reason why approval is needed.
651 pub reason: String,
652 /// Policy rule that matched this approval request, when available.
653 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub matched_rule: Option<Box<str>>,
655 /// Network context if the approval involves network access.
656 #[serde(skip_serializing_if = "Option::is_none")]
657 pub network_approval_context: Option<NetworkApprovalContext>,
658 /// Proposed execution policy rule amendments.
659 #[serde(default)]
660 pub proposed_execpolicy_amendment: Vec<String>,
661 /// Proposed network policy amendments.
662 #[serde(default)]
663 pub proposed_network_policy_amendments: Vec<NetworkPolicyAmendment>,
664 /// Additional permissions being requested.
665 #[serde(default)]
666 pub additional_permissions: Vec<String>,
667 /// The set of decisions the user can choose from.
668 #[serde(default)]
669 pub available_decisions: Vec<ReviewDecision>,
670 }
671
672 /// The channel a response delta is being written to.
673 #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
674 #[serde(rename_all = "snake_case")]
675 pub enum ResponseChannel {
676 /// The main visible text output.
677 #[default]
678 Text,
679 /// Internal reasoning / chain-of-thought output.
680 Reasoning,
681 }
682
683 impl ResponseChannel {
684 /// Returns `true` if this is the `Text` channel.
685 pub const fn is_text(&self) -> bool {
686 matches!(self, ResponseChannel::Text)
687 }
688 }
689
690 /// A user's approval decision sent in response to an approval request.
691 #[derive(Debug, Clone, Serialize, Deserialize)]
692 pub struct ApprovalDecisionRequest {
693 /// The decision identifier (e.g. `"approved"`, `"denied"`).
694 pub decision: String,
695 /// Whether to remember this decision for future similar requests.
696 #[serde(default)]
697 pub remember: bool,
698 }
699
700 /// A single streaming event frame emitted during agent execution.
701 ///
702 /// Events are tagged by the `event` field and cover the full lifecycle of a
703 /// turn: response streaming, tool calls, MCP lifecycle, command execution,
704 /// patch application, approvals, and errors.
705 #[derive(Debug, Clone, Serialize, Deserialize)]
706 #[serde(tag = "event", rename_all = "snake_case")]
707 pub enum EventFrame {
708 /// A new model response has started.
709 ResponseStart { response_id: String },
710 /// A incremental text delta for an in-progress response.
711 ResponseDelta {
712 response_id: String,
713 delta: String,
714 #[serde(default, skip_serializing_if = "ResponseChannel::is_text")]
715 channel: ResponseChannel,
716 },
717 /// The model response has finished.
718 ResponseEnd { response_id: String },
719 /// A tool call has begun.
720 ToolCallStart {
721 response_id: String,
722 tool_name: String,
723 arguments: Value,
724 },
725 /// A tool call has completed and produced a result.
726 ToolCallResult {
727 response_id: String,
728 tool_name: String,
729 output: Value,
730 },
731 /// Progress update for an MCP server starting up.
732 McpStartupUpdate { update: McpStartupUpdateEvent },
733 /// All MCP servers have finished starting.
734 McpStartupComplete { summary: McpStartupCompleteEvent },
735 /// An MCP tool call has begun.
736 McpToolCallBegin {
737 server_name: String,
738 tool_name: String,
739 },
740 /// An MCP tool call has finished.
741 McpToolCallEnd {
742 server_name: String,
743 tool_name: String,
744 ok: bool,
745 },
746 /// User approval is needed for a command execution.
747 ExecApprovalRequest { request: ExecApprovalRequestEvent },
748 /// User approval is needed for applying a patch.
749 ApplyPatchApprovalRequest { request: ExecApprovalRequestEvent },
750 /// A model tool is requesting structured clarification input from the user.
751 ///
752 /// Headless sibling of the TUI's `request_user_input` modal flow.
753 /// `request_id` correlates with an [`AppRequest::SubmitUserInput`] reply.
754 UserInputRequest { request: UserInputRequestEvent },
755 /// An MCP server is requesting user input (elicitation).
756 ElicitationRequest {
757 server_name: String,
758 request_id: String,
759 prompt: String,
760 },
761 /// A command has started executing.
762 ExecCommandBegin { command: String, cwd: String },
763 /// Incremental output from a running command.
764 ExecCommandOutputDelta { command: String, delta: String },
765 /// A command has finished executing.
766 ExecCommandEnd { command: String, exit_code: i32 },
767 /// A patch has started being applied to a file.
768 PatchApplyBegin { path: String },
769 /// A patch has finished being applied.
770 PatchApplyEnd { path: String, ok: bool },
771 /// A new turn has started within a thread.
772 TurnStarted { turn_id: String },
773 /// A turn has completed successfully.
774 TurnComplete { turn_id: String },
775 /// A turn was aborted before completion.
776 TurnAborted { turn_id: String, reason: String },
777 /// A thread goal was set or updated.
778 ThreadGoalUpdated { goal: ThreadGoal },
779 /// A thread goal was cleared.
780 ThreadGoalCleared { thread_id: String },
781 /// An error occurred during processing.
782 Error {
783 response_id: String,
784 message: String,
785 },
786 }
787
787 lines RUST