Skip to main content

codewhale_protocol/
lib.rs

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