diff --git a/src/agent/api/mod.rs b/src/agent/api/mod.rs index 25ac419..f3989df 100644 --- a/src/agent/api/mod.rs +++ b/src/agent/api/mod.rs @@ -10,7 +10,7 @@ pub mod http; mod types; mod openai; -// Public API types — used outside agent::api +// Transitional — these will go away as callers migrate to AstNode pub use types::{Message, MessageContent, ContentPart, ImageUrl, Role, ToolCall, FunctionCall, Usage}; use anyhow::Result; diff --git a/src/agent/api/types.rs b/src/agent/api/types.rs index cb87377..534cd63 100644 --- a/src/agent/api/types.rs +++ b/src/agent/api/types.rs @@ -1,29 +1,17 @@ -// api/types.rs — OpenAI-compatible API types +// api/types.rs — API wire types // -// These mirror the OpenAI chat completion API, which is the de facto -// standard that OpenRouter, vLLM, llama.cpp, and most inference -// providers implement. Using these types directly (rather than an -// SDK) means we control the wire format and can work with any -// compatible backend. +// Types that still exist here are transitional — they'll be killed +// as callers migrate to context_new::AstNode. use chrono::Utc; use serde::{Deserialize, Serialize}; -/// Function call within a tool call — name + JSON arguments. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct FunctionCall { pub name: String, pub arguments: String, } -/// Partial function call within a streaming delta. -#[derive(Debug, Deserialize)] -pub(crate) struct FunctionCallDelta { - pub name: Option, - pub arguments: Option, -} - -/// A tool call requested by the model. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ToolCall { pub id: String, @@ -32,19 +20,6 @@ pub struct ToolCall { pub function: FunctionCall, } -/// A partial tool call within a streaming delta. -#[derive(Debug, Deserialize)] -pub(crate) struct ToolCallDelta { - pub index: usize, - pub id: Option, - #[serde(rename = "type")] - pub call_type: Option, - pub function: Option, -} - -/// Message content — either plain text or an array of content parts -/// (for multimodal messages with images). Serializes as a JSON string -/// for text-only, or a JSON array for multimodal. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum MessageContent { @@ -53,7 +28,6 @@ pub enum MessageContent { } impl MessageContent { - /// Extract the text portion of the content, ignoring images. pub fn as_text(&self) -> &str { match self { MessageContent::Text(s) => s, @@ -69,7 +43,6 @@ impl MessageContent { } } -/// A single content part within a multimodal message. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] pub enum ContentPart { @@ -79,13 +52,11 @@ pub enum ContentPart { ImageUrl { image_url: ImageUrl }, } -/// Image URL — either a real URL or a base64 data URI. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ImageUrl { pub url: String, } -/// A chat message in the conversation. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Message { pub role: Role, @@ -96,9 +67,6 @@ pub struct Message { pub tool_call_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, - /// ISO 8601 timestamp — when this message entered the conversation. - /// Used for linking conversation ranges to journal entries during - /// compaction. Missing on messages from old session files. #[serde(default, skip_serializing_if = "Option::is_none")] pub timestamp: Option, } @@ -112,47 +80,6 @@ pub enum Role { Tool, } -/// Chat completion request. -#[derive(Debug, Serialize)] -pub(crate) struct ChatRequest { - pub model: String, - pub messages: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub tools: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub tool_choice: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub max_tokens: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub temperature: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub top_p: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub top_k: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub stream: Option, - /// OpenRouter reasoning control. Send both formats for compatibility: - /// - reasoning.enabled (older format, still seen in examples) - /// - reasoning.effort (documented: "none" disables entirely) - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning: Option, - /// vllm chat template kwargs — used to disable thinking on Qwen 3.5 - #[serde(skip_serializing_if = "Option::is_none")] - pub chat_template_kwargs: Option, - /// vllm request priority (lower = higher priority). - /// 0 = interactive, 1 = surface-observe, 10 = batch agents. - #[serde(skip_serializing_if = "Option::is_none")] - pub priority: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct ReasoningConfig { - pub enabled: bool, - /// "none" disables reasoning entirely per OpenRouter docs. - #[serde(skip_serializing_if = "Option::is_none")] - pub effort: Option, -} - #[derive(Debug, Clone, Deserialize)] pub struct Usage { pub prompt_tokens: u32, @@ -160,55 +87,11 @@ pub struct Usage { pub total_tokens: u32, } -// --- Streaming types --- - -/// A single chunk from a streaming chat completion response (SSE). -#[derive(Debug, Deserialize)] -pub(crate) struct ChatCompletionChunk { - pub choices: Vec, - pub usage: Option, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct ChunkChoice { - pub delta: Delta, - pub finish_reason: Option, -} - -/// The delta within a streaming chunk. All fields optional because each -/// chunk only carries the incremental change. -#[derive(Debug, Deserialize, Default)] -pub(crate) struct Delta { - #[allow(dead_code)] // present for deserialization - pub role: Option, - pub content: Option, - /// Reasoning/thinking content — sent by some models (Qwen, DeepSeek) - /// even when reasoning is "disabled". We capture it so we can detect - /// and log the problem rather than silently dropping responses. - /// OpenRouter uses multiple field names depending on the provider. - pub reasoning_content: Option, - pub reasoning: Option, - pub reasoning_details: Option, - pub tool_calls: Option>, -} - -// --- Convenience constructors --- - impl Message { - /// Extract text content regardless of whether it's Text or Parts. pub fn content_text(&self) -> &str { self.content.as_ref().map_or("", |c| c.as_text()) } - /// Append text to existing content (for streaming). - pub fn append_content(&mut self, text: &str) { - match self.content { - Some(MessageContent::Text(ref mut s)) => s.push_str(text), - None => self.content = Some(MessageContent::Text(text.to_string())), - _ => {} // Parts — don't append to multimodal - } - } - pub fn role_str(&self) -> &str { match self.role { Role::System => "system", @@ -222,8 +105,6 @@ impl Message { Some(Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)) } - /// Stamp a message with the current time if it doesn't already have one. - /// Used for messages from the API that we didn't construct ourselves. pub fn stamp(&mut self) { if self.timestamp.is_none() { self.timestamp = Self::now(); @@ -234,9 +115,7 @@ impl Message { Self { role: Role::System, content: Some(MessageContent::Text(content.into())), - tool_calls: None, - tool_call_id: None, - name: None, + tool_calls: None, tool_call_id: None, name: None, timestamp: Self::now(), } } @@ -245,31 +124,22 @@ impl Message { Self { role: Role::User, content: Some(MessageContent::Text(content.into())), - tool_calls: None, - tool_call_id: None, - name: None, + tool_calls: None, tool_call_id: None, name: None, timestamp: Self::now(), } } - /// User message with text and images (for multimodal/vision). pub fn user_with_images(text: &str, image_data_uris: &[String]) -> Self { - let mut parts = vec![ContentPart::Text { - text: text.to_string(), - }]; + let mut parts = vec![ContentPart::Text { text: text.to_string() }]; for uri in image_data_uris { parts.push(ContentPart::ImageUrl { - image_url: ImageUrl { - url: uri.clone(), - }, + image_url: ImageUrl { url: uri.clone() }, }); } Self { role: Role::User, content: Some(MessageContent::Parts(parts)), - tool_calls: None, - tool_call_id: None, - name: None, + tool_calls: None, tool_call_id: None, name: None, timestamp: Self::now(), } } @@ -278,9 +148,7 @@ impl Message { Self { role: Role::Assistant, content: Some(MessageContent::Text(content.into())), - tool_calls: None, - tool_call_id: None, - name: None, + tool_calls: None, tool_call_id: None, name: None, timestamp: Self::now(), } } @@ -289,9 +157,7 @@ impl Message { Self { role: Role::Tool, content: Some(MessageContent::Text(content.into())), - tool_calls: None, - tool_call_id: Some(id.into()), - name: None, + tool_calls: None, tool_call_id: Some(id.into()), name: None, timestamp: Self::now(), } }