Skip to main content

deepseek/agent/
messages.rs

1//! SDK message types — mirrors the Claude Agent SDK message stream.
2//!
3//! See <https://code.claude.com/docs/en/agent-sdk/agent-loop>. The loop yields
4//! a sequence of these in order: `System{Init}` → (`Assistant` →
5//! `User(tool_results)`)* → final `Assistant` → `Result`.
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use crate::types::UsageInfo;
11
12/// Per-block content inside an `Assistant` or `User` message.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(tag = "type", rename_all = "snake_case")]
15pub enum ContentBlock {
16    /// Text emitted by the assistant.
17    Text { text: String },
18
19    /// Assistant requested a tool call.
20    ToolUse {
21        id: String,
22        name: String,
23        input: Value,
24    },
25
26    /// Result of a tool call, fed back to the model on the next turn.
27    ToolResult {
28        tool_use_id: String,
29        content: String,
30        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
31        is_error: bool,
32    },
33}
34
35/// Subtype of a `System` message.
36///
37/// - `Init` is emitted once at session start, carrying the session id and
38///   the run configuration.
39/// - `Compact` is emitted after a successful history-compaction step (see
40///   [`crate::agent::CompactionConfig`]). Its `data` field carries
41///   `{"message_count_after": <usize>}`.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum SystemSubtype {
45    Init,
46    Compact,
47}
48
49/// Final disposition of an agent run.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case")]
52pub enum ResultSubtype {
53    Success,
54    ErrorMaxTurns,
55    ErrorMaxBudgetUsd,
56    ErrorDuringExecution,
57}
58
59impl ResultSubtype {
60    pub fn is_success(self) -> bool {
61        matches!(self, Self::Success)
62    }
63}
64
65/// Streamed message types in turn order.
66///
67/// Matches `SystemMessage` / `AssistantMessage` / `UserMessage` /
68/// `ResultMessage` from the Claude Agent SDK. We do not emit `StreamEvent`
69/// (token deltas) — the loop runs in non-streaming mode.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71#[serde(tag = "type", rename_all = "snake_case")]
72pub enum SdkMessage {
73    /// Lifecycle event. `Init` carries session metadata.
74    System {
75        subtype: SystemSubtype,
76        session_id: String,
77        #[serde(default, skip_serializing_if = "Value::is_null")]
78        data: Value,
79    },
80
81    /// One assistant turn — text and/or tool_use blocks. The final assistant
82    /// message in a successful run has no tool_use blocks.
83    Assistant {
84        content: Vec<ContentBlock>,
85        #[serde(skip_serializing_if = "Option::is_none")]
86        stop_reason: Option<String>,
87    },
88
89    /// One synthetic user turn carrying tool_result blocks back to the model.
90    User { content: Vec<ContentBlock> },
91
92    /// Terminal message. `result` is `Some` only when `subtype = Success`.
93    Result {
94        subtype: ResultSubtype,
95        #[serde(skip_serializing_if = "Option::is_none")]
96        result: Option<String>,
97        #[serde(skip_serializing_if = "Option::is_none")]
98        total_cost_usd: Option<f64>,
99        #[serde(skip_serializing_if = "Option::is_none")]
100        usage: Option<UsageInfo>,
101        num_turns: u32,
102        session_id: String,
103        #[serde(skip_serializing_if = "Option::is_none")]
104        stop_reason: Option<String>,
105    },
106}
107
108impl SdkMessage {
109    /// True for the terminal `Result` message.
110    pub fn is_terminal(&self) -> bool {
111        matches!(self, SdkMessage::Result { .. })
112    }
113}