use crate::error_display;
use crate::provider::{LLMError, LLMRequest, Message, MessageRole};
use serde_json::Value;
pub(crate) fn sampling_param_f64(value: f32) -> f64 {
if !value.is_finite() {
return f64::from(value);
}
format!("{value}").parse().unwrap_or_else(|_| f64::from(value))
}
pub(crate) fn float_to_json_number(value: f32) -> Result<serde_json::Number, LLMError> {
serde_json::Number::from_f64(sampling_param_f64(value)).ok_or_else(|| LLMError::InvalidRequest {
message: "invalid numeric parameter value (NaN or infinity)".to_string(),
metadata: None,
})
}
pub(crate) fn collect_history_system_directives(request: &LLMRequest) -> Vec<String> {
request
.messages
.iter()
.filter(|message| message.role == MessageRole::System)
.map(|message| message.content.as_text().trim().to_string())
.filter(|text| !text.is_empty())
.collect()
}
pub(crate) fn merge_system_prompt_with_history_directives(
base_prompt: Option<&str>,
directives: &[String],
section_header: &str,
) -> Option<String> {
let mut system_prompt = base_prompt
.map(str::trim)
.filter(|prompt| !prompt.is_empty())
.map(str::to_owned)
.unwrap_or_default();
if directives.is_empty() {
return (!system_prompt.is_empty()).then_some(system_prompt);
}
if !system_prompt.is_empty() {
system_prompt.push('\n');
}
system_prompt.push_str(section_header);
system_prompt.push('\n');
for directive in directives {
system_prompt.push_str("- ");
system_prompt.push_str(directive);
system_prompt.push('\n');
}
Some(system_prompt)
}
pub(crate) fn resolve_model(model: Option<String>, default_model: &str) -> String {
model
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| default_model.to_owned())
}
pub(crate) fn ensure_model(request: &mut LLMRequest, default_model: &str) -> String {
if request.model.trim().is_empty() {
request.model = default_model.to_owned();
}
request.model.clone()
}
pub(crate) fn validate_supported_models(
request: &LLMRequest,
provider_name: &str,
provider_key: &str,
supported_models: &[&str],
) -> Result<(), LLMError> {
let models: Vec<String> = supported_models.iter().map(|m| m.to_string()).collect();
validate_request_common(request, provider_name, provider_key, Some(&models))
}
#[inline]
pub(crate) fn make_default_request(prompt: &str, model: &str) -> LLMRequest {
LLMRequest {
messages: std::sync::Arc::new(vec![Message::user(prompt.to_owned())]),
model: model.to_owned(),
..Default::default()
}
}
#[inline]
pub(crate) fn parse_client_prompt_common<F>(prompt: &str, model: &str, parse_json: F) -> LLMRequest
where
F: FnOnce(&Value) -> Option<LLMRequest>,
{
let trimmed = prompt.trim_start();
if trimmed.starts_with('{')
&& let Ok(value) = serde_json::from_str::<Value>(trimmed)
&& let Some(request) = parse_json(&value)
{
return request;
}
make_default_request(prompt, model)
}
pub(crate) fn validate_request_common(
request: &LLMRequest,
provider_name: &str,
validation_provider: &str,
supported_models: Option<&[String]>,
) -> Result<(), LLMError> {
if request.messages.is_empty() {
let formatted = error_display::format_llm_error(provider_name, "Messages cannot be empty");
return Err(LLMError::InvalidRequest { message: formatted, metadata: None });
}
if !request.model.trim().is_empty()
&& validation_provider.eq_ignore_ascii_case("openai")
&& let Some((replacement, reason)) =
vtcode_config::constants::models::openai::deprecated_model_replacement(&request.model)
{
let msg = format!(
"Unsupported model: {}. {}. Update your config to use `{}` or run /model to pick a current model.",
request.model, reason, replacement
);
let formatted = error_display::format_llm_error(provider_name, &msg);
return Err(LLMError::InvalidRequest { message: formatted, metadata: None });
}
if let Some(models) = supported_models
&& !request.model.trim().is_empty()
&& !models.contains(&request.model)
{
let msg = build_unsupported_model_error(validation_provider, &request.model);
let formatted = error_display::format_llm_error(provider_name, &msg);
return Err(LLMError::InvalidRequest { message: formatted, metadata: None });
}
for message in request.messages.iter() {
if let Err(err) = message.validate_for_provider(validation_provider) {
let formatted = error_display::format_llm_error(provider_name, &err);
return Err(LLMError::InvalidRequest { message: formatted, metadata: None });
}
}
Ok(())
}
fn build_unsupported_model_error(validation_provider: &str, model: &str) -> String {
if validation_provider.eq_ignore_ascii_case("openai")
&& let Some((replacement, reason)) =
vtcode_config::constants::models::openai::deprecated_model_replacement(model)
{
return format!(
"Unsupported model: {model}. {reason}. Update your config to use `{replacement}` or run /model to pick a current model."
);
}
format!("Unsupported model: {model}")
}