use crate::stream_events::{map_tool_event, with_keepalive};
use std::collections::VecDeque;
use std::convert::Infallible;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::sse::{Event, Sse};
use axum::response::{IntoResponse, Response};
use axum::Json;
use futures_util::StreamExt;
use serde::Deserialize;
use serde_json::{json, Value};
use crate::generate::Usage;
use crate::output::{OutputPosture, ParsedOutput};
use crate::{
attribution, decode_error_response, output, prompt_from_messages, run_generation_emit, sse,
stats, ApiError, AppState, ChatCompletionRequest, ChatMessage, MessageContent, StopParam,
ThinkingSwitch, ToolCallFunctionIn, ToolCallIn, ToolDef, ToolFunctionDef,
};
pub(crate) const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(15);
static ID_COUNTER: AtomicU64 = AtomicU64::new(0);
fn new_id(prefix: &str) -> String {
let stamp = unix_nanos();
let n = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
format!("{prefix}{:012x}{:06x}", stamp & 0xffff_ffff_ffff, n)
}
fn tool_use_id(name: &str, index: usize) -> String {
let slug: String = name.replace('_', "-").chars().take(24).collect();
let slug = if slug.is_empty() {
"tool".to_string()
} else {
slug
};
let stamp = unix_nanos();
let n = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
format!("toolu_{slug}_{index}_{:08x}", (stamp ^ n) as u32)
}
fn unix_nanos() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0)
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum ContentIn {
Text(String),
Blocks(Vec<ContentBlockIn>),
}
#[derive(Debug, Clone, Deserialize)]
struct ContentBlockIn {
#[serde(rename = "type")]
kind: String,
#[serde(default)]
text: Option<String>,
#[serde(default)]
thinking: Option<String>,
#[serde(default)]
id: Option<String>,
#[serde(default)]
tool_use_id: Option<String>,
#[serde(default)]
name: Option<String>,
#[serde(default)]
input: Option<Value>,
#[serde(default)]
content: Option<Value>,
}
#[derive(Debug, Clone, Deserialize)]
struct AnthropicMessage {
role: String,
content: ContentIn,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum SystemIn {
Text(String),
Blocks(Vec<ContentBlockIn>),
}
#[derive(Debug, Clone, Deserialize)]
struct ToolIn {
name: String,
#[serde(default)]
description: Option<String>,
#[serde(default)]
input_schema: Option<Value>,
}
#[derive(Debug, Clone, Deserialize)]
struct ToolChoiceIn {
#[serde(rename = "type")]
kind: String,
#[serde(default)]
name: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
struct PromptFields {
messages: Vec<AnthropicMessage>,
#[serde(default)]
system: Option<SystemIn>,
#[serde(default)]
tools: Option<Vec<ToolIn>>,
#[serde(default)]
tool_choice: Option<ToolChoiceIn>,
#[serde(default)]
thinking: Option<ThinkingSwitch>,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct MessagesRequest {
#[serde(default)]
model: String,
max_tokens: usize,
#[serde(flatten)]
prompt: PromptFields,
#[serde(default)]
temperature: Option<f32>,
#[serde(default)]
top_p: Option<f32>,
#[serde(default)]
top_k: Option<usize>,
#[serde(default)]
stop_sequences: Option<Vec<String>>,
#[serde(default)]
stream: Option<bool>,
#[serde(default)]
metadata: Option<Value>,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct CountTokensRequest {
#[serde(default)]
model: String,
#[serde(flatten)]
prompt: PromptFields,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ConvertedCall {
id: String,
name: String,
arguments: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
struct Turn {
role: String,
content: Option<String>,
reasoning: Option<String>,
tool_calls: Vec<ConvertedCall>,
tool_call_id: Option<String>,
}
impl Turn {
fn lower(self) -> Option<ChatMessage> {
let has_text = self.content.as_ref().is_some_and(|c| !c.is_empty());
if !has_text && self.tool_calls.is_empty() && self.tool_call_id.is_none() {
return None;
}
let tool_calls: Vec<ToolCallIn> = self
.tool_calls
.into_iter()
.map(|call| ToolCallIn {
id: call.id,
kind: "function".to_string(),
function: ToolCallFunctionIn {
name: call.name,
arguments: call.arguments,
},
})
.collect();
Some(ChatMessage {
role: self.role,
content: match self.content {
Some(text) if !text.is_empty() || tool_calls.is_empty() => {
Some(MessageContent::Text(text))
}
_ => None,
},
tool_calls: (!tool_calls.is_empty()).then_some(tool_calls),
tool_call_id: self.tool_call_id,
reasoning_content: self.reasoning.filter(|r| !r.is_empty()),
})
}
}
fn content_text(content: &ContentIn) -> String {
match content {
ContentIn::Text(text) => text.clone(),
ContentIn::Blocks(blocks) => blocks
.iter()
.filter(|b| b.kind == "text")
.filter_map(|b| b.text.as_deref())
.collect(),
}
}
fn system_text(system: &SystemIn) -> String {
match system {
SystemIn::Text(text) => text.clone(),
SystemIn::Blocks(blocks) => blocks
.iter()
.filter(|b| b.kind == "text")
.filter_map(|b| b.text.as_deref())
.collect(),
}
}
fn tool_result_text(content: Option<&Value>) -> String {
match content {
None => String::new(),
Some(Value::String(text)) => text.clone(),
Some(Value::Array(items)) => items
.iter()
.map(|item| match item {
Value::String(text) => text.clone(),
Value::Object(_) => item
.get("text")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string(),
other => other.to_string(),
})
.collect(),
Some(other) => other.to_string(),
}
}
fn arguments_json(input: Option<&Value>) -> String {
match input {
Some(value @ Value::Object(_)) => value.to_string(),
_ => "{}".to_string(),
}
}
fn convert_prompt(prompt: &PromptFields) -> Vec<ChatMessage> {
let mut system_texts: Vec<String> = Vec::new();
if let Some(system) = &prompt.system {
system_texts.push(system_text(system));
}
let mut rest: Vec<ChatMessage> = Vec::new();
for message in &prompt.messages {
if message.role == "system" {
system_texts.push(content_text(&message.content));
continue;
}
let blocks = match &message.content {
ContentIn::Text(text) => {
rest.extend(
Turn {
role: message.role.clone(),
content: Some(text.clone()),
..Turn::default()
}
.lower(),
);
continue;
}
ContentIn::Blocks(blocks) => blocks,
};
let mut turn = Turn {
role: message.role.clone(),
..Turn::default()
};
let mut texts: Vec<String> = Vec::new();
let mut thoughts: Vec<String> = Vec::new();
for block in blocks {
match block.kind.as_str() {
"text" => {
if let Some(text) = block.text.as_ref().filter(|t| !t.is_empty()) {
texts.push(text.clone());
}
}
"thinking" => {
if let Some(text) = block.thinking.as_ref().filter(|t| !t.is_empty()) {
thoughts.push(text.clone());
}
}
"tool_use" => turn.tool_calls.push(ConvertedCall {
id: block.id.clone().unwrap_or_else(|| new_id("call_")),
name: block.name.clone().unwrap_or_default(),
arguments: arguments_json(block.input.as_ref()),
}),
"tool_result" if message.role == "user" => {
rest.push(ChatMessage {
role: "tool".to_string(),
content: Some(MessageContent::Text(tool_result_text(
block.content.as_ref(),
))),
tool_calls: None,
tool_call_id: Some(
block
.tool_use_id
.clone()
.or_else(|| block.id.clone())
.unwrap_or_default(),
),
reasoning_content: None,
});
}
"tool_result" => {
texts.push(format!(
"Tool result: {}",
tool_result_text(block.content.as_ref())
));
}
_ => {}
}
}
if !texts.is_empty() {
turn.content = Some(texts.concat());
}
if !thoughts.is_empty() {
turn.reasoning = Some(thoughts.join("\n\n"));
}
rest.extend(turn.lower());
}
let mut messages: Vec<ChatMessage> = Vec::with_capacity(rest.len() + 1);
let system = system_texts
.iter()
.filter(|t| !t.is_empty())
.cloned()
.collect::<Vec<_>>()
.join("\n\n");
if !system.is_empty() {
messages.push(ChatMessage {
role: "system".to_string(),
content: Some(MessageContent::Text(system)),
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
});
}
messages.extend(rest);
messages
}
fn tool_defs(tools: &[ToolIn]) -> Vec<ToolDef> {
tools
.iter()
.map(|tool| {
let mut schema = tool
.input_schema
.clone()
.unwrap_or_else(|| json!({"type": "object"}));
if let Some(object) = schema.as_object_mut() {
object
.entry("type".to_string())
.or_insert_with(|| json!("object"));
}
ToolDef {
kind: "function".to_string(),
function: ToolFunctionDef {
name: tool.name.clone(),
description: tool.description.clone(),
parameters: Some(schema),
},
}
})
.collect()
}
fn split_tool_lists(all: Vec<ToolDef>, selected: Option<&str>) -> (Vec<ToolDef>, Vec<ToolDef>) {
if all.is_empty() {
return (Vec::new(), Vec::new());
}
match selected {
Some(name) => (
all.iter()
.filter(|tool| tool.function.name == name)
.cloned()
.collect(),
all,
),
None => (all.clone(), all),
}
}
struct Prepared {
chat: ChatCompletionRequest,
parser_tools: Vec<ToolDef>,
}
fn prepare_prompt(prompt: &PromptFields, model: String, max_tokens: usize) -> Prepared {
let all = tool_defs(prompt.tools.as_deref().unwrap_or_default());
let choice = prompt.tool_choice.as_ref();
let (template_tools, parser_tools) = if choice.is_some_and(|c| c.kind == "none") {
(Vec::new(), Vec::new())
} else {
let selected = choice
.filter(|c| c.kind == "tool")
.and_then(|c| c.name.as_deref());
split_tool_lists(all, selected)
};
let chat = ChatCompletionRequest {
model,
messages: convert_prompt(prompt),
max_tokens,
temperature: None,
top_p: None,
min_p: None,
top_k: None,
repetition_penalty: None,
seed: None,
stop: None,
stream: None,
stream_resumable: None,
tools: template_tools,
tool_choice: None,
chat_template_kwargs: None,
reasoning_effort: None,
thinking: prompt.thinking.clone(),
session_id: None,
logprobs: None,
top_logprobs: None,
n: None,
presence_penalty: None,
frequency_penalty: None,
response_format: None,
grammar: None,
logit_bias: None,
ignore_eos: None,
};
Prepared { chat, parser_tools }
}
fn to_chat_request(req: &MessagesRequest) -> Result<Prepared, ApiError> {
let mut prepared = prepare_prompt(&req.prompt, req.model.clone(), req.max_tokens);
prepared.chat.temperature = req.temperature;
prepared.chat.top_p = req.top_p;
prepared.chat.top_k = req.top_k;
prepared.chat.stop = req.stop_sequences.clone().map(StopParam::Many);
prepared.chat.stream = req.stream;
prepared
.chat
.validate_supported_fields()
.map_err(anthropic_shape)?;
Ok(prepared)
}
fn anthropic_error(status: StatusCode, message: &str) -> ApiError {
(
status,
Json(json!({
"type": "error",
"error": {
"type": error_type(status),
"message": message,
}
})),
)
}
fn error_type(status: StatusCode) -> &'static str {
match status {
StatusCode::BAD_REQUEST => "invalid_request_error",
StatusCode::UNAUTHORIZED => "authentication_error",
StatusCode::FORBIDDEN => "permission_error",
StatusCode::NOT_FOUND => "not_found_error",
StatusCode::REQUEST_TIMEOUT => "timeout_error",
StatusCode::PAYLOAD_TOO_LARGE => "request_too_large",
StatusCode::TOO_MANY_REQUESTS => "rate_limit_error",
StatusCode::NOT_IMPLEMENTED => "invalid_request_error",
StatusCode::SERVICE_UNAVAILABLE => "overloaded_error",
_ => "api_error",
}
}
fn anthropic_shape(err: ApiError) -> ApiError {
let (status, Json(body)) = err;
let message = body
.pointer("/error/message")
.and_then(Value::as_str)
.unwrap_or("request failed")
.to_string();
anthropic_error(status, &message)
}
fn body_error(err: serde_json::Error) -> ApiError {
anthropic_error(StatusCode::BAD_REQUEST, &err.to_string())
}
fn stop_reason(finish: &str) -> Option<&'static str> {
match finish {
"stop" => Some("end_turn"),
"length" => Some("max_tokens"),
"tool_calls" => Some("tool_use"),
_ => None,
}
}
fn terminal_stop(
finish: &str,
calls: bool,
matched_stop: Option<&str>,
) -> (Option<&'static str>, Value) {
if finish == "length" {
return (stop_reason(finish), Value::Null);
}
if calls {
return (stop_reason("tool_calls"), Value::Null);
}
match matched_stop {
Some(stop) => (Some("stop_sequence"), json!(stop)),
None => (stop_reason(finish), Value::Null),
}
}
fn caller_stop(finish: &crate::generate::FinishReason, caller: &[String]) -> Option<String> {
let matched = finish.matched_stop()?;
caller
.iter()
.any(|s| s == matched)
.then(|| matched.to_string())
}
fn usage_json(usage: &Usage) -> Value {
let cached = usage.cached_tokens.unwrap_or(0);
let mut out = json!({
"input_tokens": usage.prompt_tokens.saturating_sub(cached),
"output_tokens": usage.completion_tokens,
});
if cached > 0 {
out["cache_read_input_tokens"] = json!(cached);
}
out
}
fn parse_json_args(arguments: &str) -> Value {
match serde_json::from_str::<Value>(arguments) {
Ok(value @ Value::Object(_)) => value,
_ => json!({}),
}
}
fn message_body(
parsed: ParsedOutput,
finish: &str,
matched_stop: Option<&str>,
usage: &Usage,
id: &str,
model: &str,
) -> Value {
let mut content: Vec<Value> = Vec::new();
if let Some(reasoning) = parsed.reasoning.filter(|r| !r.is_empty()) {
content.push(json!({"type": "thinking", "thinking": reasoning, "signature": ""}));
}
content.push(json!({"type": "text", "text": parsed.content}));
for (index, call) in parsed.calls.iter().enumerate() {
content.push(json!({
"type": "tool_use",
"id": tool_use_id(&call.name, index),
"name": call.name,
"input": parse_json_args(&call.arguments),
}));
}
let (reason, sequence) = terminal_stop(finish, !parsed.calls.is_empty(), matched_stop);
json!({
"id": id,
"type": "message",
"role": "assistant",
"content": content,
"model": model,
"stop_reason": reason,
"stop_sequence": sequence,
"usage": usage_json(usage),
})
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum GenEvent {
Reasoning(String),
Content(String),
CallStart {
index: usize,
name: String,
},
CallArguments {
index: usize,
fragment: String,
},
CallEnd {
index: usize,
arguments: String,
},
WholeCall {
index: usize,
name: String,
arguments: String,
},
Done {
finish: &'static str,
matched_stop: Option<String>,
usage: Usage,
},
Failed {
status: StatusCode,
message: String,
},
Keepalive,
}
impl crate::stream_events::StreamEvent for GenEvent {
fn keepalive() -> Self {
GenEvent::Keepalive
}
fn content(text: String) -> Self {
GenEvent::Content(text)
}
fn call_start(index: usize, name: String) -> Self {
GenEvent::CallStart { index, name }
}
fn call_arguments(index: usize, fragment: String) -> Self {
GenEvent::CallArguments { index, fragment }
}
fn call_end(index: usize, arguments: String) -> Self {
GenEvent::CallEnd { index, arguments }
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Frame {
name: &'static str,
data: Value,
}
impl Frame {
fn into_event(self) -> Event {
let data = serde_json::to_string(&self.data).unwrap_or_else(|e| {
tracing::error!("failed to serialize an anthropic stream event: {e}");
"{}".to_string()
});
Event::default().event(self.name).data(data)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum OpenBlock {
Text,
Thinking,
Tool {
ordinal: usize,
sent: String,
},
}
pub(crate) struct MessagesStream {
message_id: String,
model: String,
index: usize,
open: Option<OpenBlock>,
calls_opened: usize,
}
impl MessagesStream {
pub(crate) fn new(message_id: String, model: String) -> Self {
MessagesStream {
message_id,
model,
index: 0,
open: None,
calls_opened: 0,
}
}
fn frame(&mut self, name: &'static str, mut data: Value) -> Frame {
if let Some(object) = data.as_object_mut() {
object.insert("type".to_string(), json!(name));
}
Frame { name, data }
}
pub(crate) fn opening(&mut self) -> Vec<Frame> {
let message = json!({
"id": self.message_id,
"type": "message",
"role": "assistant",
"content": [],
"model": self.model,
"usage": {"input_tokens": 0, "output_tokens": 0},
});
vec![self.frame("message_start", json!({"message": message}))]
}
fn close_open(&mut self) -> Vec<Frame> {
let Some(open) = self.open.take() else {
return Vec::new();
};
let index = self.index;
let mut frames = Vec::new();
if open == OpenBlock::Thinking {
frames.push(self.frame(
"content_block_delta",
json!({"index": index, "delta": {"type": "signature_delta", "signature": ""}}),
));
}
frames.push(self.frame("content_block_stop", json!({"index": index})));
self.index += 1;
frames
}
fn open_text(&mut self) -> Vec<Frame> {
let mut frames = self.close_open();
let index = self.index;
self.open = Some(OpenBlock::Text);
frames.push(self.frame(
"content_block_start",
json!({"index": index, "content_block": {"type": "text", "text": ""}}),
));
frames
}
fn open_thinking(&mut self) -> Vec<Frame> {
let mut frames = self.close_open();
let index = self.index;
self.open = Some(OpenBlock::Thinking);
frames.push(self.frame(
"content_block_start",
json!({"index": index, "content_block": {"type": "thinking", "thinking": ""}}),
));
frames
}
fn open_tool(&mut self, name: &str, ordinal: usize) -> Vec<Frame> {
let mut frames = self.close_open();
let index = self.index;
let id = tool_use_id(name, index);
self.open = Some(OpenBlock::Tool {
ordinal,
sent: String::new(),
});
self.calls_opened += 1;
frames.push(self.frame(
"content_block_start",
json!({
"index": index,
"content_block": {"type": "tool_use", "id": id, "name": name, "input": {}},
}),
));
frames
}
fn arguments_delta(&mut self, fragment: &str) -> Option<Frame> {
match &mut self.open {
Some(OpenBlock::Tool { sent, .. }) => sent.push_str(fragment),
_ => return None,
}
let index = self.index;
Some(self.frame(
"content_block_delta",
json!({
"index": index,
"delta": {"type": "input_json_delta", "partial_json": fragment},
}),
))
}
pub(crate) fn push(&mut self, event: GenEvent) -> Vec<Frame> {
match event {
GenEvent::Keepalive => vec![self.frame("ping", json!({}))],
GenEvent::Reasoning(text) => {
if text.is_empty() {
return Vec::new();
}
let mut frames = Vec::new();
if self.open != Some(OpenBlock::Thinking) {
frames.extend(self.open_thinking());
}
let index = self.index;
frames.push(self.frame(
"content_block_delta",
json!({"index": index, "delta": {"type": "thinking_delta", "thinking": text}}),
));
frames
}
GenEvent::Content(text) => {
if text.is_empty() {
return Vec::new();
}
let mut frames = Vec::new();
if self.open != Some(OpenBlock::Text) {
frames.extend(self.open_text());
}
let index = self.index;
frames.push(self.frame(
"content_block_delta",
json!({"index": index, "delta": {"type": "text_delta", "text": text}}),
));
frames
}
GenEvent::CallStart { index, name } => self.open_tool(&name, index),
GenEvent::CallArguments { fragment, .. } => {
self.arguments_delta(&fragment).into_iter().collect()
}
GenEvent::CallEnd { index, arguments } => {
let streamed = match &self.open {
Some(OpenBlock::Tool { ordinal, sent }) if *ordinal == index => sent.clone(),
_ => return Vec::new(),
};
let mut frames = Vec::new();
if let Some(remainder) = arguments.strip_prefix(streamed.as_str()) {
if !remainder.is_empty() {
frames.extend(self.arguments_delta(remainder));
}
}
frames.extend(self.close_open());
frames
}
GenEvent::WholeCall {
index,
name,
arguments,
} => {
let mut frames = self.open_tool(&name, index);
frames.extend(self.arguments_delta(&arguments));
frames.extend(self.close_open());
frames
}
GenEvent::Done {
finish,
matched_stop,
usage,
} => {
let mut frames = self.close_open();
let (reason, sequence) =
terminal_stop(finish, self.calls_opened > 0, matched_stop.as_deref());
let mut delta = json!({});
if let Some(reason) = reason {
delta["stop_reason"] = json!(reason);
}
if !sequence.is_null() {
delta["stop_sequence"] = sequence;
}
let usage = usage_json(&usage);
frames.push(self.frame("message_delta", json!({"delta": delta, "usage": usage})));
frames.push(self.frame("message_stop", json!({})));
frames
}
GenEvent::Failed { status, message } => {
let mut frames = self.close_open();
let error = json!({"type": error_type(status), "message": message});
frames.push(self.frame("error", json!({"error": error})));
frames
}
}
}
}
pub async fn messages(
State(state): State<Arc<AppState>>,
headers: axum::http::HeaderMap,
Json(body): Json<Value>,
) -> Response {
let attribution = attribution::Attribution::from_headers(&headers);
state
.requests_total
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let started = std::time::Instant::now();
let request_id = ferrox_api::next_request_id();
let parsed = serde_json::from_value::<MessagesRequest>(body).map_err(body_error);
let stream = parsed
.as_ref()
.ok()
.and_then(|req| req.stream)
.unwrap_or(false);
let result = async {
crate::cache_admin::check_admission(&state).map_err(anthropic_shape)?;
let req = parsed?;
let _ = &req.metadata;
let prepared = to_chat_request(&req)?;
if stream {
messages_stream(
Arc::clone(&state),
prepared,
request_id.clone(),
started,
attribution.clone(),
)
.await
} else {
messages_full(
Arc::clone(&state),
prepared,
request_id.clone(),
started,
attribution.clone(),
)
.await
}
}
.await;
let mut response = match result {
Ok(response) => response,
Err(err) => err.into_response(),
};
if let Ok(value) = axum::http::HeaderValue::from_str(&request_id) {
response
.headers_mut()
.insert(axum::http::HeaderName::from_static("request-id"), value);
}
if response.status().is_client_error() || response.status().is_server_error() {
state
.request_errors_total
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
state.record_request(stats::Record {
request_id: &request_id,
route: ferrox_api::routes::V1_MESSAGES,
model: state.active_model_name(),
status: response.status().as_u16(),
stream,
duration_ms: started.elapsed().as_millis() as u64,
usage: None,
attribution: &attribution,
});
}
state.mark_request_finished();
response
}
async fn messages_full(
state: Arc<AppState>,
prepared: Prepared,
request_id: String,
started: std::time::Instant,
attribution: attribution::Attribution,
) -> Result<Response, ApiError> {
let active = state.require_active().map_err(anthropic_shape)?;
let chat = prepared.chat;
let template = active
.generative()
.map_err(anthropic_shape)?
.chat_template();
let kwargs = chat.resolve_template_kwargs(&template);
let prompt = prompt_from_messages(&chat.messages, &template, &chat.tools, kwargs)
.map_err(anthropic_shape)?;
let posture = OutputPosture::resolve(active.name(), &prompt);
let caller_stops = chat.stop_sequences();
let params = chat.generation_params_for_template(&template, active.name())?;
let (chunks, finish, usage) = crate::decode_task::buffered(
crate::decode_task::DecodeHandles::take(&state, &active).map_err(anthropic_shape)?,
prompt,
params,
)
.await
.map_err(anthropic_shape)?;
let parsed = output::parse_output(&chunks.concat(), &prepared.parser_tools, posture);
state.record_request(stats::Record {
request_id: &request_id,
route: ferrox_api::routes::V1_MESSAGES,
model: Some(active.name().to_string()),
status: 200,
stream: false,
duration_ms: started.elapsed().as_millis() as u64,
usage: Some(&usage),
attribution: &attribution,
});
let matched_stop = caller_stop(&finish, &caller_stops);
Ok(Json(message_body(
parsed,
finish.as_str(),
matched_stop.as_deref(),
&usage,
&new_id("msg_"),
&chat.model,
))
.into_response())
}
async fn messages_stream(
state: Arc<AppState>,
prepared: Prepared,
request_id: String,
started: std::time::Instant,
attribution: attribution::Attribution,
) -> Result<Response, ApiError> {
let active = state.require_active().map_err(anthropic_shape)?;
let chat = prepared.chat;
let template = active
.generative()
.map_err(anthropic_shape)?
.chat_template();
let kwargs = chat.resolve_template_kwargs(&template);
let prompt = prompt_from_messages(&chat.messages, &template, &chat.tools, kwargs)
.map_err(anthropic_shape)?;
let served_model = active.name().to_string();
let posture = OutputPosture::resolve(&served_model, &prompt);
let caller_stops = chat.stop_sequences();
let mut params = chat.generation_params_for_template(&template, &served_model)?;
let (cancel_token, cancel_guard) = state.cancels.register(&request_id);
params.cancel = Some(cancel_token.clone());
let model = Arc::clone(active.generative().map_err(anthropic_shape)?);
let kv_pool = state.kv_pool.clone();
let paged_kv = state.paged_kv.clone();
let prefix_cache = state.prefix_cache.clone();
let batcher = active.batcher.clone();
let ceiling = active.ceiling.clone();
let metal_private_decode_gate = state.metal_private_decode_gate.clone();
let overlap = true;
let offered = prepared.parser_tools;
let stats_state = Arc::clone(&state);
let stats_request_id = request_id.clone();
let (tx, rx) = tokio::sync::mpsc::channel::<GenEvent>(64);
tokio::task::spawn_blocking(move || {
let _cancel_guard = cancel_guard;
let orphan = sse::orphan_timeout_from_env();
let send = |event: GenEvent| {
if sse::send_or_orphan(&tx, event, orphan).is_err() {
cancel_token.cancel();
}
};
let mut reasoning = posture.reasoning_parser();
let mut tools = (!offered.is_empty()).then(|| posture.tool_call_parser(&offered));
let result = run_generation_emit(
&model,
&prompt,
¶ms,
kv_pool.as_ref(),
paged_kv.as_ref(),
prefix_cache.as_deref(),
batcher.as_ref(),
ceiling.as_deref(),
metal_private_decode_gate.as_deref(),
|chunk| {
if !overlap || chunk.is_empty() {
return;
}
let (thought, content) = match reasoning.as_mut() {
Some(parser) => {
let delta = parser.push(chunk);
(delta.reasoning, delta.content)
}
None => (String::new(), chunk.to_string()),
};
if !thought.is_empty() {
send(GenEvent::Reasoning(thought));
}
match tools.as_mut() {
Some(parser) => {
for event in parser.push(&content) {
for mapped in map_tool_event(event) {
send(mapped);
}
}
}
None if !content.is_empty() => send(GenEvent::Content(content)),
None => {}
}
},
);
match result {
Ok((finish, usage, full_text)) => {
if overlap {
let tail = reasoning.as_mut().map(|p| p.flush()).unwrap_or_default();
if !tail.reasoning.is_empty() {
send(GenEvent::Reasoning(tail.reasoning));
}
match tools.as_mut() {
Some(parser) => {
let mut events = parser.push(&tail.content);
events.extend(parser.finish());
for event in events {
for mapped in map_tool_event(event) {
send(mapped);
}
}
}
None if !tail.content.is_empty() => send(GenEvent::Content(tail.content)),
None => {}
}
} else {
let parsed = output::parse_output(&full_text, &offered, posture);
if let Some(thought) = parsed.reasoning.filter(|r| !r.is_empty()) {
send(GenEvent::Reasoning(thought));
}
if !parsed.content.is_empty() {
send(GenEvent::Content(parsed.content));
}
for (index, call) in parsed.calls.into_iter().enumerate() {
send(GenEvent::WholeCall {
index,
name: call.name,
arguments: call.arguments,
});
}
}
stats_state.record_request(stats::Record {
request_id: &stats_request_id,
route: ferrox_api::routes::V1_MESSAGES,
model: Some(served_model.clone()),
status: 200,
stream: true,
duration_ms: started.elapsed().as_millis() as u64,
usage: Some(&usage),
attribution: &attribution,
});
send(GenEvent::Done {
finish: finish.as_str(),
matched_stop: caller_stop(&finish, &caller_stops),
usage,
});
}
Err(e) => {
tracing::warn!("decode error on streamed message {stats_request_id}: {e}");
stats_state.record_request(stats::Record {
request_id: &stats_request_id,
route: ferrox_api::routes::V1_MESSAGES,
model: Some(served_model.clone()),
status: 500,
stream: true,
duration_ms: started.elapsed().as_millis() as u64,
usage: None,
attribution: &attribution,
});
let (status, Json(body)) = decode_error_response(e);
let message = body
.pointer("/error/message")
.and_then(Value::as_str)
.unwrap_or("generation failed")
.to_string();
send(GenEvent::Failed { status, message });
}
}
});
let mut machine = MessagesStream::new(new_id("msg_"), chat.model.clone());
let queue: VecDeque<Frame> = machine.opening().into();
let events = Box::pin(with_keepalive(rx, KEEPALIVE_INTERVAL));
let stream = futures_util::stream::unfold(
(machine, events, queue),
|(mut machine, mut events, mut queue)| async move {
loop {
if let Some(frame) = queue.pop_front() {
return Some((
Ok::<Event, Infallible>(frame.into_event()),
(machine, events, queue),
));
}
let event = events.next().await?;
queue.extend(machine.push(event));
}
},
);
Ok((
[(
axum::http::HeaderName::from_static("x-accel-buffering"),
axum::http::HeaderValue::from_static("no"),
)],
Sse::new(stream),
)
.into_response())
}
pub(crate) async fn count_tokens(
State(state): State<Arc<AppState>>,
headers: axum::http::HeaderMap,
Json(body): Json<Value>,
) -> Response {
let attribution = attribution::Attribution::from_headers(&headers);
let started = std::time::Instant::now();
let request_id = ferrox_api::next_request_id();
let result = (|| {
let req = serde_json::from_value::<CountTokensRequest>(body).map_err(body_error)?;
let prepared = countable_prompt(&req)?;
let model = state.require_model().map_err(anthropic_shape)?;
let template = model.chat_template();
let kwargs = prepared.chat.resolve_template_kwargs(&template);
let prompt = prompt_from_messages(
&prepared.chat.messages,
&template,
&prepared.chat.tools,
kwargs,
)
.map_err(anthropic_shape)?;
let input_tokens = model.encode(&prompt).len();
Ok::<Value, ApiError>(json!({"input_tokens": input_tokens}))
})();
let response = match result {
Ok(body) => Json(body).into_response(),
Err(err) => err.into_response(),
};
if response.status().is_client_error() || response.status().is_server_error() {
state
.request_errors_total
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
state.record_request(stats::Record {
request_id: &request_id,
route: ferrox_api::routes::V1_MESSAGES_COUNT_TOKENS,
model: state.active_model_name(),
status: response.status().as_u16(),
stream: false,
duration_ms: started.elapsed().as_millis() as u64,
usage: None,
attribution: &attribution,
});
response
}
fn countable_prompt(req: &CountTokensRequest) -> Result<Prepared, ApiError> {
if req.prompt.messages.is_empty() {
return Err(anthropic_error(
StatusCode::BAD_REQUEST,
"messages: at least one message is required",
));
}
let prepared = prepare_prompt(&req.prompt, req.model.clone(), 1);
if prepared.chat.messages.is_empty() {
return Err(anthropic_error(
StatusCode::BAD_REQUEST,
"messages: no tokenizable content",
));
}
Ok(prepared)
}
#[cfg(test)]
mod tests {
use super::*;
fn messages_request(value: Value) -> MessagesRequest {
serde_json::from_value(value).expect("the fixture is a valid Messages request")
}
fn count_request(value: Value) -> CountTokensRequest {
serde_json::from_value(value).expect("the fixture is a valid count_tokens request")
}
fn converted(value: Value) -> Prepared {
to_chat_request(&messages_request(value)).expect("the fixture converts")
}
fn text_of(message: &ChatMessage) -> String {
message
.content
.as_ref()
.map(MessageContent::as_text)
.unwrap_or_default()
}
fn shape(messages: &[ChatMessage]) -> Vec<(String, String, Option<String>, Vec<String>)> {
messages
.iter()
.map(|m| {
(
m.role.clone(),
text_of(m),
m.tool_call_id.clone(),
m.tool_calls
.as_ref()
.map(|calls| {
calls
.iter()
.map(|c| format!("{}({})", c.function.name, c.function.arguments))
.collect()
})
.unwrap_or_default(),
)
})
.collect()
}
fn usage(prompt: usize, completion: usize) -> Usage {
Usage::new(prompt, completion)
}
fn run(events: Vec<GenEvent>) -> Vec<Frame> {
let mut machine = MessagesStream::new("msg_test".to_string(), "test-model".to_string());
let mut frames = machine.opening();
for event in events {
frames.extend(machine.push(event));
}
frames
}
fn names(frames: &[Frame]) -> Vec<&str> {
frames.iter().map(|f| f.name).collect()
}
fn only(frames: &[Frame], name: &str) -> Vec<Value> {
frames
.iter()
.filter(|f| f.name == name)
.map(|f| f.data.clone())
.collect()
}
#[test]
fn the_top_level_system_and_a_system_role_message_merge_into_one_leading_message() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"system": "you are terse",
"messages": [
{"role": "system", "content": "and precise"},
{"role": "user", "content": "hi"},
],
}));
let messages = &prepared.chat.messages;
assert_eq!(
messages.iter().filter(|m| m.role == "system").count(),
1,
"exactly one system message must survive: {:?}",
shape(messages)
);
assert_eq!(messages[0].role, "system", "and it must lead");
assert_eq!(text_of(&messages[0]), "you are terse\n\nand precise");
assert_eq!(messages[1].role, "user");
}
#[test]
fn a_system_message_in_the_middle_of_the_array_still_leads_the_conversation() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [
{"role": "user", "content": "hi"},
{"role": "system", "content": "be terse"},
{"role": "assistant", "content": "ok"},
],
}));
assert_eq!(
shape(&prepared.chat.messages)
.iter()
.map(|(role, ..)| role.clone())
.collect::<Vec<_>>(),
vec!["system", "user", "assistant"]
);
}
#[test]
fn a_block_list_system_concatenates_and_an_absent_one_adds_no_message() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"system": [{"type": "text", "text": "a"}, {"type": "text", "text": "b"}],
"messages": [{"role": "user", "content": "hi"}],
}));
assert_eq!(text_of(&prepared.chat.messages[0]), "ab");
let bare = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [{"role": "user", "content": "hi"}],
}));
assert_eq!(bare.chat.messages.len(), 1);
assert_eq!(bare.chat.messages[0].role, "user");
}
#[test]
fn a_tool_result_is_keyed_by_its_tool_use_id_and_not_by_its_own_id() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [
{"role": "assistant", "content": [
{"type": "tool_use", "id": "toolu_read", "name": "read", "input": {"path": "a"}},
{"type": "tool_use", "id": "toolu_grep", "name": "grep", "input": {"q": "b"}},
]},
{"role": "user", "content": [
{"type": "tool_result", "id": "block_1", "tool_use_id": "toolu_read",
"content": "file a"},
{"type": "tool_result", "id": "block_2", "tool_use_id": "toolu_grep",
"content": "no match"},
]},
],
}));
let tools: Vec<_> = prepared
.chat
.messages
.iter()
.filter(|m| m.role == "tool")
.map(|m| (m.tool_call_id.clone().unwrap_or_default(), text_of(m)))
.collect();
assert_eq!(
tools,
vec![
("toolu_read".to_string(), "file a".to_string()),
("toolu_grep".to_string(), "no match".to_string()),
],
"each result must name the call it answers, not its own block id"
);
}
#[test]
fn a_tool_use_block_becomes_a_tool_call_with_json_encoded_arguments() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [{"role": "assistant", "content": [
{"type": "tool_use", "id": "toolu_1", "name": "read", "input": {"path": "a.rs"}},
]}],
}));
let calls = prepared.chat.messages[0]
.tool_calls
.as_ref()
.expect("the assistant turn carries its call");
assert_eq!(calls[0].function.name, "read");
assert_eq!(calls[0].function.arguments, r#"{"path":"a.rs"}"#);
assert!(
prepared.chat.messages[0].content.is_none(),
"a turn that only called tools has no content, per the OpenAI convention"
);
}
#[test]
fn a_block_list_tool_result_flattens_and_an_assistant_side_result_becomes_prose() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "t1",
"content": [{"type": "text", "text": "one"}, {"type": "text", "text": "two"}]},
]},
{"role": "assistant", "content": [
{"type": "tool_result", "tool_use_id": "t2", "content": "late"},
]},
],
}));
let shaped = shape(&prepared.chat.messages);
assert_eq!(shaped[0].0, "tool");
assert_eq!(shaped[0].1, "onetwo");
assert_eq!(shaped[1].0, "assistant");
assert_eq!(shaped[1].1, "Tool result: late");
}
#[test]
fn a_tool_result_and_a_question_in_one_turn_keep_their_order() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "t1", "content": "42"},
{"type": "text", "text": "now what?"},
]}],
}));
let shaped = shape(&prepared.chat.messages);
assert_eq!(shaped[0].0, "tool");
assert_eq!(shaped[1].0, "user");
assert_eq!(shaped[1].1, "now what?");
}
#[test]
fn redacted_thinking_images_and_unknown_blocks_are_skipped_rather_than_refused() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [{"role": "user", "content": [
{"type": "redacted_thinking", "data": "opaque"},
{"type": "image", "source": {"type": "base64", "data": "..."}},
{"type": "some_future_block", "whatever": 1},
{"type": "text", "text": "what is this?"},
]}],
}));
assert_eq!(
prepared.chat.messages.len(),
1,
"the request still converts: {:?}",
shape(&prepared.chat.messages)
);
assert_eq!(text_of(&prepared.chat.messages[0]), "what is this?");
}
#[test]
fn a_message_whose_blocks_are_all_skipped_contributes_no_turn() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [
{"role": "user", "content": [
{"type": "image", "source": {"type": "base64", "data": "..."}},
]},
{"role": "user", "content": "still here"},
],
}));
assert_eq!(prepared.chat.messages.len(), 1);
assert_eq!(text_of(&prepared.chat.messages[0]), "still here");
}
#[test]
fn a_thinking_block_is_replayed_beside_the_answer_and_never_as_content() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [{"role": "assistant", "content": [
{"type": "thinking", "thinking": "the user probably means X"},
{"type": "text", "text": "X."},
]}],
}));
assert_eq!(text_of(&prepared.chat.messages[0]), "X.");
assert_eq!(
prepared.chat.messages[0].reasoning_content.as_deref(),
Some("the user probably means X"),
);
let thinking_only = converted(json!({
"model": "m",
"max_tokens": 16,
"messages": [{"role": "assistant", "content": [
{"type": "thinking", "thinking": "hmm"},
]}],
}));
assert!(
thinking_only.chat.messages.is_empty(),
"a turn that held only thinking contributes no message"
);
}
fn tools_fixture() -> Value {
json!([
{"name": "read", "description": "read a file",
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}}}},
{"name": "write", "description": "write a file", "input_schema": {}},
])
}
#[test]
fn tool_choice_none_hides_the_tools_from_the_template_and_from_the_parser() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"tools": tools_fixture(),
"tool_choice": {"type": "none"},
"messages": [{"role": "user", "content": "hi"}],
}));
assert!(prepared.chat.tools.is_empty(), "template offers nothing");
assert!(prepared.parser_tools.is_empty(), "parser is disarmed");
}
#[test]
fn a_named_tool_choice_narrows_the_template_list_but_not_the_parser() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"tools": tools_fixture(),
"tool_choice": {"type": "tool", "name": "write"},
"messages": [{"role": "user", "content": "hi"}],
}));
assert_eq!(
prepared
.chat
.tools
.iter()
.map(|t| t.function.name.clone())
.collect::<Vec<_>>(),
vec!["write"]
);
assert_eq!(
prepared
.parser_tools
.iter()
.map(|t| t.function.name.clone())
.collect::<Vec<_>>(),
vec!["read", "write"]
);
}
#[test]
fn auto_offers_every_tool_and_a_typeless_schema_is_given_object() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 16,
"tools": tools_fixture(),
"tool_choice": {"type": "auto"},
"messages": [{"role": "user", "content": "hi"}],
}));
assert_eq!(prepared.chat.tools.len(), 2);
assert_eq!(prepared.parser_tools.len(), 2);
let write = &prepared.chat.tools[1].function;
assert_eq!(
write.parameters.as_ref().unwrap()["type"],
json!("object"),
"a schema with no type is given one"
);
}
#[test]
fn the_sampling_knobs_land_on_the_shared_chat_request() {
let prepared = converted(json!({
"model": "m",
"max_tokens": 128,
"temperature": 0.5,
"top_p": 0.9,
"top_k": 40,
"stop_sequences": ["END"],
"messages": [{"role": "user", "content": "hi"}],
}));
assert_eq!(prepared.chat.max_tokens, 128);
assert_eq!(prepared.chat.temperature, Some(0.5));
assert_eq!(prepared.chat.top_p, Some(0.9));
assert_eq!(prepared.chat.top_k, Some(40));
assert_eq!(prepared.chat.stop_sequences(), vec!["END".to_string()]);
}
#[test]
fn a_zero_max_tokens_is_refused_in_the_anthropic_error_envelope() {
let req = messages_request(json!({
"model": "m",
"max_tokens": 0,
"messages": [{"role": "user", "content": "hi"}],
}));
let Err((status, Json(body))) = to_chat_request(&req) else {
panic!("max_tokens: 0 must be refused");
};
assert_eq!(status, StatusCode::BAD_REQUEST);
assert_eq!(body["type"], json!("error"));
assert_eq!(body["error"]["type"], json!("invalid_request_error"));
assert!(body["error"]["message"]
.as_str()
.is_some_and(|m| m.contains("max_tokens")));
}
#[test]
fn the_thinking_toggle_is_handed_to_the_shared_switch() {
let on = converted(json!({
"model": "m",
"max_tokens": 16,
"thinking": {"type": "enabled", "budget_tokens": 1024},
"messages": [{"role": "user", "content": "hi"}],
}));
assert_eq!(
on.chat.thinking.as_ref().map(|t| t.kind.as_str()),
Some("enabled")
);
let off = converted(json!({
"model": "m",
"max_tokens": 16,
"thinking": {"type": "disabled"},
"messages": [{"role": "user", "content": "hi"}],
}));
assert_eq!(
off.chat.thinking.as_ref().map(|t| t.kind.as_str()),
Some("disabled")
);
}
#[test]
fn usage_excludes_the_cached_prefix_from_input_tokens() {
let cached = usage(100, 7).with_cached_tokens(40);
assert_eq!(
usage_json(&cached),
json!({
"input_tokens": 60,
"output_tokens": 7,
"cache_read_input_tokens": 40,
})
);
}
#[test]
fn a_zero_cache_read_is_absent_rather_than_zero() {
for usage in [usage(10, 2), usage(10, 2).with_cached_tokens(0)] {
let json = usage_json(&usage);
assert_eq!(json["input_tokens"], json!(10));
assert!(
json.get("cache_read_input_tokens").is_none(),
"nothing was served from cache, so the field must be absent"
);
}
}
#[test]
fn the_stream_terminates_on_message_stop_with_no_done_sentinel() {
let frames = run(vec![
GenEvent::Content("hi".into()),
GenEvent::Done {
finish: "stop",
matched_stop: None,
usage: usage(5, 1),
},
]);
assert_eq!(
names(&frames).last().copied(),
Some("message_stop"),
"message_stop is the last event"
);
assert!(
!frames
.iter()
.any(|f| f.name == "done" || f.data == json!("[DONE]")),
"no OpenAI sentinel may follow it: {:?}",
names(&frames)
);
}
#[test]
fn a_text_answer_opens_and_closes_one_block_between_start_and_stop() {
let frames = run(vec![
GenEvent::Content("he".into()),
GenEvent::Content("llo".into()),
GenEvent::Done {
finish: "stop",
matched_stop: None,
usage: usage(5, 2),
},
]);
assert_eq!(
names(&frames),
vec![
"message_start",
"content_block_start",
"content_block_delta",
"content_block_delta",
"content_block_stop",
"message_delta",
"message_stop",
]
);
let start = &only(&frames, "message_start")[0];
assert_eq!(start["message"]["role"], json!("assistant"));
assert_eq!(start["message"]["content"], json!([]));
assert_eq!(start["message"]["usage"]["input_tokens"], json!(0));
}
#[test]
fn the_block_index_advances_on_every_content_block_stop() {
let frames = run(vec![
GenEvent::Reasoning("think".into()),
GenEvent::Content("say".into()),
GenEvent::CallStart {
index: 0,
name: "read".into(),
},
GenEvent::CallEnd {
index: 0,
arguments: "{}".into(),
},
GenEvent::Done {
finish: "stop",
matched_stop: None,
usage: usage(5, 3),
},
]);
let indexes: Vec<i64> = frames
.iter()
.filter(|f| f.name == "content_block_start")
.map(|f| f.data["index"].as_i64().unwrap())
.collect();
assert_eq!(indexes, vec![0, 1, 2], "three blocks, numbered in order");
let stops: Vec<i64> = frames
.iter()
.filter(|f| f.name == "content_block_stop")
.map(|f| f.data["index"].as_i64().unwrap())
.collect();
assert_eq!(stops, vec![0, 1, 2], "each closes the index it opened");
}
#[test]
fn a_thinking_block_closes_with_an_empty_signature_delta_first() {
let frames = run(vec![
GenEvent::Reasoning("weighing it up".into()),
GenEvent::Content("answer".into()),
GenEvent::Done {
finish: "stop",
matched_stop: None,
usage: usage(5, 4),
},
]);
let deltas = only(&frames, "content_block_delta");
assert_eq!(deltas[0]["delta"]["type"], json!("thinking_delta"));
assert_eq!(
deltas[1]["delta"],
json!({"type": "signature_delta", "signature": ""})
);
let order = names(&frames);
let signature = order
.iter()
.position(|n| *n == "content_block_delta")
.unwrap()
+ 1;
assert_eq!(
order[signature + 1],
"content_block_stop",
"the signature delta comes immediately before the stop"
);
}
#[test]
fn a_tool_block_streams_its_arguments_and_tops_up_the_remainder_at_close() {
let frames = run(vec![
GenEvent::CallStart {
index: 0,
name: "read_file".into(),
},
GenEvent::CallArguments {
index: 0,
fragment: r#"{"path":"#.into(),
},
GenEvent::CallEnd {
index: 0,
arguments: r#"{"path":"a.rs"}"#.into(),
},
GenEvent::Done {
finish: "stop",
matched_stop: None,
usage: usage(5, 5),
},
]);
let start = &only(&frames, "content_block_start")[0];
assert_eq!(start["content_block"]["type"], json!("tool_use"));
assert_eq!(start["content_block"]["name"], json!("read_file"));
assert_eq!(start["content_block"]["input"], json!({}));
assert!(
start["content_block"]["id"]
.as_str()
.is_some_and(|id| id.starts_with("toolu_read-file_0_")),
"the id names its tool and block: {}",
start["content_block"]["id"]
);
let fragments: Vec<String> = only(&frames, "content_block_delta")
.iter()
.map(|d| d["delta"]["partial_json"].as_str().unwrap().to_string())
.collect();
assert_eq!(
fragments.concat(),
r#"{"path":"a.rs"}"#,
"the concatenated fragments are exactly the final arguments"
);
}
#[test]
fn a_close_tops_up_only_the_remainder_of_what_already_streamed() {
let frames = run(vec![
GenEvent::CallStart {
index: 0,
name: "t".into(),
},
GenEvent::CallArguments {
index: 0,
fragment: r#"{"a":1"#.into(),
},
GenEvent::CallEnd {
index: 0,
arguments: r#"{"a":1}"#.into(),
},
]);
let fragments: Vec<String> = only(&frames, "content_block_delta")
.iter()
.map(|d| d["delta"]["partial_json"].as_str().unwrap().to_string())
.collect();
assert_eq!(fragments, vec![r#"{"a":1"#.to_string(), "}".to_string()]);
}
#[test]
fn a_whole_call_opens_delivers_and_closes_in_one_step() {
let frames = run(vec![GenEvent::WholeCall {
index: 0,
name: "t".into(),
arguments: r#"{"a":1}"#.into(),
}]);
assert_eq!(
names(&frames),
vec![
"message_start",
"content_block_start",
"content_block_delta",
"content_block_stop",
]
);
assert_eq!(
only(&frames, "content_block_delta")[0]["delta"]["partial_json"],
json!(r#"{"a":1}"#)
);
}
#[test]
fn a_turn_that_opened_a_tool_block_reports_the_tool_use_stop_reason() {
let frames = run(vec![
GenEvent::WholeCall {
index: 0,
name: "t".into(),
arguments: "{}".into(),
},
GenEvent::Done {
finish: "stop",
matched_stop: None,
usage: usage(5, 6),
},
]);
assert_eq!(
only(&frames, "message_delta")[0]["delta"]["stop_reason"],
json!("tool_use")
);
}
#[test]
fn a_truncated_turn_reports_max_tokens_even_after_opening_a_call() {
let frames = run(vec![
GenEvent::WholeCall {
index: 0,
name: "t".into(),
arguments: "{}".into(),
},
GenEvent::Done {
finish: "length",
matched_stop: None,
usage: usage(5, 7),
},
]);
assert_eq!(
only(&frames, "message_delta")[0]["delta"]["stop_reason"],
json!("max_tokens")
);
}
#[test]
fn a_cancelled_generation_reports_no_stop_reason() {
let frames = run(vec![GenEvent::Done {
finish: "cancelled",
matched_stop: None,
usage: usage(5, 8),
}]);
let delta = &only(&frames, "message_delta")[0]["delta"];
assert!(
delta.get("stop_reason").is_none(),
"an interrupted turn claims nothing: {delta}"
);
}
#[test]
fn the_terminal_message_delta_carries_the_cache_split_usage() {
let frames = run(vec![GenEvent::Done {
finish: "stop",
matched_stop: None,
usage: usage(100, 9).with_cached_tokens(40),
}]);
assert_eq!(
only(&frames, "message_delta")[0]["usage"],
json!({"input_tokens": 60, "output_tokens": 9, "cache_read_input_tokens": 40})
);
}
#[test]
fn an_open_block_is_closed_before_the_terminal_events() {
let frames = run(vec![
GenEvent::Content("half a sen".into()),
GenEvent::Done {
finish: "length",
matched_stop: None,
usage: usage(5, 10),
},
]);
let order = names(&frames);
let stop = order.iter().position(|n| *n == "content_block_stop");
let delta = order.iter().position(|n| *n == "message_delta");
assert!(stop < delta, "the block closes first: {order:?}");
}
#[test]
fn a_mid_stream_failure_closes_the_open_block_and_emits_an_error_event() {
let frames = run(vec![
GenEvent::Content("partial".into()),
GenEvent::Failed {
status: StatusCode::SERVICE_UNAVAILABLE,
message: "kv pool exhausted".into(),
},
]);
assert_eq!(
names(&frames),
vec![
"message_start",
"content_block_start",
"content_block_delta",
"content_block_stop",
"error",
]
);
assert_eq!(
only(&frames, "error")[0]["error"],
json!({"type": "overloaded_error", "message": "kv pool exhausted"})
);
}
#[test]
fn a_keepalive_is_a_protocol_native_ping() {
let frames = run(vec![GenEvent::Keepalive]);
assert_eq!(names(&frames), vec!["message_start", "ping"]);
assert_eq!(only(&frames, "ping")[0], json!({"type": "ping"}));
}
#[test]
fn an_empty_delta_produces_no_frame() {
let frames = run(vec![
GenEvent::Content(String::new()),
GenEvent::Reasoning(String::new()),
]);
assert_eq!(names(&frames), vec!["message_start"]);
}
#[test]
fn a_stray_arguments_fragment_is_dropped() {
let frames = run(vec![GenEvent::CallArguments {
index: 0,
fragment: r#"{"a":1}"#.into(),
}]);
assert_eq!(names(&frames), vec!["message_start"]);
}
#[test]
fn a_frame_serializes_as_a_named_sse_event() {
let frames = run(vec![GenEvent::Keepalive]);
let encoded = format!("{:?}", frames[1].clone().into_event());
assert!(encoded.contains("ping"), "{encoded}");
assert_eq!(frames[1].data["type"], json!("ping"));
}
#[test]
fn a_buffered_answer_orders_thinking_then_text_then_calls() {
let parsed = ParsedOutput {
reasoning: Some("thought".into()),
content: String::new(),
calls: vec![crate::output::ParsedToolCall {
name: "read".into(),
arguments: r#"{"path":"a"}"#.into(),
}],
};
let body = message_body(parsed, "stop", None, &usage(11, 3), "msg_1", "m");
let kinds: Vec<&str> = body["content"]
.as_array()
.unwrap()
.iter()
.map(|b| b["type"].as_str().unwrap())
.collect();
assert_eq!(kinds, vec!["thinking", "text", "tool_use"]);
assert_eq!(body["content"][0]["signature"], json!(""));
assert_eq!(body["content"][2]["input"], json!({"path": "a"}));
assert_eq!(body["stop_reason"], json!("tool_use"));
assert_eq!(
body["usage"],
json!({"input_tokens": 11, "output_tokens": 3})
);
}
#[test]
fn unparseable_tool_arguments_become_an_empty_input_object() {
let parsed = ParsedOutput {
reasoning: None,
content: String::new(),
calls: vec![crate::output::ParsedToolCall {
name: "t".into(),
arguments: "not json".into(),
}],
};
let body = message_body(parsed, "stop", None, &usage(1, 1), "msg_1", "m");
assert_eq!(body["content"][1]["input"], json!({}));
}
#[test]
fn count_tokens_converts_a_body_exactly_as_a_generation_of_it_would() {
let body = json!({
"system": "be terse",
"tools": tools_fixture(),
"tool_choice": {"type": "tool", "name": "write"},
"thinking": {"type": "enabled"},
"messages": [
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "t1", "content": "42"},
{"type": "text", "text": "and now?"},
]},
{"role": "assistant", "content": [
{"type": "thinking", "thinking": "hmm"},
{"type": "tool_use", "id": "t2", "name": "read", "input": {"path": "a"}},
]},
],
});
let mut generation = body.clone();
generation["model"] = json!("m");
generation["max_tokens"] = json!(64);
let generated = converted(generation);
let mut counted_body = body;
counted_body["model"] = json!("m");
let counted = countable_prompt(&count_request(counted_body)).expect("converts");
assert_eq!(
shape(&counted.chat.messages),
shape(&generated.chat.messages)
);
assert_eq!(
counted
.chat
.tools
.iter()
.map(|t| t.function.name.clone())
.collect::<Vec<_>>(),
generated
.chat
.tools
.iter()
.map(|t| t.function.name.clone())
.collect::<Vec<_>>(),
"the template is offered the same tools"
);
assert_eq!(
counted.chat.thinking.map(|t| t.kind),
generated.chat.thinking.map(|t| t.kind),
"and renders with the same thinking direction"
);
}
#[test]
fn count_tokens_refuses_an_empty_messages_array() {
let req = count_request(json!({"model": "m", "messages": []}));
let Err((status, Json(body))) = countable_prompt(&req) else {
panic!("an empty messages array must be refused");
};
assert_eq!(status, StatusCode::BAD_REQUEST);
assert_eq!(body["type"], json!("error"));
assert!(body["error"]["message"]
.as_str()
.is_some_and(|m| m.contains("at least one message")));
}
#[test]
fn count_tokens_distinguishes_no_messages_from_no_tokenizable_content() {
let req = count_request(json!({
"model": "m",
"messages": [{"role": "user", "content": [
{"type": "image", "source": {"type": "base64", "data": "..."}},
]}],
}));
let Err((status, Json(body))) = countable_prompt(&req) else {
panic!("an image-only conversation must be refused");
};
assert_eq!(status, StatusCode::BAD_REQUEST);
assert_eq!(
body["error"]["message"],
json!("messages: no tokenizable content")
);
}
#[test]
fn count_tokens_accepts_a_body_with_tokenizable_content() {
let req = count_request(json!({
"model": "m",
"messages": [{"role": "user", "content": "hello"}],
}));
let prepared = countable_prompt(&req).expect("this one counts");
assert_eq!(text_of(&prepared.chat.messages[0]), "hello");
}
#[test]
fn a_shared_error_is_re_dressed_in_the_anthropic_envelope() {
let (status, Json(body)) = anthropic_shape(crate::invalid_request("bad thing", "field"));
assert_eq!(status, StatusCode::BAD_REQUEST);
assert_eq!(
body,
json!({
"type": "error",
"error": {"type": "invalid_request_error", "message": "bad thing"},
})
);
}
#[test]
fn every_status_maps_onto_the_anthropic_error_vocabulary() {
assert_eq!(error_type(StatusCode::BAD_REQUEST), "invalid_request_error");
assert_eq!(
error_type(StatusCode::SERVICE_UNAVAILABLE),
"overloaded_error"
);
assert_eq!(
error_type(StatusCode::TOO_MANY_REQUESTS),
"rate_limit_error"
);
assert_eq!(error_type(StatusCode::INTERNAL_SERVER_ERROR), "api_error");
assert_eq!(error_type(StatusCode::IM_A_TEAPOT), "api_error");
}
#[test]
fn a_malformed_body_is_refused_in_the_anthropic_envelope() {
let err = serde_json::from_value::<MessagesRequest>(json!({"model": "m"}))
.expect_err("max_tokens and messages are required");
let (status, Json(body)) = body_error(err);
assert_eq!(status, StatusCode::BAD_REQUEST);
assert_eq!(body["type"], json!("error"));
assert_eq!(body["error"]["type"], json!("invalid_request_error"));
}
#[test]
fn a_callers_own_stop_string_is_reported_as_a_stop_sequence_with_the_string() {
let parsed = ParsedOutput {
reasoning: None,
content: "up to here".into(),
calls: Vec::new(),
};
let body = message_body(parsed, "stop", Some("END"), &usage(4, 2), "msg_1", "m");
assert_eq!(body["stop_reason"], "stop_sequence");
assert_eq!(body["stop_sequence"], "END");
}
#[test]
fn a_turn_the_model_ended_itself_stays_end_turn_with_no_sequence() {
let parsed = ParsedOutput {
reasoning: None,
content: "done".into(),
calls: Vec::new(),
};
let body = message_body(parsed, "stop", None, &usage(4, 2), "msg_1", "m");
assert_eq!(body["stop_reason"], "end_turn");
assert!(body["stop_sequence"].is_null());
}
#[test]
fn truncation_outranks_a_stop_string_and_calls_outrank_it_too() {
let call = || crate::output::ParsedToolCall {
name: "read".into(),
arguments: "{}".into(),
};
let truncated = ParsedOutput {
reasoning: None,
content: "half".into(),
calls: vec![call()],
};
let body = message_body(truncated, "length", Some("END"), &usage(4, 2), "i", "m");
assert_eq!(body["stop_reason"], "max_tokens");
assert!(body["stop_sequence"].is_null());
let with_call = ParsedOutput {
reasoning: None,
content: String::new(),
calls: vec![call()],
};
let body = message_body(with_call, "stop", Some("END"), &usage(4, 2), "i", "m");
assert_eq!(body["stop_reason"], "tool_use");
assert!(body["stop_sequence"].is_null());
}
#[test]
fn the_terminal_delta_carries_the_stop_sequence_only_when_there_is_one() {
let frames = run(vec![GenEvent::Done {
finish: "stop",
matched_stop: Some("END".to_string()),
usage: usage(4, 2),
}]);
let delta = &only(&frames, "message_delta")[0];
assert_eq!(delta["delta"]["stop_reason"], "stop_sequence");
assert_eq!(delta["delta"]["stop_sequence"], "END");
let frames = run(vec![GenEvent::Done {
finish: "stop",
matched_stop: None,
usage: usage(4, 2),
}]);
let delta = &only(&frames, "message_delta")[0];
assert_eq!(delta["delta"]["stop_reason"], "end_turn");
assert!(delta["delta"].get("stop_sequence").is_none());
}
#[test]
fn only_a_stop_the_client_asked_for_can_become_a_stop_sequence() {
let caller = vec!["END".to_string()];
assert_eq!(
caller_stop(
&crate::generate::FinishReason::StopSequence("END".into()),
&caller
),
Some("END".to_string())
);
assert_eq!(
caller_stop(
&crate::generate::FinishReason::StopSequence("<|im_end|>".into()),
&caller
),
None,
"a template's own marker is not the caller's fence"
);
assert_eq!(
caller_stop(&crate::generate::FinishReason::Stop, &caller),
None
);
}
}