Skip to main content

kimi_wire/protocol/
request.rs

1use serde::{Deserialize, Serialize, Serializer};
2
3use super::content::{DisplayBlock, ToolReturnValue};
4use super::event::HookAction;
5
6/// A request from the agent to the client, sent via the `request` method.
7///
8/// The client must respond before the agent can continue execution.
9///
10/// Wire type names are PascalCase (e.g. `ApprovalRequest`).
11///
12/// Serialization follows the official wire envelope format:
13/// `{"type": "ApprovalRequest", "payload": {"id": ...}}`.
14#[derive(Debug, Clone, PartialEq)]
15#[non_exhaustive]
16pub enum Request {
17    /// Request for user approval before executing a tool.
18    ApprovalRequest(ApprovalRequest),
19    /// Request to execute a tool.
20    ToolCallRequest(ToolCallRequest),
21    /// Interactive question for the user.
22    QuestionRequest(QuestionRequest),
23    /// Hook trigger notification.
24    HookRequest(HookRequest),
25}
26
27// ---------------------------------------------------------------------------
28// FlatRequest – internal mirror used for (de)serialization
29// ---------------------------------------------------------------------------
30
31#[derive(Serialize, Deserialize)]
32#[serde(tag = "type")]
33#[allow(clippy::enum_variant_names)]
34pub(crate) enum FlatRequest {
35    ApprovalRequest(ApprovalRequest),
36    ToolCallRequest(ToolCallRequest),
37    QuestionRequest(QuestionRequest),
38    HookRequest(HookRequest),
39}
40
41impl From<Request> for FlatRequest {
42    fn from(req: Request) -> Self {
43        match req {
44            Request::ApprovalRequest(inner) => FlatRequest::ApprovalRequest(inner),
45            Request::ToolCallRequest(inner) => FlatRequest::ToolCallRequest(inner),
46            Request::QuestionRequest(inner) => FlatRequest::QuestionRequest(inner),
47            Request::HookRequest(inner) => FlatRequest::HookRequest(inner),
48        }
49    }
50}
51
52impl From<FlatRequest> for Request {
53    fn from(req: FlatRequest) -> Self {
54        match req {
55            FlatRequest::ApprovalRequest(inner) => Request::ApprovalRequest(inner),
56            FlatRequest::ToolCallRequest(inner) => Request::ToolCallRequest(inner),
57            FlatRequest::QuestionRequest(inner) => Request::QuestionRequest(inner),
58            FlatRequest::HookRequest(inner) => Request::HookRequest(inner),
59        }
60    }
61}
62
63// ---------------------------------------------------------------------------
64// RequestEnvelope – {type, payload} wire format
65// ---------------------------------------------------------------------------
66
67#[derive(Serialize, Deserialize)]
68struct RequestEnvelope {
69    #[serde(rename = "type")]
70    type_name: String,
71    payload: serde_json::Value,
72}
73
74impl Serialize for Request {
75    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
76        let flat = FlatRequest::from(self.clone());
77        let mut value = serde_json::to_value(&flat).map_err(serde::ser::Error::custom)?;
78        let obj = value
79            .as_object_mut()
80            .ok_or_else(|| serde::ser::Error::custom("expected object"))?;
81        let type_name = obj
82            .remove("type")
83            .and_then(|v| v.as_str().map(String::from))
84            .ok_or_else(|| serde::ser::Error::custom("missing type"))?;
85        RequestEnvelope { type_name, payload: value }.serialize(serializer)
86    }
87}
88
89impl<'de> Deserialize<'de> for Request {
90    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
91        let envelope = RequestEnvelope::deserialize(deserializer)?;
92        let mut value = envelope.payload;
93        if let Some(obj) = value.as_object_mut() {
94            obj.insert("type".to_string(), serde_json::Value::String(envelope.type_name));
95        }
96        let flat: FlatRequest = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
97        Ok(Request::from(flat))
98    }
99}
100
101/// Approval request sent by the agent.
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
103pub struct ApprovalRequest {
104    /// Request id.
105    pub id: String,
106    /// Id of the tool call requiring approval.
107    pub tool_call_id: String,
108    /// Tool sender name.
109    pub sender: String,
110    /// Action description.
111    pub action: String,
112    /// Detailed description.
113    pub description: String,
114    /// Display blocks for the user.
115    #[serde(skip_serializing_if = "Option::is_none", default)]
116    pub display: Option<Vec<DisplayBlock>>,
117    /// Source of the request.
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub source_kind: Option<SourceKind>,
120    /// Source id.
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub source_id: Option<String>,
123    /// Agent id.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub agent_id: Option<String>,
126    /// Subagent type.
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub subagent_type: Option<String>,
129    /// Source description.
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub source_description: Option<String>,
132}
133
134/// Source of an approval request.
135#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
136#[serde(rename_all = "snake_case")]
137#[non_exhaustive]
138pub enum SourceKind {
139    /// Request originated from a foreground turn.
140    ForegroundTurn,
141    /// Request originated from a background agent.
142    BackgroundAgent,
143}
144
145/// Tool call request sent by the agent.
146#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
147pub struct ToolCallRequest {
148    /// Request id.
149    pub id: String,
150    /// Tool name.
151    pub name: String,
152    /// JSON-encoded arguments.
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub arguments: Option<String>,
155}
156
157/// Question request sent by the agent.
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
159pub struct QuestionRequest {
160    /// Request id.
161    pub id: String,
162    /// Id of the tool call that triggered the question.
163    pub tool_call_id: String,
164    /// Questions to ask the user.
165    pub questions: Vec<QuestionItem>,
166}
167
168/// A single question item.
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170pub struct QuestionItem {
171    /// Question text.
172    pub question: String,
173    /// Short label, max 12 characters.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub header: Option<String>,
176    /// Available options.
177    pub options: Vec<QuestionOption>,
178    /// Whether multiple options can be selected.
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub multi_select: Option<bool>,
181}
182
183/// An option for a question.
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
185pub struct QuestionOption {
186    /// Option label.
187    pub label: String,
188    /// Option description.
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub description: Option<String>,
191}
192
193/// Hook request sent by the agent.
194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
195pub struct HookRequest {
196    /// Request id.
197    pub id: String,
198    /// Subscription id that matched.
199    pub subscription_id: String,
200    /// Event name.
201    pub event: String,
202    /// Event target.
203    pub target: String,
204    /// Event input data.
205    pub input_data: serde_json::Value,
206}
207
208// ============================================================================
209// Response types (what the client sends back)
210// ============================================================================
211
212/// Response to an [`ApprovalRequest`].
213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
214pub struct ApprovalResponse {
215    /// Id of the approval request.
216    pub request_id: String,
217    /// Approval decision.
218    pub response: ApprovalResponseKind,
219    /// Optional feedback text.
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub feedback: Option<String>,
222}
223
224pub use crate::protocol::event::ApprovalResponseKind;
225
226/// Response to a [`ToolCallRequest`].
227#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
228pub struct ToolCallResponse {
229    /// Id of the tool call.
230    pub tool_call_id: String,
231    /// Tool return value.
232    pub return_value: ToolReturnValue,
233}
234
235/// Response to a [`QuestionRequest`].
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
237pub struct QuestionResponse {
238    /// Id of the question request.
239    pub request_id: String,
240    /// Mapping of question text to selected option label(s).
241    /// For multi-select, values are comma-separated.
242    pub answers: std::collections::HashMap<String, String>,
243}
244
245/// Response to a [`HookRequest`].
246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
247pub struct HookResponse {
248    /// Id of the hook request.
249    pub request_id: String,
250    /// Action taken.
251    pub action: HookAction,
252    /// Reason for the action.
253    pub reason: String,
254}