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