Skip to main content

opi_ai/
stream.rs

1//! Streaming response events (S7.3).
2
3use serde::{Deserialize, Serialize};
4
5// Legacy placeholder — replaced by AssistantStreamEvent in task 1.2.
6#[derive(Debug, Clone)]
7pub enum StreamEvent {
8    Text(String),
9    ToolCall {
10        id: String,
11        name: String,
12        arguments: String,
13    },
14    Thinking(String),
15    Usage {
16        input_tokens: u32,
17        output_tokens: u32,
18    },
19    Stop,
20}
21
22#[non_exhaustive]
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24pub enum StopReason {
25    #[serde(rename = "stop")]
26    Stop,
27    #[serde(rename = "length")]
28    Length,
29    #[serde(rename = "tool_use")]
30    ToolUse,
31    #[serde(rename = "error")]
32    Error,
33    #[serde(rename = "aborted")]
34    Aborted,
35}
36
37impl StopReason {
38    pub fn as_str(self) -> &'static str {
39        match self {
40            Self::Stop => "stop",
41            Self::Length => "length",
42            Self::ToolUse => "tool_use",
43            Self::Error => "error",
44            Self::Aborted => "aborted",
45        }
46    }
47}
48
49#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
50pub struct Usage {
51    pub input_tokens: u32,
52    pub output_tokens: u32,
53}
54
55#[non_exhaustive]
56#[derive(Debug, Clone, PartialEq, Serialize)]
57#[serde(tag = "type")]
58pub enum AssistantStreamEvent {
59    #[serde(rename = "start")]
60    Start {
61        partial: crate::message::AssistantMessage,
62    },
63    #[serde(rename = "text_start")]
64    TextStart {
65        content_index: usize,
66        partial: crate::message::AssistantMessage,
67    },
68    #[serde(rename = "text_delta")]
69    TextDelta {
70        content_index: usize,
71        delta: String,
72        partial: crate::message::AssistantMessage,
73    },
74    #[serde(rename = "text_end")]
75    TextEnd {
76        content_index: usize,
77        content: String,
78        partial: crate::message::AssistantMessage,
79    },
80    #[serde(rename = "thinking_start")]
81    ThinkingStart {
82        content_index: usize,
83        partial: crate::message::AssistantMessage,
84    },
85    #[serde(rename = "thinking_delta")]
86    ThinkingDelta {
87        content_index: usize,
88        delta: String,
89        partial: crate::message::AssistantMessage,
90    },
91    #[serde(rename = "thinking_end")]
92    ThinkingEnd {
93        content_index: usize,
94        content: String,
95        partial: crate::message::AssistantMessage,
96    },
97    #[serde(rename = "tool_call_start")]
98    ToolCallStart {
99        content_index: usize,
100        partial: crate::message::AssistantMessage,
101    },
102    #[serde(rename = "tool_call_delta")]
103    ToolCallDelta {
104        content_index: usize,
105        delta: String,
106        partial: crate::message::AssistantMessage,
107    },
108    #[serde(rename = "tool_call_end")]
109    ToolCallEnd {
110        content_index: usize,
111        tool_call: crate::message::ToolCall,
112        partial: crate::message::AssistantMessage,
113    },
114    #[serde(rename = "done")]
115    Done {
116        reason: StopReason,
117        message: crate::message::AssistantMessage,
118    },
119    #[serde(rename = "error")]
120    Error {
121        reason: StopReason,
122        message: crate::message::AssistantMessage,
123    },
124}
125
126impl AssistantStreamEvent {
127    pub fn is_terminal(&self) -> bool {
128        matches!(self, Self::Done { .. } | Self::Error { .. })
129    }
130}