Skip to main content

oxicode_agent/
model_id.rs

1use oxicode_ai::Model;
2use oxicode_ai::register_builtins::get_builtin_provider;
3use oxicode_ai::{get_model, get_model_entry, lookup_model};
4use std::collections::HashMap;
5
6/// Parse a model ID in "provider/model" or plain "model" format.
7/// Uses >= 2 segments (handles provider/org/model format).
8pub fn resolve_model_from_id(model_id: &str) -> Option<Model> {
9    let parts: Vec<&str> = model_id.split('/').collect();
10    let provider = if parts.len() >= 2 {
11        parts[0]
12    } else {
13        "anthropic"
14    };
15    let model_id_part = if parts.len() >= 2 {
16        parts[1..].join("/")
17    } else {
18        parts[0].to_string()
19    };
20
21    // Check dynamic registry first (includes router/auto and custom provider models).
22    if let Some(m) = lookup_model(provider, &model_id_part) {
23        return Some(m);
24    }
25    // Fall back to static registry.
26    if let Some(m) = get_model(provider, &model_id_part) {
27        return Some(m.clone());
28    }
29    // Fallback: construct from ModelEntry catalog (materialized from models.dev snapshot).
30    // This handles catalog-only providers (e.g. "zai-coding-plan/glm-5-turbo") that
31    // aren't in the hand-written STATIC_MODELS registry.
32    if let Some(entry) = get_model_entry(provider, &model_id_part) {
33        return Some(model_from_entry(provider, entry));
34    }
35    None
36}
37/// Construct a `Model` from a `ModelEntry` catalog entry + `BuiltinProvider` metadata.
38///
39/// Used as fallback when a model is in the materialized models.dev catalog
40/// but not in the static `STATIC_MODELS` registry (e.g. `zai-coding-plan/glm-5-turbo`).
41fn model_from_entry(provider: &str, entry: &oxicode_ai::model_db::ModelEntry) -> Model {
42    let builtin = get_builtin_provider(provider);
43    let base_url = builtin.map(|b| b.base_url).unwrap_or("").to_string();
44    let headers: HashMap<String, String> = builtin
45        .map(|b| {
46            b.extra_headers
47                .iter()
48                .map(|(k, v)| (k.to_string(), v.to_string()))
49                .collect()
50        })
51        .unwrap_or_default();
52    let compat = match provider {
53        "zai" => Some(oxicode_ai::CompatSettings {
54            thinking_format: Some(oxicode_ai::ThinkingFormat::Zai),
55            ..Default::default()
56        }),
57        _ => None,
58    };
59
60    Model {
61        id: entry.id.to_string(),
62        name: entry.name.to_string(),
63        api: entry.api,
64        provider: provider.to_string(),
65        base_url,
66        reasoning: entry.reasoning,
67        input: entry.input.to_vec(),
68        cost: oxicode_ai::Cost {
69            input: entry.cost_input,
70            output: entry.cost_output,
71            cache_read: entry.cost_cache_read,
72            cache_write: entry.cost_cache_write,
73        },
74        context_window: entry.context_window as usize,
75        max_tokens: entry.max_tokens as usize,
76        headers,
77        compat,
78    }
79}