Kill dead API types: ChatRequest, ChatCompletionChunk, Delta, streaming types
Removed all chat completions wire types that are no longer used: ChatRequest, ReasoningConfig, ChatCompletionChunk, ChunkChoice, Delta, FunctionCallDelta, ToolCallDelta, append_content, user_with_images. Remaining types in api/types.rs are transitional (Message, ToolCall, etc.) — they'll go away as outer callers migrate to AstNode. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
1e5cd0dd3f
commit
39e6ae350d
2 changed files with 11 additions and 145 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
pub arguments: Option<String>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub call_type: Option<String>,
|
||||
pub function: Option<FunctionCallDelta>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
|
@ -112,47 +80,6 @@ pub enum Role {
|
|||
Tool,
|
||||
}
|
||||
|
||||
/// Chat completion request.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ChatRequest {
|
||||
pub model: String,
|
||||
pub messages: Vec<Message>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tools: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tool_choice: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_tokens: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub temperature: Option<f32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub top_p: Option<f32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub top_k: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stream: Option<bool>,
|
||||
/// 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<ReasoningConfig>,
|
||||
/// vllm chat template kwargs — used to disable thinking on Qwen 3.5
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub chat_template_kwargs: Option<serde_json::Value>,
|
||||
/// vllm request priority (lower = higher priority).
|
||||
/// 0 = interactive, 1 = surface-observe, 10 = batch agents.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<i32>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
}
|
||||
|
||||
#[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<ChunkChoice>,
|
||||
pub usage: Option<Usage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct ChunkChoice {
|
||||
pub delta: Delta,
|
||||
pub finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
/// 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<Role>,
|
||||
pub content: Option<String>,
|
||||
/// 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<String>,
|
||||
pub reasoning: Option<String>,
|
||||
pub reasoning_details: Option<serde_json::Value>,
|
||||
pub tool_calls: Option<Vec<ToolCallDelta>>,
|
||||
}
|
||||
|
||||
// --- 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(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue