Skip to main content

everruns_openai/
types.rs

1// OpenAI Protocol Types
2//
3// These types are kept for backward compatibility with existing code.
4// The actual implementation is now in everruns-core/src/openai.rs.
5
6use everruns_provider::{ToolCall, ToolDefinition};
7use serde::{Deserialize, Serialize};
8
9/// Provider-agnostic chat message following OpenAI's format
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ChatMessage {
12    pub role: MessageRole,
13    pub content: String,
14    /// Tool call results (for assistant messages with tool calls)
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub tool_calls: Option<Vec<ToolCall>>,
17    /// Tool call ID (for tool result messages)
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub tool_call_id: Option<String>,
20}
21
22/// Message role in conversation (OpenAI format)
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
24#[serde(rename_all = "lowercase")]
25pub enum MessageRole {
26    System,
27    User,
28    Assistant,
29    Tool,
30}
31
32/// LLM configuration following OpenAI's API parameters
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct LlmConfig {
35    /// Model identifier (e.g., "gpt-5.2", "gpt-3.5-turbo")
36    pub model: String,
37    /// Sampling temperature (0.0 - 2.0)
38    pub temperature: Option<f32>,
39    /// Maximum tokens to generate
40    pub max_tokens: Option<u32>,
41    /// System prompt (if not in messages)
42    pub system_prompt: Option<String>,
43    /// Available tools (for function calling)
44    #[serde(default)]
45    pub tools: Vec<ToolDefinition>,
46}
47
48/// Events emitted during LLM streaming
49#[derive(Debug, Clone)]
50pub enum LlmStreamEvent {
51    /// Text delta (incremental content)
52    TextDelta(String),
53    /// Tool calls from the LLM
54    ToolCalls(Vec<ToolCall>),
55    /// Streaming completed successfully
56    Done(CompletionMetadata),
57    /// Error occurred during streaming
58    Error(String),
59}
60
61/// Completion metadata returned on stream completion
62#[derive(Debug, Clone, Default, Serialize, Deserialize)]
63pub struct CompletionMetadata {
64    /// Total tokens used (if available)
65    pub total_tokens: Option<u32>,
66    /// Input tokens used (if available)
67    pub prompt_tokens: Option<u32>,
68    /// Output tokens generated (if available)
69    pub completion_tokens: Option<u32>,
70    /// Model used
71    pub model: String,
72    /// Finish reason
73    pub finish_reason: Option<String>,
74}
75
76/// OpenAI chat completion request format
77#[derive(Debug, Clone, Serialize)]
78pub struct ChatRequest {
79    pub model: String,
80    pub messages: Vec<OpenAiMessage>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub temperature: Option<f32>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub max_tokens: Option<u32>,
85    pub stream: bool,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub tools: Option<Vec<OpenAiTool>>,
88}
89
90// ============================================================================
91// OpenAI API Types (for ChatRequest serialization)
92// ============================================================================
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct OpenAiMessage {
96    pub role: String,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub content: Option<String>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub tool_calls: Option<Vec<OpenAiToolCall>>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub tool_call_id: Option<String>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct OpenAiTool {
107    pub r#type: String,
108    pub function: OpenAiFunction,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct OpenAiFunction {
113    pub name: String,
114    pub description: String,
115    pub parameters: serde_json::Value,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct OpenAiToolCall {
120    pub id: String,
121    pub r#type: String,
122    pub function: OpenAiFunctionCall,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct OpenAiFunctionCall {
127    pub name: String,
128    pub arguments: String,
129}
130
131// ============================================================================
132// Conversions
133// ============================================================================
134
135impl ChatMessage {
136    /// Convert to OpenAI API message format
137    pub fn to_openai(&self) -> OpenAiMessage {
138        let role = match self.role {
139            MessageRole::System => "system",
140            MessageRole::User => "user",
141            MessageRole::Assistant => "assistant",
142            MessageRole::Tool => "tool",
143        };
144
145        OpenAiMessage {
146            role: role.to_string(),
147            content: Some(self.content.clone()),
148            tool_calls: self.tool_calls.as_ref().map(|calls| {
149                calls
150                    .iter()
151                    .map(|tc| OpenAiToolCall {
152                        id: tc.id.clone(),
153                        r#type: "function".to_string(),
154                        function: OpenAiFunctionCall {
155                            name: tc.name.clone(),
156                            arguments: serde_json::to_string(&tc.arguments).unwrap_or_default(),
157                        },
158                    })
159                    .collect()
160            }),
161            tool_call_id: self.tool_call_id.clone(),
162        }
163    }
164}
165
166impl LlmConfig {
167    /// Convert tool definitions to OpenAI's format
168    pub fn tools_to_openai(&self) -> Vec<OpenAiTool> {
169        self.tools
170            .iter()
171            .map(|tool| {
172                let (name, description, parameters) = match tool {
173                    ToolDefinition::Builtin(builtin) => {
174                        (&builtin.name, &builtin.description, &builtin.parameters)
175                    }
176                    ToolDefinition::ClientSide(client) => {
177                        (&client.name, &client.description, &client.parameters)
178                    }
179                };
180
181                OpenAiTool {
182                    r#type: "function".to_string(),
183                    function: OpenAiFunction {
184                        name: name.clone(),
185                        description: description.clone(),
186                        parameters: parameters.clone(),
187                    },
188                }
189            })
190            .collect()
191    }
192}
193
194// ============================================================================
195// OpenAI Models API Types
196// ============================================================================
197
198/// Response from OpenAI's /v1/models endpoint
199#[derive(Debug, Clone, Deserialize)]
200pub struct OpenAiModelsResponse {
201    pub data: Vec<OpenAiModelInfo>,
202}
203
204/// Individual model info from OpenAI's models API
205#[derive(Debug, Clone, Deserialize)]
206pub struct OpenAiModelInfo {
207    /// Model identifier (e.g., "gpt-5.2", "gpt-4o")
208    pub id: String,
209    /// Unix timestamp of model creation
210    pub created: i64,
211    /// Owner organization (e.g., "openai", "system")
212    pub owned_by: String,
213}
214
215impl OpenAiModelInfo {
216    /// Check if this model is a chat/completion model (not embedding, TTS, etc.)
217    ///
218    /// Includes: gpt-*, o1*, o3*, o4*, chatgpt-*
219    /// Excludes: text-embedding-*, dall-e-*, tts-*, whisper-*, davinci-*, babbage-*, omni-moderation-*, sora-*, codex-*, gpt-image-*
220    pub fn is_chat_model(&self) -> bool {
221        let id = self.id.as_str();
222
223        // Exclude patterns (check these first)
224        if id.starts_with("text-embedding")
225            || id.starts_with("dall-e")
226            || id.starts_with("tts-")
227            || id.starts_with("whisper")
228            || id.starts_with("davinci")
229            || id.starts_with("babbage")
230            || id.starts_with("omni-moderation")
231            || id.starts_with("sora-")
232            || id.starts_with("gpt-image")
233            || id.starts_with("codex-")
234            || id.contains("-transcribe")
235            || id.contains("-realtime")
236            || id.contains("-audio")
237            || id.contains("-tts")
238        {
239            return false;
240        }
241
242        // Include patterns
243        id.starts_with("gpt-")
244            || id.starts_with("o1")
245            || id.starts_with("o3")
246            || id.starts_with("o4")
247            || id.starts_with("chatgpt-")
248    }
249}