Skip to main content

codewhale_protocol/
lib.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6pub mod agent_run;
7pub mod fleet;
8pub mod runtime;
9pub 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.
16pub 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)]
31pub 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")]
40pub enum ThreadStatus {
41    Running,
42    Idle,
43    Completed,
44    Failed,
45    Paused,
46    Archived,
47}
48
49impl 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")]
63pub enum SessionSource {
64    Interactive,
65    Resume,
66    Fork,
67    Api,
68    Unknown,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub 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")]
91pub enum ThreadGoalStatus {
92    Active,
93    Paused,
94    Blocked,
95    UsageLimited,
96    BudgetLimited,
97    Complete,
98}
99
100impl 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)]
113pub 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)]
128pub 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)]
140pub 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)]
169pub 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)]
194pub 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)]
202pub struct ThreadReadParams {
203    pub thread_id: String,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ThreadSetNameParams {
208    pub thread_id: String,
209    pub name: String,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub 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)]
221pub struct ThreadGoalGetParams {
222    pub thread_id: String,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct ThreadGoalClearParams {
227    pub thread_id: String,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub 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")]
243pub 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)]
272pub 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")]
312pub 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)]
352pub 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)]
364pub 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)]
377pub 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")]
390pub 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")]
410pub 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)]
419pub 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")]
433pub 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")]
453pub 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
469impl 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")]
487pub 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)]
496pub 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")]
506pub 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")]
527pub 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)]
540pub 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)]
549pub 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)]
558pub 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)]
569pub 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)]
583pub 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)]
592pub 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)]
615pub 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)]
628pub 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)]
639pub 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")]
675pub enum ResponseChannel {
676    /// The main visible text output.
677    #[default]
678    Text,
679    /// Internal reasoning / chain-of-thought output.
680    Reasoning,
681}
682
683impl 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)]
692pub 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")]
707pub 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}