use std::sync::Arc;
use reqwest::Client;
use serde_json::Value;
use super::errors::ConduitError;
use super::execution::LLMCore;
use super::provider_runtime::ProviderRuntime;
use super::request_adapters::normalize_responses_kwargs;
use super::tool_calls::{
normalize_tool_calls, tool_call_arguments_string, tool_call_id, tool_call_name,
};
use crate::clients::parsing::TransportKind;
use crate::providers;
#[derive(Debug, Clone)]
pub struct TransportCallRequest {
pub client: Arc<Client>,
pub provider_name: String,
pub model_id: String,
pub api_base: Option<String>,
pub messages_payload: Vec<Value>,
pub tools_payload: Option<Vec<Value>>,
pub max_tokens: Option<u32>,
pub stream: bool,
pub reasoning_effort: Option<Value>,
pub kwargs: serde_json::Map<String, Value>,
pub is_anthropic_oauth: bool,
}
impl LLMCore {
pub fn decide_kwargs_for_provider(
provider: &str,
max_tokens: Option<u32>,
kwargs: &serde_json::Map<String, Value>,
) -> serde_json::Map<String, Value> {
let max_tokens_arg = ProviderRuntime::completion_max_tokens_arg(provider);
let mut clean = kwargs.clone();
if clean.contains_key(&max_tokens_arg) {
return clean;
}
if let Some(mt) = max_tokens {
clean.insert(max_tokens_arg, Value::Number(mt.into()));
}
clean
}
pub fn decide_responses_kwargs(
max_tokens: Option<u32>,
kwargs: &serde_json::Map<String, Value>,
drop_extra_headers: bool,
) -> serde_json::Map<String, Value> {
let mut clean = kwargs.clone();
if drop_extra_headers {
clean.remove("extra_headers");
}
normalize_responses_kwargs(&mut clean);
if clean.contains_key("max_output_tokens") || max_tokens.is_none() {
return clean;
}
if let Some(mt) = max_tokens {
clean.insert("max_output_tokens".to_owned(), Value::Number(mt.into()));
}
clean
}
pub fn should_default_completion_stream_usage(provider_name: &str) -> bool {
ProviderRuntime::should_include_completion_stream_usage(provider_name)
}
pub fn with_default_completion_stream_options(
provider_name: &str,
stream: bool,
kwargs: &serde_json::Map<String, Value>,
) -> serde_json::Map<String, Value> {
if !stream {
return kwargs.clone();
}
if !Self::should_default_completion_stream_usage(provider_name) {
return kwargs.clone();
}
if kwargs.contains_key("stream_options") {
return kwargs.clone();
}
let mut result = kwargs.clone();
result.insert(
"stream_options".to_owned(),
serde_json::json!({"include_usage": true}),
);
result
}
pub fn with_responses_reasoning(
kwargs: &serde_json::Map<String, Value>,
reasoning_effort: Option<&Value>,
) -> serde_json::Map<String, Value> {
let effort = match reasoning_effort {
Some(e) if !e.is_null() => e,
_ => return kwargs.clone(),
};
if kwargs.contains_key("reasoning") {
return kwargs.clone();
}
let mut result = kwargs.clone();
result.insert(
"reasoning".to_owned(),
serde_json::json!({"effort": effort}),
);
result
}
pub fn convert_tools_for_responses(tools_payload: Option<&[Value]>) -> Option<Vec<Value>> {
let tools = tools_payload?;
if tools.is_empty() {
return None;
}
let mut converted = Vec::with_capacity(tools.len());
for tool in tools {
if let Some(function) = tool.get("function").and_then(|f| f.as_object()) {
let mut entry = serde_json::Map::new();
entry.insert(
"type".to_owned(),
tool.get("type")
.cloned()
.unwrap_or(Value::String("function".to_owned())),
);
if let Some(name) = function.get("name") {
entry.insert("name".to_owned(), name.clone());
}
entry.insert(
"description".to_owned(),
function
.get("description")
.cloned()
.unwrap_or(Value::String(String::new())),
);
entry.insert(
"parameters".to_owned(),
function
.get("parameters")
.cloned()
.unwrap_or(serde_json::json!({})),
);
if let Some(strict) = function.get("strict") {
entry.insert("strict".to_owned(), strict.clone());
}
converted.push(Value::Object(entry));
} else {
converted.push(tool.clone());
}
}
Some(converted)
}
pub fn selected_transport(
&self,
provider_name: &str,
model_id: &str,
tools_payload: Option<&[Value]>,
supports_responses: bool,
preferred_transport: Option<TransportKind>,
) -> Result<TransportKind, ConduitError> {
ProviderRuntime::new(provider_name, model_id, None, None, self.api_format())
.selected_transport(tools_payload, supports_responses, preferred_transport)
}
pub fn split_messages_for_responses(messages: &[Value]) -> (Option<String>, Vec<Value>) {
let mut instruction_parts: Vec<String> = Vec::new();
let mut filtered: Vec<&Value> = Vec::new();
for message in messages {
let role = message.get("role").and_then(|r| r.as_str()).unwrap_or("");
if role == "system" || role == "developer" {
if let Some(content) = message.get("content").and_then(|c| c.as_str())
&& !content.is_empty()
{
instruction_parts.push(content.to_owned());
}
continue;
}
filtered.push(message);
}
let instructions = if instruction_parts.is_empty() {
None
} else {
let joined = instruction_parts
.into_iter()
.filter(|p| !p.trim().is_empty())
.collect::<Vec<_>>()
.join("\n\n");
if joined.is_empty() {
None
} else {
Some(joined)
}
};
let input_items = Self::convert_messages_to_responses_input(&filtered);
(instructions, input_items)
}
fn convert_messages_to_responses_input(messages: &[&Value]) -> Vec<Value> {
let mut items: Vec<Value> = Vec::new();
for message in messages {
let role = message.get("role").and_then(|r| r.as_str()).unwrap_or("");
let content = message.get("content");
let content_str = content.and_then(|c| c.as_str()).unwrap_or("");
if (role == "user" || role == "assistant") && !content_str.is_empty() {
items.push(serde_json::json!({
"role": role,
"content": content_str,
"type": "message",
}));
}
if role == "assistant"
&& let Some(tool_calls) = message.get("tool_calls").and_then(|tc| tc.as_array())
{
for (index, tool_call) in normalize_tool_calls(tool_calls).into_iter().enumerate() {
let name = tool_call_name(&tool_call).unwrap_or("");
if name.is_empty() {
continue;
}
let call_id = tool_call_id(&tool_call)
.map(|s| s.to_owned())
.unwrap_or_else(|| format!("call_{}", index + 1));
let arguments = tool_call_arguments_string(&tool_call);
items.push(serde_json::json!({
"type": "function_call",
"name": name,
"arguments": arguments,
"call_id": call_id,
}));
}
}
if role == "tool" {
let call_id = message
.get("tool_call_id")
.or_else(|| message.get("call_id"))
.and_then(|v| v.as_str());
if let Some(cid) = call_id {
let output = message
.get("content")
.and_then(|c| c.as_str())
.unwrap_or("");
items.push(serde_json::json!({
"type": "function_call_output",
"call_id": cid,
"output": output,
}));
}
}
}
items
}
pub fn build_request_url(api_base: &str, transport: TransportKind) -> String {
providers::adapter_for_transport(transport).build_request_url(api_base, transport)
}
pub fn build_completion_body(
request: &TransportCallRequest,
provider_name: &str,
) -> Result<Value, ConduitError> {
let mut adapter_request = request.clone();
adapter_request.provider_name = provider_name.to_owned();
providers::adapter_for_transport(TransportKind::Completion)
.build_request_body(&adapter_request, TransportKind::Completion)
}
pub fn build_messages_body(request: &TransportCallRequest) -> Result<Value, ConduitError> {
providers::adapter_for_transport(TransportKind::Messages)
.build_request_body(request, TransportKind::Messages)
}
pub fn build_responses_body(request: &TransportCallRequest) -> Result<Value, ConduitError> {
providers::adapter_for_transport(TransportKind::Responses)
.build_request_body(request, TransportKind::Responses)
}
}