link-assistant-router 1.11.2

Link.Assistant.Router — Claude MAX OAuth proxy and token gateway for Anthropic APIs
Documentation
//! Process-local Codex catalog projection for `router with`.

use std::path::{Path, PathBuf};

use serde_json::{Value, json};

use super::AnyError;
use crate::clients::RouterModel;

/// Write the live Router catalog as a complete process-local Codex catalog,
/// preventing foreign model ids without changing the user's configuration.
pub(super) fn write_codex_model_catalog(
    root: &Path,
    models: &[RouterModel],
    configured_effort: Option<&str>,
    selected_model: Option<&str>,
) -> Result<PathBuf, AnyError> {
    if models.is_empty() {
        return Err("the Router advertised no models for Codex".into());
    }
    let mut described = Vec::with_capacity(models.len());
    let mut omitted = Vec::new();
    for model in models {
        match validate_codex_reasoning_metadata(model) {
            Ok(()) => described.push(model),
            Err(error) if selected_model == Some(model.id.as_str()) => return Err(error),
            Err(error) => omitted.push((model.id.as_str(), error.to_string())),
        }
    }
    if !omitted.is_empty() {
        let ids = omitted
            .iter()
            .map(|(id, _)| *id)
            .collect::<Vec<_>>()
            .join(", ");
        eprintln!(
            "warning: omitted Codex model(s) with unavailable reasoning metadata: {ids}; \
             fully described models remain available"
        );
    }
    if described.is_empty() {
        let ids = omitted
            .iter()
            .map(|(id, _)| *id)
            .collect::<Vec<_>>()
            .join(", ");
        return Err(format!(
            "the live Codex catalog has no model with usable reasoning metadata; omitted: {ids}"
        )
        .into());
    }
    if let (Some(effort), Some(selected)) = (configured_effort, selected_model) {
        let model = described
            .iter()
            .copied()
            .find(|model| model.id == selected)
            .ok_or_else(|| format!("the Router advertised no Codex model named `{selected}`"))?;
        if !model_supports_reasoning_effort(model, effort) {
            return Err(format!(
                "Codex model `{selected}` does not support configured reasoning effort \
                 `{effort}`; choose a supported model or change `model_reasoning_effort`"
            )
            .into());
        }
    }
    let compatible = described
        .into_iter()
        .filter(|model| {
            configured_effort.is_none_or(|effort| model_supports_reasoning_effort(model, effort))
        })
        .collect::<Vec<_>>();
    if compatible.is_empty() {
        let effort = configured_effort.expect("an unfiltered non-empty catalog stays non-empty");
        return Err(format!(
            "the live Codex catalog has no model supporting configured reasoning effort \
             `{effort}`; change `model_reasoning_effort` and retry"
        )
        .into());
    }
    let entries = compatible
        .iter()
        .enumerate()
        .map(|(index, model)| {
            let supported = model
                .supported_reasoning_levels
                .as_ref()
                .expect("metadata was validated before projection");
            crate::codex_catalog::model_info(
                &crate::codex_catalog::ModelDescription {
                    id: &model.id,
                    display_name: &model.id,
                    owner: &model.owned_by,
                    default_reasoning_level: model
                        .default_reasoning_level
                        .clone()
                        .map(Value::String),
                    supported_reasoning_levels: serde_json::to_value(supported)
                        .expect("reasoning metadata serializes"),
                },
                index,
            )
        })
        .collect::<Vec<_>>();
    let path = root.join("router-codex-models.json");
    let rendered = format!(
        "{}\n",
        serde_json::to_string_pretty(&json!({"models": entries}))?
    );
    crate::durable_file::atomic_write_owner_only(&path, rendered.as_bytes())?;
    Ok(path)
}

fn validate_codex_reasoning_metadata(model: &RouterModel) -> Result<(), AnyError> {
    let supported = model.supported_reasoning_levels.as_ref().ok_or_else(|| {
        format!(
            "the live Codex catalog omitted reasoning metadata for model `{}`; refusing to \
             launch because model selection could silently replace the user's configured \
             reasoning effort",
            model.id
        )
    })?;
    if let Some(default) = model.default_reasoning_level.as_deref()
        && !supported.iter().any(|level| level.effort == default)
    {
        return Err(format!(
            "the live Codex catalog reports unsupported default reasoning level `{default}` \
             for model `{}`",
            model.id
        )
        .into());
    }
    Ok(())
}

fn model_supports_reasoning_effort(model: &RouterModel, effort: &str) -> bool {
    model
        .supported_reasoning_levels
        .as_ref()
        .is_some_and(|levels| levels.iter().any(|level| level.effort == effort))
}