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. Currently only `Init` is emitted; compaction
36/// boundaries are deferred.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum SystemSubtype {
40    Init,
41}
42
43/// Final disposition of an agent run.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46pub enum ResultSubtype {
47    Success,
48    ErrorMaxTurns,
49    ErrorMaxBudgetUsd,
50    ErrorDuringExecution,
51}
52
53impl ResultSubtype {
54    pub fn is_success(self) -> bool {
55        matches!(self, Self::Success)
56    }
57}
58
59/// Streamed message types in turn order.
60///
61/// Matches `SystemMessage` / `AssistantMessage` / `UserMessage` /
62/// `ResultMessage` from the Claude Agent SDK. We do not emit `StreamEvent`
63/// (token deltas) — the loop runs in non-streaming mode.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(tag = "type", rename_all = "snake_case")]
66pub enum SdkMessage {
67    /// Lifecycle event. `Init` carries session metadata.
68    System {
69        subtype: SystemSubtype,
70        session_id: String,
71        #[serde(default, skip_serializing_if = "Value::is_null")]
72        data: Value,
73    },
74
75    /// One assistant turn — text and/or tool_use blocks. The final assistant
76    /// message in a successful run has no tool_use blocks.
77    Assistant {
78        content: Vec<ContentBlock>,
79        #[serde(skip_serializing_if = "Option::is_none")]
80        stop_reason: Option<String>,
81    },
82
83    /// One synthetic user turn carrying tool_result blocks back to the model.
84    User { content: Vec<ContentBlock> },
85
86    /// Terminal message. `result` is `Some` only when `subtype = Success`.
87    Result {
88        subtype: ResultSubtype,
89        #[serde(skip_serializing_if = "Option::is_none")]
90        result: Option<String>,
91        #[serde(skip_serializing_if = "Option::is_none")]
92        total_cost_usd: Option<f64>,
93        #[serde(skip_serializing_if = "Option::is_none")]
94        usage: Option<UsageInfo>,
95        num_turns: u32,
96        session_id: String,
97        #[serde(skip_serializing_if = "Option::is_none")]
98        stop_reason: Option<String>,
99    },
100}
101
102impl SdkMessage {
103    /// True for the terminal `Result` message.
104    pub fn is_terminal(&self) -> bool {
105        matches!(self, SdkMessage::Result { .. })
106    }
107}