Skip to main content

codeoid_protocol/
tool.rs

1//! Tool invocation state machine.
2//!
3//! Tool calls have a lifecycle: `streaming → waiting_confirmation → executing → completed`
4//! (with `cancelled` as a terminal alternative). Each transition arrives as a
5//! [`SessionMessageDelta`](crate::message::SessionMessageDelta) with a
6//! `tool_state_update` field carrying the **whole next state**, not a patch.
7//!
8//! # Wire format
9//!
10//! Variant names serialize as `snake_case` to match the TS `ToolPhase` union
11//! (`"streaming"` / `"waiting_confirmation"` / `"executing"` / `"completed"`
12//! / `"cancelled"`). Inline variant fields serialize as `camelCase` to match
13//! the TS interface shape (`partialInput`, `approvalId`, `elapsedMs`,
14//! `confirmedBy`). The per-variant `rename_all = "camelCase"` attribute
15//! handles the latter — a bare `rename_all` on the enum only affects
16//! variant names, not their fields.
17
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20
21/// Phase name — useful as a lightweight discriminator when you don't need
22/// the full state payload (e.g. to pick an icon).
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum ToolPhase {
26    Streaming,
27    WaitingConfirmation,
28    Executing,
29    Completed,
30    Cancelled,
31}
32
33/// Tool state — a full replacement on each transition, never a delta.
34///
35/// Matching `toolStateUpdate` from the TS protocol, serialized as
36/// `{"phase": "...", ...variant-specific-fields-in-camelCase}`.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(tag = "phase", rename_all = "snake_case")]
39pub enum ToolState {
40    #[serde(rename_all = "camelCase")]
41    Streaming {
42        #[serde(default, skip_serializing_if = "Option::is_none")]
43        partial_input: Option<Value>,
44    },
45    #[serde(rename_all = "camelCase")]
46    WaitingConfirmation {
47        input: Value,
48        description: String,
49        approval_id: String,
50    },
51    #[serde(rename_all = "camelCase")]
52    Executing {
53        #[serde(default, skip_serializing_if = "Option::is_none")]
54        progress: Option<String>,
55        #[serde(default, skip_serializing_if = "Option::is_none")]
56        elapsed_ms: Option<u64>,
57    },
58    #[serde(rename_all = "camelCase")]
59    Completed {
60        success: bool,
61        #[serde(default, skip_serializing_if = "Option::is_none")]
62        output: Option<String>,
63        #[serde(default, skip_serializing_if = "Option::is_none")]
64        elapsed_ms: Option<u64>,
65        #[serde(default, skip_serializing_if = "Option::is_none")]
66        confirmed_by: Option<ConfirmedBy>,
67    },
68    #[serde(rename_all = "camelCase")]
69    Cancelled {
70        reason: CancelReason,
71        #[serde(default, skip_serializing_if = "Option::is_none")]
72        message: Option<String>,
73    },
74}
75
76impl ToolState {
77    #[must_use]
78    pub fn phase(&self) -> ToolPhase {
79        match self {
80            Self::Streaming { .. } => ToolPhase::Streaming,
81            Self::WaitingConfirmation { .. } => ToolPhase::WaitingConfirmation,
82            Self::Executing { .. } => ToolPhase::Executing,
83            Self::Completed { .. } => ToolPhase::Completed,
84            Self::Cancelled { .. } => ToolPhase::Cancelled,
85        }
86    }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91pub enum ConfirmedBy {
92    User,
93    Auto,
94    Setting,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
98#[serde(rename_all = "snake_case")]
99pub enum CancelReason {
100    Denied,
101    Interrupted,
102    Timeout,
103}
104
105/// Metadata for an in-flight or finished tool invocation, attached to a
106/// [`SessionMessage`](crate::message::SessionMessage) when `role = tool_call`.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct ToolInfo {
110    pub tool_id: String,
111    pub name: String,
112    pub state: ToolState,
113}