pub mod adapter;
pub(crate) mod anthropic_compatible;
#[cfg(feature = "audio")]
pub(crate) mod audio_generation;
pub(crate) mod auth;
pub mod chunk_lifecycle;
pub(crate) mod completion_send;
#[cfg(not(target_family = "wasm"))]
pub(crate) mod device_auth;
pub(crate) mod envelope;
#[cfg(feature = "image")]
pub(crate) mod image_generation;
pub(crate) mod model_listing;
pub(crate) mod openai_chat_completions_compatible;
pub(crate) mod schema;
#[cfg(any(test, debug_assertions))]
pub(crate) mod sequence_law;
pub(crate) mod sse_transport;
pub mod tool_call_bridge;
pub(crate) mod transcription;
pub mod wire;
pub fn resolve_empty_tool_result_names(history: &mut [crate::message::Message]) {
use std::collections::HashMap;
let mut names_by_id: HashMap<String, String> = HashMap::new();
for message in history.iter() {
let crate::message::Message::Assistant { content, .. } = message else {
continue;
};
for item in content.iter() {
let crate::message::AssistantContent::ToolCall(call) = item else {
continue;
};
names_by_id.insert(call.id.as_str().to_owned(), call.function.name.clone());
if let Some(provider) = &call.provider {
names_by_id.insert(provider.call_id.clone(), call.function.name.clone());
if let Some(item_id) = &provider.item_id {
names_by_id.insert(item_id.clone(), call.function.name.clone());
}
}
}
}
if names_by_id.is_empty() {
return;
}
for message in history.iter_mut() {
let crate::message::Message::User { content } = message else {
continue;
};
for item in content.iter_mut() {
let crate::message::UserContent::ToolResult(result) = item else {
continue;
};
if !result.name.is_empty() {
continue;
}
let resolved = names_by_id.get(result.call.as_str()).or_else(|| {
result.provider.as_ref().and_then(|provider| {
names_by_id.get(&provider.call_id).or_else(|| {
provider
.item_id
.as_ref()
.and_then(|item_id| names_by_id.get(item_id))
})
})
});
if let Some(name) = resolved {
result.name = name.clone();
}
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum LogTarget {
Completions,
Streaming,
}
pub(crate) fn trace_json(target: LogTarget, label: &str, value: &impl serde::Serialize) {
macro_rules! emit {
($target:literal) => {
if tracing::enabled!(target: $target, tracing::Level::TRACE) {
if let Ok(json) = serde_json::to_string_pretty(value) {
tracing::trace!(target: $target, "{label}: {json}");
}
}
};
}
match target {
LogTarget::Streaming => emit!("rig::streaming"),
LogTarget::Completions => emit!("rig::completions"),
}
}
pub(crate) fn completion_usage(
input_tokens: u64,
output_tokens: u64,
total_tokens: u64,
cached_input_tokens: u64,
) -> crate::completion::Usage {
crate::completion::Usage {
input_tokens,
output_tokens,
total_tokens,
cached_input_tokens,
cache_creation_input_tokens: 0,
tool_use_prompt_tokens: 0,
reasoning_tokens: 0,
}
}
#[cfg(test)]
mod tests {
use crate::message::{
AssistantContent, Message, ToolCall, ToolFunction, ToolResultContent, UserContent,
};
fn call(wire_id: &str, name: &str) -> Message {
Message::Assistant {
id: None,
content: vec![AssistantContent::ToolCall(ToolCall::from_wire(
wire_id,
ToolFunction {
name: name.to_owned(),
arguments: serde_json::json!({}),
},
))],
}
}
fn nameless_result(wire_id: &str) -> Message {
Message::User {
content: vec![UserContent::tool_result_from_wire(
wire_id,
"",
vec![ToolResultContent::text("out")],
)],
}
}
fn result_names(history: &[Message]) -> Vec<String> {
history
.iter()
.filter_map(|message| match message {
Message::User { content } => content.iter().next().and_then(|item| match item {
UserContent::ToolResult(result) => Some(result.name.clone()),
_ => None,
}),
_ => None,
})
.collect()
}
#[test]
fn empty_names_resolve_from_the_paired_call() {
let mut history = vec![
call("toolu_1", "get_weather"),
nameless_result("toolu_1"),
call("toolu_2", "get_time"),
nameless_result("toolu_2"),
];
super::resolve_empty_tool_result_names(&mut history);
assert_eq!(result_names(&history), ["get_weather", "get_time"]);
}
#[test]
fn an_unmatched_result_keeps_its_empty_name() {
let mut history = vec![call("toolu_1", "get_weather"), nameless_result("toolu_9")];
super::resolve_empty_tool_result_names(&mut history);
assert_eq!(result_names(&history), [""]);
}
#[test]
fn an_established_name_is_never_overwritten() {
let mut history = vec![
call("toolu_1", "add"),
Message::User {
content: vec![UserContent::tool_result_from_wire(
"toolu_1",
"sum",
vec![ToolResultContent::text("3")],
)],
},
];
super::resolve_empty_tool_result_names(&mut history);
assert_eq!(result_names(&history), ["sum"]);
}
#[test]
fn a_handle_only_result_resolves_from_an_id_less_call() {
let id_less = ToolCall::new(
crate::message::ToolCallId::mint(),
ToolFunction {
name: "lookup".to_owned(),
arguments: serde_json::json!({}),
},
);
let handle = id_less.id.as_str().to_owned();
let mut history = vec![
Message::Assistant {
id: None,
content: vec![AssistantContent::ToolCall(id_less)],
},
Message::User {
content: vec![UserContent::tool_result(
handle,
"",
vec![ToolResultContent::text("out")],
)],
},
];
super::resolve_empty_tool_result_names(&mut history);
assert_eq!(result_names(&history), ["lookup"]);
}
}