car-inference 0.63.0

Local model inference for CAR — Candle backend with Qwen3 models
Documentation
//! Direct TypeSafe System One decisions. The row owns its endpoint and stored
//! key name; Parslee authority, identity and balance are never consulted.

use crate::schema::{ModelSchema, ModelSource, ProprietaryAuth, ProprietaryWire};
use crate::InferenceError;

pub(crate) fn is_direct(row: &ModelSchema) -> bool {
    row.provider.eq_ignore_ascii_case("typesafe")
        && matches!(&row.source, ModelSource::Proprietary { provider, .. }
            if provider.eq_ignore_ascii_case("typesafe"))
}

pub(crate) fn key_name(row: &ModelSchema) -> Result<&str, InferenceError> {
    match &row.source {
        ModelSource::Proprietary {
            auth: ProprietaryAuth::ApiKeyEnv { env_var },
            protocol,
            ..
        } if is_direct(row)
            && protocol.wire == ProprietaryWire::SystemOne
            && !env_var.trim().is_empty() =>
        {
            Ok(env_var)
        }
        _ => Err(InferenceError::InferenceFailed(format!(
            "{}: TypeSafe direct requires System One wire and an api_key_env naming a stored key",
            row.id
        ))),
    }
}

pub(crate) fn stored_key(row: &ModelSchema) -> Result<String, InferenceError> {
    let name = key_name(row)?;
    let value = car_secrets::resolve_stored_provider_key(name)
        .map(|value| value.trim().to_owned())
        .filter(|value| !value.is_empty())
        .ok_or_else(|| InferenceError::ProviderKeyMissing {
            provider: "typesafe".into(),
            model: row.id.clone(),
            env_vars: vec![name.into()],
            message: format!("TypeSafe direct needs stored key {name}; run `car keys set {name}`"),
        })?;
    Ok(value)
}

pub(crate) fn audit_reason(row: &ModelSchema, reason: &str) -> String {
    if !is_direct(row) {
        return reason.into();
    }
    serde_json::json!({"provider":"typesafe","provider_row":row.id,
        "key_id":key_name(row).ok(),"route":reason})
    .to_string()
}