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