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:
Kent Overstreet 2026-04-08 15:08:41 -04:00
parent 1e5cd0dd3f
commit 39e6ae350d
2 changed files with 11 additions and 145 deletions

View file

@ -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;

View file

@ -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(),
}
}