use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
System,
User,
Assistant,
}
impl std::fmt::Display for Role {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Role::System => write!(f, "system"),
Role::User => write!(f, "user"),
Role::Assistant => write!(f, "assistant"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CacheControl {
#[default]
Ephemeral,
Extended,
}
impl CacheControl {
pub fn ephemeral() -> Self {
CacheControl::Ephemeral
}
pub fn extended() -> Self {
CacheControl::Extended
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacheBreakpoint {
pub cache_control: CacheControl,
}
impl CacheBreakpoint {
pub fn ephemeral() -> Self {
Self {
cache_control: CacheControl::Ephemeral,
}
}
pub fn extended() -> Self {
Self {
cache_control: CacheControl::Extended,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ThinkingConfig {
#[serde(rename = "type")]
pub thinking_type: ThinkingType,
#[serde(skip_serializing_if = "Option::is_none")]
pub budget_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<ThinkingEffort>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub exclude_from_response: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ThinkingType {
Enabled,
Disabled,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ThinkingEffort {
Low,
#[default]
Medium,
High,
Max,
}
impl ThinkingConfig {
pub fn enabled(budget_tokens: u32) -> Self {
Self {
thinking_type: ThinkingType::Enabled,
budget_tokens: Some(budget_tokens.max(1024)),
effort: None,
exclude_from_response: false,
}
}
pub fn disabled() -> Self {
Self {
thinking_type: ThinkingType::Disabled,
budget_tokens: None,
effort: None,
exclude_from_response: false,
}
}
pub fn with_effort(effort: ThinkingEffort) -> Self {
Self {
thinking_type: ThinkingType::Enabled,
budget_tokens: None,
effort: Some(effort),
exclude_from_response: false,
}
}
pub fn with_effort_and_budget(effort: ThinkingEffort, budget_tokens: u32) -> Self {
Self {
thinking_type: ThinkingType::Enabled,
budget_tokens: Some(budget_tokens),
effort: Some(effort),
exclude_from_response: false,
}
}
pub fn exclude_from_response(mut self, exclude: bool) -> Self {
self.exclude_from_response = exclude;
self
}
pub fn is_enabled(&self) -> bool {
matches!(self.thinking_type, ThinkingType::Enabled)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StructuredOutput {
#[serde(rename = "type")]
pub format_type: StructuredOutputType,
#[serde(skip_serializing_if = "Option::is_none")]
pub json_schema: Option<JsonSchemaDefinition>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StructuredOutputType {
Text,
JsonObject,
JsonSchema,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct JsonSchemaDefinition {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub schema: Value,
#[serde(default = "default_strict")]
pub strict: bool,
}
fn default_strict() -> bool {
true
}
impl StructuredOutput {
pub fn json_schema(name: impl Into<String>, schema: Value) -> Self {
Self {
format_type: StructuredOutputType::JsonSchema,
json_schema: Some(JsonSchemaDefinition {
name: name.into(),
description: None,
schema,
strict: true,
}),
}
}
pub fn json_schema_with_description(
name: impl Into<String>,
description: impl Into<String>,
schema: Value,
) -> Self {
Self {
format_type: StructuredOutputType::JsonSchema,
json_schema: Some(JsonSchemaDefinition {
name: name.into(),
description: Some(description.into()),
schema,
strict: true,
}),
}
}
pub fn json_object() -> Self {
Self {
format_type: StructuredOutputType::JsonObject,
json_schema: None,
}
}
pub fn text() -> Self {
Self {
format_type: StructuredOutputType::Text,
json_schema: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PredictionConfig {
#[serde(rename = "type")]
pub prediction_type: PredictionType,
pub content: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PredictionType {
Content,
}
impl PredictionConfig {
pub fn content(predicted_content: impl Into<String>) -> Self {
Self {
prediction_type: PredictionType::Content,
content: predicted_content.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DocumentSource {
Base64 {
media_type: String,
data: String,
},
Url {
url: String,
},
File {
file_id: String,
},
}
impl DocumentSource {
pub fn base64(media_type: impl Into<String>, data: impl Into<String>) -> Self {
DocumentSource::Base64 {
media_type: media_type.into(),
data: data.into(),
}
}
pub fn pdf_base64(data: impl Into<String>) -> Self {
DocumentSource::Base64 {
media_type: "application/pdf".to_string(),
data: data.into(),
}
}
pub fn url(url: impl Into<String>) -> Self {
DocumentSource::Url { url: url.into() }
}
pub fn file(file_id: impl Into<String>) -> Self {
DocumentSource::File {
file_id: file_id.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BetaFeature {
Output128k,
ExtendedCacheTtl,
InterleavedThinking,
FilesApi,
PdfSupport,
Custom(String),
}
impl BetaFeature {
pub fn anthropic_header(&self) -> &str {
match self {
BetaFeature::Output128k => "output-128k-2025-02-19",
BetaFeature::ExtendedCacheTtl => "extended-cache-ttl-2025-04-11",
BetaFeature::InterleavedThinking => "interleaved-thinking-2025-05-14",
BetaFeature::FilesApi => "files-api-2025-04-14",
BetaFeature::PdfSupport => "pdfs-2024-09-25",
BetaFeature::Custom(s) => s,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text { text: String },
Image {
media_type: String,
data: String,
},
ImageUrl {
url: String,
},
ToolUse {
id: String,
name: String,
input: Value,
},
ToolResult {
tool_use_id: String,
content: String,
#[serde(default)]
is_error: bool,
},
Thinking {
thinking: String,
},
Document {
source: DocumentSource,
#[serde(skip_serializing_if = "Option::is_none")]
cache_control: Option<CacheBreakpoint>,
},
TextWithCache {
text: String,
cache_control: CacheBreakpoint,
},
}
impl ContentBlock {
pub fn text(text: impl Into<String>) -> Self {
ContentBlock::Text { text: text.into() }
}
pub fn image(media_type: impl Into<String>, data: impl Into<String>) -> Self {
ContentBlock::Image {
media_type: media_type.into(),
data: data.into(),
}
}
pub fn image_url(url: impl Into<String>) -> Self {
ContentBlock::ImageUrl { url: url.into() }
}
pub fn tool_use(id: impl Into<String>, name: impl Into<String>, input: Value) -> Self {
ContentBlock::ToolUse {
id: id.into(),
name: name.into(),
input,
}
}
pub fn tool_result(
tool_use_id: impl Into<String>,
content: impl Into<String>,
is_error: bool,
) -> Self {
ContentBlock::ToolResult {
tool_use_id: tool_use_id.into(),
content: content.into(),
is_error,
}
}
pub fn thinking(thinking: impl Into<String>) -> Self {
ContentBlock::Thinking {
thinking: thinking.into(),
}
}
pub fn document(source: DocumentSource) -> Self {
ContentBlock::Document {
source,
cache_control: None,
}
}
pub fn document_cached(source: DocumentSource, cache_control: CacheBreakpoint) -> Self {
ContentBlock::Document {
source,
cache_control: Some(cache_control),
}
}
pub fn pdf(data: impl Into<String>) -> Self {
ContentBlock::Document {
source: DocumentSource::pdf_base64(data),
cache_control: None,
}
}
pub fn text_cached(text: impl Into<String>, cache_control: CacheBreakpoint) -> Self {
ContentBlock::TextWithCache {
text: text.into(),
cache_control,
}
}
pub fn text_cached_ephemeral(text: impl Into<String>) -> Self {
ContentBlock::TextWithCache {
text: text.into(),
cache_control: CacheBreakpoint::ephemeral(),
}
}
pub fn is_text(&self) -> bool {
matches!(
self,
ContentBlock::Text { .. } | ContentBlock::TextWithCache { .. }
)
}
pub fn is_document(&self) -> bool {
matches!(self, ContentBlock::Document { .. })
}
pub fn is_tool_use(&self) -> bool {
matches!(self, ContentBlock::ToolUse { .. })
}
pub fn is_tool_result(&self) -> bool {
matches!(self, ContentBlock::ToolResult { .. })
}
pub fn as_text(&self) -> Option<&str> {
match self {
ContentBlock::Text { text } => Some(text),
ContentBlock::TextWithCache { text, .. } => Some(text),
_ => None,
}
}
pub fn as_tool_use(&self) -> Option<(&str, &str, &Value)> {
match self {
ContentBlock::ToolUse { id, name, input } => Some((id, name, input)),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Message {
pub role: Role,
pub content: Vec<ContentBlock>,
}
impl Message {
pub fn new(role: Role, content: Vec<ContentBlock>) -> Self {
Self { role, content }
}
pub fn system(text: impl Into<String>) -> Self {
Self {
role: Role::System,
content: vec![ContentBlock::text(text)],
}
}
pub fn user(text: impl Into<String>) -> Self {
Self {
role: Role::User,
content: vec![ContentBlock::text(text)],
}
}
pub fn assistant(text: impl Into<String>) -> Self {
Self {
role: Role::Assistant,
content: vec![ContentBlock::text(text)],
}
}
pub fn user_with_content(content: Vec<ContentBlock>) -> Self {
Self {
role: Role::User,
content,
}
}
pub fn assistant_with_content(content: Vec<ContentBlock>) -> Self {
Self {
role: Role::Assistant,
content,
}
}
pub fn tool_results(results: Vec<ContentBlock>) -> Self {
Self {
role: Role::User,
content: results,
}
}
pub fn text_content(&self) -> String {
self.content
.iter()
.filter_map(|block| block.as_text())
.collect::<Vec<_>>()
.join("")
}
pub fn tool_uses(&self) -> Vec<&ContentBlock> {
self.content
.iter()
.filter(|block| block.is_tool_use())
.collect()
}
pub fn has_tool_use(&self) -> bool {
self.content.iter().any(|block| block.is_tool_use())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub input_schema: Value,
}
impl ToolDefinition {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
input_schema: Value,
) -> Self {
Self {
name: name.into(),
description: description.into(),
input_schema,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionRequest {
pub model: String,
pub messages: Vec<Message>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolDefinition>>,
#[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 stop_sequences: Option<Vec<String>>,
#[serde(default)]
pub stream: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking: Option<ThinkingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<StructuredOutput>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prediction: Option<PredictionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_cache_control: Option<CacheBreakpoint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub beta_features: Option<Vec<BetaFeature>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<Value>,
}
impl CompletionRequest {
pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
Self {
model: model.into(),
messages,
system: None,
tools: None,
max_tokens: None,
temperature: None,
top_p: None,
stop_sequences: None,
stream: false,
thinking: None,
response_format: None,
prediction: None,
system_cache_control: None,
beta_features: None,
extra: None,
}
}
pub fn with_system(mut self, system: impl Into<String>) -> Self {
self.system = Some(system.into());
self
}
pub fn with_tools(mut self, tools: Vec<ToolDefinition>) -> Self {
self.tools = Some(tools);
self
}
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn with_top_p(mut self, top_p: f32) -> Self {
self.top_p = Some(top_p);
self
}
pub fn with_stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
self.stop_sequences = Some(stop_sequences);
self
}
pub fn with_streaming(mut self) -> Self {
self.stream = true;
self
}
pub fn with_thinking(mut self, budget_tokens: u32) -> Self {
self.thinking = Some(ThinkingConfig::enabled(budget_tokens));
self
}
pub fn with_thinking_config(mut self, config: ThinkingConfig) -> Self {
self.thinking = Some(config);
self
}
pub fn without_thinking(mut self) -> Self {
self.thinking = Some(ThinkingConfig::disabled());
self
}
pub fn with_thinking_effort(mut self, effort: ThinkingEffort) -> Self {
self.thinking = Some(ThinkingConfig::with_effort(effort));
self
}
pub fn with_json_schema(mut self, name: impl Into<String>, schema: Value) -> Self {
self.response_format = Some(StructuredOutput::json_schema(name, schema));
self
}
pub fn with_response_format(mut self, format: StructuredOutput) -> Self {
self.response_format = Some(format);
self
}
pub fn with_json_output(mut self) -> Self {
self.response_format = Some(StructuredOutput::json_object());
self
}
pub fn with_prediction(mut self, predicted_content: impl Into<String>) -> Self {
self.prediction = Some(PredictionConfig::content(predicted_content));
self
}
pub fn with_system_caching(mut self) -> Self {
self.system_cache_control = Some(CacheBreakpoint::ephemeral());
self
}
pub fn with_system_caching_extended(mut self) -> Self {
self.system_cache_control = Some(CacheBreakpoint::extended());
let mut features = self.beta_features.unwrap_or_default();
if !features.contains(&BetaFeature::ExtendedCacheTtl) {
features.push(BetaFeature::ExtendedCacheTtl);
}
self.beta_features = Some(features);
self
}
pub fn with_beta_feature(mut self, feature: BetaFeature) -> Self {
let mut features = self.beta_features.unwrap_or_default();
if !features.contains(&feature) {
features.push(feature);
}
self.beta_features = Some(features);
self
}
pub fn with_extended_output(self) -> Self {
self.with_beta_feature(BetaFeature::Output128k)
}
pub fn with_interleaved_thinking(self) -> Self {
self.with_beta_feature(BetaFeature::InterleavedThinking)
}
pub fn with_extra(mut self, extra: Value) -> Self {
self.extra = Some(extra);
self
}
pub fn has_caching(&self) -> bool {
self.system_cache_control.is_some()
|| self.messages.iter().any(|m| {
m.content.iter().any(|c| {
matches!(
c,
ContentBlock::TextWithCache { .. }
| ContentBlock::Document {
cache_control: Some(_),
..
}
)
})
})
}
pub fn has_thinking(&self) -> bool {
self.thinking.as_ref().is_some_and(|t| t.is_enabled())
}
pub fn has_structured_output(&self) -> bool {
self.response_format.is_some()
}
pub fn anthropic_beta_headers(&self) -> Vec<&str> {
let mut headers = Vec::new();
if let Some(ref features) = self.beta_features {
for feature in features {
headers.push(feature.anthropic_header());
}
}
if self.thinking.is_some() && !headers.iter().any(|h| h.contains("thinking")) {
}
if let Some(CacheBreakpoint {
cache_control: CacheControl::Extended,
}) = &self.system_cache_control
{
if !headers.iter().any(|h| h.contains("cache-ttl")) {
headers.push(BetaFeature::ExtendedCacheTtl.anthropic_header());
}
}
headers
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StopReason {
EndTurn,
MaxTokens,
ToolUse,
StopSequence,
ContentFilter,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Usage {
pub input_tokens: u32,
pub output_tokens: u32,
#[serde(default)]
pub cache_creation_input_tokens: u32,
#[serde(default)]
pub cache_read_input_tokens: u32,
}
impl Usage {
pub fn total_tokens(&self) -> u32 {
self.input_tokens + self.output_tokens
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenCountRequest {
pub model: String,
pub messages: Vec<Message>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolDefinition>>,
}
impl TokenCountRequest {
pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
Self {
model: model.into(),
messages,
system: None,
tools: None,
}
}
pub fn with_system(mut self, system: impl Into<String>) -> Self {
self.system = Some(system.into());
self
}
pub fn with_tools(mut self, tools: Vec<ToolDefinition>) -> Self {
self.tools = Some(tools);
self
}
pub fn from_completion_request(request: &CompletionRequest) -> Self {
Self {
model: request.model.clone(),
messages: request.messages.clone(),
system: request.system.clone(),
tools: request.tools.clone(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenCountResult {
pub input_tokens: u32,
}
impl TokenCountResult {
pub fn new(input_tokens: u32) -> Self {
Self { input_tokens }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchRequest {
pub custom_id: String,
pub request: CompletionRequest,
}
impl BatchRequest {
pub fn new(custom_id: impl Into<String>, request: CompletionRequest) -> Self {
Self {
custom_id: custom_id.into(),
request,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BatchStatus {
Validating,
InProgress,
Finalizing,
Completed,
Failed,
Expired,
Cancelled,
}
impl BatchStatus {
pub fn is_processing(&self) -> bool {
matches!(
self,
BatchStatus::Validating | BatchStatus::InProgress | BatchStatus::Finalizing
)
}
pub fn is_done(&self) -> bool {
matches!(
self,
BatchStatus::Completed
| BatchStatus::Failed
| BatchStatus::Expired
| BatchStatus::Cancelled
)
}
pub fn is_success(&self) -> bool {
matches!(self, BatchStatus::Completed)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchJob {
pub id: String,
pub status: BatchStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub started_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ended_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
#[serde(default)]
pub request_counts: BatchRequestCounts,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchRequestCounts {
pub total: u32,
pub succeeded: u32,
pub failed: u32,
pub pending: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchResult {
pub custom_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub response: Option<CompletionResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<BatchError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchError {
#[serde(rename = "type")]
pub error_type: String,
pub message: String,
}
impl BatchResult {
pub fn is_success(&self) -> bool {
self.response.is_some()
}
pub fn is_error(&self) -> bool {
self.error.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionResponse {
pub id: String,
pub model: String,
pub content: Vec<ContentBlock>,
pub stop_reason: StopReason,
pub usage: Usage,
}
impl CompletionResponse {
pub fn text_content(&self) -> String {
self.content
.iter()
.filter_map(|block| block.as_text())
.collect::<Vec<_>>()
.join("")
}
pub fn tool_uses(&self) -> Vec<&ContentBlock> {
self.content
.iter()
.filter(|block| block.is_tool_use())
.collect()
}
pub fn has_tool_use(&self) -> bool {
self.stop_reason == StopReason::ToolUse || self.content.iter().any(|b| b.is_tool_use())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentDelta {
Text { text: String },
ToolUse {
id: Option<String>,
name: Option<String>,
input_json_delta: Option<String>,
},
Thinking { thinking: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamChunk {
pub event_type: StreamEventType,
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delta: Option<ContentDelta>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<StopReason>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StreamEventType {
MessageStart,
ContentBlockStart,
ContentBlockDelta,
ContentBlockStop,
MessageDelta,
MessageStop,
Ping,
Error,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_creation() {
let msg = Message::user("Hello, world!");
assert_eq!(msg.role, Role::User);
assert_eq!(msg.text_content(), "Hello, world!");
}
#[test]
fn test_content_block_helpers() {
let text = ContentBlock::text("test");
assert!(text.is_text());
assert_eq!(text.as_text(), Some("test"));
let tool = ContentBlock::tool_use("id1", "bash", serde_json::json!({"command": "ls"}));
assert!(tool.is_tool_use());
}
#[test]
fn test_completion_request_builder() {
let req = CompletionRequest::new("claude-sonnet-4-20250514", vec![Message::user("Hi")])
.with_max_tokens(1024)
.with_temperature(0.7)
.with_streaming();
assert_eq!(req.model, "claude-sonnet-4-20250514");
assert_eq!(req.max_tokens, Some(1024));
assert_eq!(req.temperature, Some(0.7));
assert!(req.stream);
}
#[test]
fn test_batch_status() {
assert!(BatchStatus::Validating.is_processing());
assert!(BatchStatus::InProgress.is_processing());
assert!(BatchStatus::Finalizing.is_processing());
assert!(!BatchStatus::Completed.is_processing());
assert!(!BatchStatus::Failed.is_processing());
assert!(BatchStatus::Completed.is_done());
assert!(BatchStatus::Failed.is_done());
assert!(BatchStatus::Expired.is_done());
assert!(BatchStatus::Cancelled.is_done());
assert!(!BatchStatus::InProgress.is_done());
assert!(BatchStatus::Completed.is_success());
assert!(!BatchStatus::Failed.is_success());
}
#[test]
fn test_batch_request_creation() {
let request =
CompletionRequest::new("claude-sonnet-4-20250514", vec![Message::user("Hello")]);
let batch_req = BatchRequest::new("req-001", request);
assert_eq!(batch_req.custom_id, "req-001");
assert_eq!(batch_req.request.model, "claude-sonnet-4-20250514");
}
}