use crate::llm_config;
use crate::schema::json_to_vm_value;
use crate::value::{VmDictExt, VmValue};
use super::batch_projection::string_list_to_vm_value;
use super::capability_projection::{capabilities_to_vm_value, insert_batch_support_fields};
use super::catalog_projection::insert_tool_mode_parity_fields;
use super::provider_projection::{performance_to_vm_value, rate_limits_to_vm_value};
#[derive(Clone, Copy)]
pub(super) enum PricingView {
Effective,
Stored,
}
pub(crate) fn llm_catalog_value() -> VmValue {
let entries: Vec<VmValue> = llm_config::model_catalog_entries()
.into_iter()
.map(|(id, model)| model_def_to_vm_value(&id, &model, PricingView::Effective))
.collect();
VmValue::list(entries)
}
pub(super) fn resolved_model_to_vm_value(resolved: &llm_config::ResolvedModel) -> VmValue {
let mut dict = crate::value::DictMap::new();
dict.put_str("id", resolved.id.as_str());
dict.put_str("provider", resolved.provider.as_str());
dict.put_nullable_str("alias", resolved.alias.as_deref());
dict.put_str("tool_format", resolved.tool_format.as_str());
dict.put_str("tier", resolved.tier.as_str());
dict.put_str("family", resolved.family.as_str());
dict.put_str("lineage", resolved.lineage.as_str());
VmValue::dict(dict)
}
pub(super) fn model_info_to_vm_value(resolved: &llm_config::ResolvedModel) -> VmValue {
let mut dict = match resolved_model_to_vm_value(resolved) {
VmValue::Dict(dict) => dict.as_ref().clone(),
_ => unreachable!("resolved_model_to_vm_value returns a dict"),
};
let caps = crate::llm::capabilities::lookup(&resolved.provider, &resolved.id);
dict.insert(
crate::value::intern_key("capabilities"),
capabilities_to_vm_value(&resolved.provider, &resolved.id, &caps),
);
dict.put_nullable(
"catalog",
llm_config::model_catalog_entry(&resolved.id)
.map(|entry| model_def_to_vm_value(&resolved.id, &entry, PricingView::Effective)),
);
dict.put_nullable_str(
"qc_default_model",
llm_config::qc_default_model(&resolved.provider),
);
dict.put_bool(
"auth_available",
crate::llm::provider_auth_status(&resolved.provider).available,
);
VmValue::dict(dict)
}
pub(super) fn model_def_to_vm_value(
id: &str,
model: &llm_config::ModelDef,
pricing_view: PricingView,
) -> VmValue {
let mut dict = crate::value::DictMap::new();
let capabilities = crate::llm::capabilities::lookup(&model.provider, id);
let batch_api =
crate::llm_config::effective_batch_api_supported(&model.provider, &capabilities);
dict.put_str("id", id);
dict.put_str("name", model.name.as_str());
dict.put_nullable_str("blurb", model.blurb.as_deref());
dict.put_str("provider", model.provider.as_str());
dict.put_nullable(
"operations",
model.operations.as_ref().map(|operations| {
string_list_to_vm_value(operations.iter().map(|op| op.as_str().to_owned()).collect())
}),
);
dict.put_int("context_window", model.context_window as i64);
dict.put_nullable_str("logical_model", model.logical_model.as_deref());
dict.put_nullable_str("equivalence_group", model.equivalence_group.as_deref());
dict.put_nullable_str("served_variant", model.served_variant.as_deref());
dict.put_nullable_str("wire_model", model.wire_model.as_deref());
dict.put_nullable_str("api_dialect", model.api_dialect.as_deref());
dict.put_nullable(
"rate_limits",
model
.rate_limits
.as_ref()
.filter(|limits| !limits.is_empty())
.map(rate_limits_to_vm_value),
);
dict.put_nullable(
"performance",
model
.performance
.as_ref()
.filter(|performance| !performance.is_empty())
.map(performance_to_vm_value),
);
dict.put_nullable(
"architecture",
model
.architecture
.as_ref()
.filter(|architecture| !architecture.is_empty())
.map(architecture_to_vm_value),
);
dict.put_nullable(
"runtime_context_window",
model
.runtime_context_window
.map(|window| VmValue::Int(window as i64)),
);
dict.put_nullable("stream_timeout", model.stream_timeout.map(VmValue::Float));
dict.insert(
crate::value::intern_key("capabilities"),
string_list_to_vm_value(model.capabilities.clone()),
);
insert_tool_mode_parity_fields(&mut dict, &capabilities);
dict.insert(
crate::value::intern_key("reasoning_effort_levels"),
string_list_to_vm_value(capabilities.reasoning_effort_levels.clone()),
);
insert_batch_support_fields(&mut dict, batch_api, &capabilities);
dict.put_nullable(
"pricing",
model
.pricing
.as_ref()
.map(|pricing| pricing_to_vm_value(pricing, pricing_view)),
);
dict.put_bool("deprecated", model.deprecated);
dict.put_nullable_str("deprecation_note", model.deprecation_note.as_deref());
dict.put_nullable_str("sunset_date", model.sunset_date.as_deref());
dict.put_nullable_str("superseded_by", model.superseded_by.as_deref());
dict.insert(
crate::value::intern_key("quality_tags"),
string_list_to_vm_value(model.quality_tags.clone()),
);
dict.put_str("family", llm_config::model_family(&model.provider, id));
dict.put_str("lineage", llm_config::model_lineage(&model.provider, id));
dict.insert(
crate::value::intern_key("complementary_with"),
string_list_to_vm_value(model.complementary_with.clone()),
);
dict.insert(
crate::value::intern_key("avoid_as_reviewer_for"),
string_list_to_vm_value(model.avoid_as_reviewer_for.clone()),
);
dict.put_nullable(
"completion_review",
model
.completion_review
.as_ref()
.map(completion_review_to_vm_value),
);
dict.put_str("availability", model.availability.as_str());
dict.put_str("tier", llm_config::model_tier(id));
dict.put_nullable("open_weight", model.open_weight.map(VmValue::Bool));
dict.insert(
crate::value::intern_key("strengths"),
string_list_to_vm_value(model.strengths.clone()),
);
let benchmarks: crate::value::DictMap = model
.benchmarks
.iter()
.map(|(key, value)| (crate::value::intern_key(key), VmValue::Float(*value)))
.collect();
dict.put("benchmarks", VmValue::dict(benchmarks));
dict.insert(
crate::value::intern_key("serving_tiers"),
json_to_vm_value(
&serde_json::to_value(&model.serving_tiers).unwrap_or_else(|_| serde_json::json!([])),
),
);
VmValue::dict(dict)
}
fn architecture_to_vm_value(architecture: &llm_config::ModelArchitectureDef) -> VmValue {
json_to_vm_value(&serde_json::to_value(architecture).unwrap_or_else(|_| serde_json::json!({})))
}
fn pricing_to_vm_value(pricing: &llm_config::ModelPricing, view: PricingView) -> VmValue {
if matches!(view, PricingView::Stored) {
return json_to_vm_value(
&serde_json::to_value(pricing).expect("model pricing is serializable"),
);
}
let pricing = pricing.effective_today();
json_to_vm_value(&serde_json::json!({
"input_per_mtok": pricing.input_per_mtok,
"output_per_mtok": pricing.output_per_mtok,
"cache_read_per_mtok": pricing.cache_read_per_mtok,
"cache_write_per_mtok": pricing.cache_write_per_mtok,
}))
}
fn completion_review_to_vm_value(review: &llm_config::CompletionReviewDef) -> VmValue {
let mut dict = crate::value::DictMap::new();
dict.put_str("scrutiny", review.scrutiny.as_str());
dict.put_nullable(
"max_judge_calls",
review
.max_judge_calls
.map(|cap| VmValue::Int(i64::from(cap))),
);
dict.put_str("evidence", review.evidence.as_str());
VmValue::dict(dict)
}