blob: 6b7b0e2cd1542c1eadea4d02129c77120527757f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Anthropic Messages protocol. This module owns wire types and request-local
//! state; authentication, network access and cancellation belong to the host.
pub mod citations;
pub mod error;
pub mod hosted;
pub mod prompt;
pub mod request;
pub mod response;
mod schema;
pub mod stream;
#[cfg(test)]
mod tests;
mod tools;
pub use schema::constrained_schema;
pub use tools::*;
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// Provider-reported accounting. Unknown fields remain available to the caller
/// through its raw response; they do not influence executor token totals.
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Usage {
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_creation_input_tokens: Option<u64>,
pub cache_read_input_tokens: Option<u64>,
pub output_tokens_details: Option<OutputTokensDetails>,
pub iterations: Option<Vec<UsageIteration>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OutputTokensDetails {
pub thinking_tokens: Option<u64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UsageIteration {
#[serde(rename = "type")]
pub kind: IterationKind,
pub input_tokens: u64,
pub output_tokens: u64,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum IterationKind {
Message,
Compaction,
AdvisorMessage,
FallbackMessage,
#[serde(other)]
Other,
}
#[derive(Debug, PartialEq, Eq)]
pub struct TokenCounts {
pub input: u64,
pub uncached_input: u64,
pub cache_read: u64,
pub cache_write: u64,
pub output: u64,
pub reasoning: Option<u64>,
}
impl Usage {
pub fn totals(&self) -> Result<TokenCounts> {
let iterations = self.iterations.as_deref().unwrap_or_default();
let mut executor = iterations
.iter()
.filter(|v| matches!(v.kind, IterationKind::Message | IterationKind::Compaction));
let served_fallback = iterations
.iter()
.any(|v| v.kind == IterationKind::FallbackMessage);
let (input, output) = if !served_fallback && executor.clone().next().is_some() {
executor.try_fold((0, 0), |(input, output), v| {
Ok::<_, Error>((add(input, v.input_tokens)?, add(output, v.output_tokens)?))
})?
} else {
(self.input_tokens, self.output_tokens)
};
let cache_read = self.cache_read_input_tokens.unwrap_or_default();
let cache_write = self.cache_creation_input_tokens.unwrap_or_default();
Ok(TokenCounts {
input: add(add(input, cache_read)?, cache_write)?,
uncached_input: input,
cache_read,
cache_write,
output,
reasoning: self
.output_tokens_details
.as_ref()
.and_then(|v| v.thinking_tokens),
})
}
}
fn add(left: u64, right: u64) -> Result<u64> {
left.checked_add(right)
.ok_or_else(|| Error::invalid("Provider token count overflow.", Value::Null))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum CacheTtl {
#[serde(rename = "5m")]
FiveMinutes,
#[serde(rename = "1h")]
OneHour,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CacheDirective {
Ephemeral {
#[serde(skip_serializing_if = "Option::is_none")]
ttl: Option<CacheTtl>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheSite {
System,
Text,
Image,
Document,
ToolResult,
ToolDefinition,
Reasoning,
RedactedReasoning,
}
impl CacheSite {
fn can_cache(self) -> bool {
!matches!(self, Self::Reasoning | Self::RedactedReasoning)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Warning {
CacheNotAllowed(CacheSite),
CacheLimit { attempted: usize },
StrictToolIgnored { name: String },
ForcedToolChoiceIgnored { name: Option<String> },
UnsupportedTool { id: String },
}
/// Owned by one request builder and shared across its message and tool passes.
/// It cannot leak capacity or warnings into another concurrent request.
#[derive(Default)]
pub struct CacheBudget {
attempted: usize,
warnings: Vec<Warning>,
}
impl CacheBudget {
pub fn reserve(
&mut self,
directive: Option<CacheDirective>,
site: CacheSite,
) -> Option<CacheDirective> {
let directive = directive?;
if !site.can_cache() {
self.warnings.push(Warning::CacheNotAllowed(site));
return None;
}
self.attempted = self.attempted.saturating_add(1);
if self.attempted > 4 {
self.warnings.push(Warning::CacheLimit {
attempted: self.attempted,
});
None
} else {
Some(directive)
}
}
pub fn warnings(&self) -> &[Warning] {
&self.warnings
}
pub fn into_warnings(self) -> Vec<Warning> {
self.warnings
}
}