| 1 | //! Compile immutable provider metadata from its one committed data owner. |
| 2 | |
| 3 | use std::{ |
| 4 | collections::{BTreeMap, BTreeSet}, |
| 5 | env, fs, |
| 6 | path::PathBuf, |
| 7 | }; |
| 8 | |
| 9 | use serde_json::Value; |
| 10 | |
| 11 | fn text<'a>(row: &'a Value, key: &str) -> &'a str { |
| 12 | row[key] |
| 13 | .as_str() |
| 14 | .unwrap_or_else(|| panic!("missing string {key}")) |
| 15 | } |
| 16 | |
| 17 | fn quoted(value: &str) -> String { |
| 18 | format!("{value:?}") |
| 19 | } |
| 20 | |
| 21 | fn optional(row: &Value, key: &str) -> String { |
| 22 | match row.get(key) { |
| 23 | Some(Value::String(value)) => format!("Some({})", quoted(value)), |
| 24 | Some(Value::Null) => "None".into(), |
| 25 | _ => panic!("missing or malformed optional string {key}"), |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | fn strings(row: &Value, key: &str) -> String { |
| 30 | let values = row[key] |
| 31 | .as_array() |
| 32 | .unwrap_or_else(|| panic!("missing array {key}")); |
| 33 | format!( |
| 34 | "&[{}]", |
| 35 | values |
| 36 | .iter() |
| 37 | .map(|value| quoted(value.as_str().expect("string array"))) |
| 38 | .collect::<Vec<_>>() |
| 39 | .join(", ") |
| 40 | ) |
| 41 | } |
| 42 | |
| 43 | fn credential(row: &Value) -> String { |
| 44 | let acquisition = match text(row, "acquisition") { |
| 45 | "api_key" => "ApiKey", |
| 46 | "api_key_or_oauth" => "ApiKeyOrOAuth", |
| 47 | "local_optional" => "LocalOptional", |
| 48 | "oauth" => "OAuth", |
| 49 | "configuration" => "Configuration", |
| 50 | other => panic!("unknown credential classification {other}"), |
| 51 | }; |
| 52 | format!( |
| 53 | "crate::provider::CredentialHelp {{ acquisition: crate::provider::CredentialAcquisition::{acquisition}, credential_url: {}, docs_url: {}, guidance: {} }}", |
| 54 | optional(row, "credential_url"), |
| 55 | optional(row, "docs_url"), |
| 56 | quoted(text(row, "guidance")) |
| 57 | ) |
| 58 | } |
| 59 | |
| 60 | fn wire(row: &Value) -> &'static str { |
| 61 | match text(row, "wire_policy") { |
| 62 | "chat_completions" => { |
| 63 | "crate::provider::WirePolicy::Fixed(crate::provider::WireFormat::ChatCompletions)" |
| 64 | } |
| 65 | "responses" => "crate::provider::WirePolicy::Fixed(crate::provider::WireFormat::Responses)", |
| 66 | "anthropic_messages" => { |
| 67 | "crate::provider::WirePolicy::Fixed(crate::provider::WireFormat::AnthropicMessages)" |
| 68 | } |
| 69 | "model_aware" => "crate::provider::WirePolicy::ModelAware", |
| 70 | other => panic!("unknown wire classification {other}"), |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | fn main() { |
| 75 | println!("cargo:rerun-if-changed=assets/provider_descriptors.json"); |
| 76 | let file: Value = serde_json::from_str( |
| 77 | &fs::read_to_string("assets/provider_descriptors.json").expect("provider data"), |
| 78 | ) |
| 79 | .expect("provider data JSON"); |
| 80 | assert_eq!( |
| 81 | file["schema_version"].as_u64(), |
| 82 | Some(3), |
| 83 | "unsupported provider schema" |
| 84 | ); |
| 85 | let rows = file["providers"].as_array().expect("built-in rows"); |
| 86 | let mut ids = BTreeSet::new(); |
| 87 | let mut kinds = BTreeSet::new(); |
| 88 | let mut output = String::from("// Generated from provider_descriptors.json. Do not edit.\n"); |
| 89 | let mut selected = Vec::new(); |
| 90 | output.push_str("pub(crate) static BUILTIN_DESCRIPTORS: &[BuiltinProviderDescriptor] = &[\n"); |
| 91 | for row in rows { |
| 92 | let id = text(row, "id"); |
| 93 | let kind = text(row, "kind"); |
| 94 | assert!(!id.is_empty() && !kind.is_empty(), "empty identity"); |
| 95 | assert!(ids.insert(id), "duplicate provider id {id}"); |
| 96 | assert!(kinds.insert(kind), "duplicate provider kind {kind}"); |
| 97 | assert!( |
| 98 | kind.chars().all(|c| c.is_ascii_alphanumeric()), |
| 99 | "invalid Rust kind" |
| 100 | ); |
| 101 | let selectable = row["selectable"].as_bool().expect("selectable status"); |
| 102 | let retired = row["retired"].as_bool().expect("retired status"); |
| 103 | assert!( |
| 104 | !retired || !selectable, |
| 105 | "retired provider cannot be selectable" |
| 106 | ); |
| 107 | if kind == "Antigravity" { |
| 108 | assert!(retired && !selectable, "Antigravity remains retired"); |
| 109 | } |
| 110 | if selectable { |
| 111 | selected.push(( |
| 112 | row["selection_order"].as_u64().expect("selection order"), |
| 113 | format!("crate::ProviderKind::{kind}"), |
| 114 | )); |
| 115 | } else { |
| 116 | assert!(row["selection_order"].is_null(), "non-selectable order"); |
| 117 | } |
| 118 | output.push_str(&format!("BuiltinProviderDescriptor {{ kind: crate::ProviderKind::{kind}, id: {}, label: {}, base_url: {}, default_model: {}, env_vars: {}, aliases: {}, config_key: {}, secret_store_slot: {}, family: {}, selectable: {selectable}, retired: {retired}, wire_policy: {}, credential_help: {} }},\n", |
| 119 | quoted(id),quoted(text(row,"label")),quoted(text(row,"base_url")),quoted(text(row,"default_model")),strings(row,"env_vars"),strings(row,"aliases"),quoted(text(row,"config_key")),quoted(text(row,"secret_store_slot")),quoted(text(row,"family")),wire(row),credential(&row["credential_help"]))); |
| 120 | } |
| 121 | output.push_str("];\n"); |
| 122 | // Generated exhaustive projection makes a new/missing enum variant a |
| 123 | // compiler error, rather than a delayed registry panic. |
| 124 | output.push_str("pub(crate) const fn builtin_provider_descriptor(kind: crate::ProviderKind) -> &'static BuiltinProviderDescriptor { match kind {\n"); |
| 125 | for (index, row) in rows.iter().enumerate() { |
| 126 | output.push_str(&format!( |
| 127 | "crate::ProviderKind::{} => &BUILTIN_DESCRIPTORS[{index}],\n", |
| 128 | text(row, "kind") |
| 129 | )); |
| 130 | } |
| 131 | output.push_str("} }\n"); |
| 132 | selected.sort_by_key(|(order, _)| *order); |
| 133 | for (index, (order, _)) in selected.iter().enumerate() { |
| 134 | assert_eq!( |
| 135 | *order, |
| 136 | u64::try_from(index).expect("selection index"), |
| 137 | "selection order must be unique and contiguous" |
| 138 | ); |
| 139 | } |
| 140 | let selected: Vec<_> = selected.into_iter().map(|(_, kind)| kind).collect(); |
| 141 | output.push_str(&format!( |
| 142 | "pub(crate) const SELECTABLE_PROVIDER_KINDS: [crate::ProviderKind; {}] = [{}];\n", |
| 143 | selected.len(), |
| 144 | selected.join(", ") |
| 145 | )); |
| 146 | for row in file["descriptors"].as_array().expect("compatible rows") { |
| 147 | assert!( |
| 148 | ids.insert(text(row, "id")), |
| 149 | "descriptor shadows built-in identity" |
| 150 | ); |
| 151 | for field in ["id", "label", "base_url", "api_key_env", "default_model"] { |
| 152 | assert!(!text(row, field).is_empty(), "empty compatible-host field"); |
| 153 | } |
| 154 | assert!( |
| 155 | matches!( |
| 156 | text(row, "wire"), |
| 157 | "openai-compatible" | "anthropic-messages" |
| 158 | ), |
| 159 | "unknown compatible wire" |
| 160 | ); |
| 161 | assert!( |
| 162 | matches!(text(row, "discovery"), "models_endpoint" | "none"), |
| 163 | "unknown discovery" |
| 164 | ); |
| 165 | if row.get("aliases").is_some() { |
| 166 | let _ = strings(row, "aliases"); |
| 167 | } |
| 168 | for field in ["docs_url", "credential_url", "guidance"] { |
| 169 | if row.get(field).is_some() { |
| 170 | let _ = optional(row, field); |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | let legacy = &file["legacy_tui"]; |
| 175 | output.push_str(&format!("/// Exact retained TUI-only compatibility metadata.\npub const LEGACY_DEEPSEEK_CN: LegacyProviderDescriptor = LegacyProviderDescriptor {{ id: {}, label: {}, base_url: {}, default_model: {}, config_key: {}, secret_store_slot: {} }};\n", |
| 176 | quoted(text(legacy,"id")),quoted(text(legacy,"label")),quoted(text(legacy,"base_url")),quoted(text(legacy,"default_model")),quoted(text(legacy,"config_key")),quoted(text(legacy,"secret_store_slot")))); |
| 177 | // Existing descriptor data owns the released presentation tags and typed |
| 178 | // table projection. A missing field or duplicate tag refuses the build. |
| 179 | let mut compatibility: Vec<_> = rows.iter().chain(std::iter::once(legacy)).collect(); |
| 180 | compatibility.sort_by_key(|row| row["tui_order"].as_u64().expect("compatibility order")); |
| 181 | let mut tags = BTreeSet::new(); |
| 182 | let mut compatibility_ids = BTreeSet::new(); |
| 183 | output.push_str("static PROVIDER_COMPATIBILITY: &[ProviderCompatibility] = &[\n"); |
| 184 | for (index, row) in compatibility.iter().enumerate() { |
| 185 | assert_eq!( |
| 186 | row["tui_order"].as_u64(), |
| 187 | Some(index as u64), |
| 188 | "compatibility order must be contiguous" |
| 189 | ); |
| 190 | let id = text(row, "id"); |
| 191 | let tag = text(row, "tui_wire_tag"); |
| 192 | assert!( |
| 193 | !tag.is_empty() && tags.insert(tag), |
| 194 | "duplicate/empty compatibility tag" |
| 195 | ); |
| 196 | assert!( |
| 197 | compatibility_ids.insert(id), |
| 198 | "duplicate compatibility identity" |
| 199 | ); |
| 200 | for field in ["catalog_id", "catalog_source_id"] { |
| 201 | assert!( |
| 202 | rows.iter() |
| 203 | .any(|primary| text(primary, "id") == text(row, field)) |
| 204 | || text(row, field) == id, |
| 205 | "unknown catalog projection" |
| 206 | ); |
| 207 | } |
| 208 | let field = text(row, "table_field"); |
| 209 | assert!( |
| 210 | field.chars().next().is_some_and(|c| c.is_ascii_lowercase()) |
| 211 | && field |
| 212 | .chars() |
| 213 | .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'), |
| 214 | "invalid Rust table field" |
| 215 | ); |
| 216 | if let Some(key) = row.get("base_url_config_key") { |
| 217 | let key = key.as_str().expect("endpoint table key"); |
| 218 | assert!( |
| 219 | rows.iter().any(|row| text(row, "config_key") == key), |
| 220 | "unknown endpoint config table" |
| 221 | ); |
| 222 | } |
| 223 | output.push_str(&format!("ProviderCompatibility {{ kind: crate::ProviderKind::{}, id: {}, tui_wire_tag: {}, config_key: {}, base_url_config_key: {}, catalog_id: {}, catalog_source_id: {}, subagent_aliases: {}, selector_aliases: {}, label: {}, base_url: {}, default_model: {} }},\n", text(row,"kind"),quoted(id),quoted(tag),quoted(text(row,"config_key")),quoted(row.get("base_url_config_key").map_or_else(|| text(row,"config_key"), |value| value.as_str().expect("endpoint table key"))),quoted(text(row,"catalog_id")),quoted(text(row,"catalog_source_id")),strings(row,"subagent_aliases"),strings(row,"selector_aliases"),quoted(text(row,"label")),quoted(text(row,"base_url")),quoted(text(row,"default_model")))); |
| 224 | } |
| 225 | output.push_str("];\n"); |
| 226 | // Expand against the consumer's released typed configuration struct. The |
| 227 | // field inventory remains generated; custom/legacy provenance is admitted |
| 228 | // by that consumer before this pure projection is called. |
| 229 | output.push_str("#[macro_export]\nmacro_rules! provider_config_table {\n"); |
| 230 | for (mode, borrow) in [("read", "&"), ("write", "&mut ")] { |
| 231 | output.push_str(&format!( |
| 232 | "(@{mode} $providers:expr, $id:expr) => {{ match $id {{\n" |
| 233 | )); |
| 234 | for row in &compatibility { |
| 235 | if text(row, "kind") != "Custom" { |
| 236 | output.push_str(&format!( |
| 237 | "{} => Some({borrow}$providers.{}),\n", |
| 238 | quoted(text(row, "id")), |
| 239 | text(row, "table_field") |
| 240 | )); |
| 241 | } |
| 242 | } |
| 243 | output.push_str("_ => None } };\n"); |
| 244 | } |
| 245 | output.push_str("}\n"); |
| 246 | let mut public_orders = BTreeSet::new(); |
| 247 | for row in rows { |
| 248 | if row["retired"] == false && text(row, "kind") != "Custom" { |
| 249 | let order = row["web"]["order"] |
| 250 | .as_u64() |
| 251 | .expect("public presentation order"); |
| 252 | assert!( |
| 253 | public_orders.insert(order), |
| 254 | "duplicate public presentation order" |
| 255 | ); |
| 256 | assert!( |
| 257 | row["web"].get("variant").is_none(), |
| 258 | "retired enum presentation identity" |
| 259 | ); |
| 260 | } |
| 261 | } |
| 262 | assert!( |
| 263 | public_orders |
| 264 | .iter() |
| 265 | .enumerate() |
| 266 | .all(|(index, order)| *order == index as u64), |
| 267 | "public order must be contiguous" |
| 268 | ); |
| 269 | for (key, row) in file["route_credential_help"] |
| 270 | .as_object() |
| 271 | .expect("route credential help") |
| 272 | .iter() |
| 273 | .collect::<BTreeMap<_, _>>() |
| 274 | { |
| 275 | let name = key.replace('-', "_").to_ascii_uppercase(); |
| 276 | assert!( |
| 277 | name.chars().all(|c| c.is_ascii_uppercase() || c == '_'), |
| 278 | "invalid route help key" |
| 279 | ); |
| 280 | output.push_str(&format!( |
| 281 | "pub(crate) const {name}_CREDENTIAL_HELP: crate::provider::CredentialHelp = {};\n", |
| 282 | credential(row) |
| 283 | )); |
| 284 | } |
| 285 | let mut defaults = String::from("// Generated data projections. Do not edit.\n"); |
| 286 | let refs = file["constant_refs"] |
| 287 | .as_object() |
| 288 | .expect("constant references"); |
| 289 | let constants = file["compatibility_constants"] |
| 290 | .as_object() |
| 291 | .expect("compatibility constants"); |
| 292 | for (name, value) in refs.iter().collect::<BTreeMap<_, _>>() { |
| 293 | assert!(!constants.contains_key(name), "duplicate constant"); |
| 294 | assert!( |
| 295 | name.chars() |
| 296 | .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'), |
| 297 | "invalid constant name" |
| 298 | ); |
| 299 | let row = rows |
| 300 | .iter() |
| 301 | .find(|row| row["id"] == value["provider"]) |
| 302 | .expect("constant row"); |
| 303 | let field = text(value, "field"); |
| 304 | assert!( |
| 305 | matches!(field, "base_url" | "default_model" | "credential_url"), |
| 306 | "unknown constant field" |
| 307 | ); |
| 308 | let source = if field == "credential_url" { |
| 309 | &row["credential_help"][field] |
| 310 | } else { |
| 311 | &row[field] |
| 312 | }; |
| 313 | defaults.push_str(&format!( |
| 314 | "/// Projection of the committed provider descriptor.\npub const {name}: &str = {};\n", |
| 315 | quoted(source.as_str().expect("constant field string")) |
| 316 | )); |
| 317 | } |
| 318 | for (name, value) in constants.iter().collect::<BTreeMap<_, _>>() { |
| 319 | assert!( |
| 320 | name.chars() |
| 321 | .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'), |
| 322 | "invalid constant name" |
| 323 | ); |
| 324 | defaults.push_str(&format!( |
| 325 | "/// Retained data-owned compatibility seed.\npub const {name}: &str = {};\n", |
| 326 | quoted(value.as_str().expect("compatibility string")) |
| 327 | )); |
| 328 | } |
| 329 | let directory = PathBuf::from(env::var_os("OUT_DIR").expect("build output")); |
| 330 | fs::write(directory.join("provider_descriptors.rs"), output).expect("write descriptors"); |
| 331 | fs::write(directory.join("provider_defaults.rs"), defaults).expect("write defaults"); |
| 332 | |
| 333 | println!("cargo:rerun-if-changed=assets/catalog_corrections.json"); |
| 334 | let catalog: Value = serde_json::from_str( |
| 335 | &fs::read_to_string("assets/catalog_corrections.json").expect("catalog data"), |
| 336 | ) |
| 337 | .expect("catalog JSON"); |
| 338 | let reviewed = &catalog["reviewed"]; |
| 339 | assert!(!text(reviewed, "revision").is_empty(), "reviewed revision"); |
| 340 | let mut catalog_constants = |
| 341 | String::from("// Generated from catalog_corrections.json.reviewed. Do not edit.\n"); |
| 342 | for (name, value) in reviewed["constants"] |
| 343 | .as_object() |
| 344 | .expect("model constants") |
| 345 | .iter() |
| 346 | .collect::<BTreeMap<_, _>>() |
| 347 | { |
| 348 | assert!( |
| 349 | name.chars() |
| 350 | .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'), |
| 351 | "invalid catalog constant" |
| 352 | ); |
| 353 | catalog_constants.push_str(&format!( |
| 354 | "pub const {name}: &str = {};\n", |
| 355 | quoted(value.as_str().expect("model constant string")) |
| 356 | )); |
| 357 | } |
| 358 | for (name, reference) in reviewed["numeric_refs"] |
| 359 | .as_object() |
| 360 | .expect("numeric contract references") |
| 361 | { |
| 362 | assert!( |
| 363 | name.chars() |
| 364 | .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'), |
| 365 | "invalid numeric contract name" |
| 366 | ); |
| 367 | let field = text(reference, "field"); |
| 368 | assert!( |
| 369 | matches!( |
| 370 | field, |
| 371 | "context_window" | "max_output" | "generation_default" |
| 372 | ), |
| 373 | "unknown numeric contract field" |
| 374 | ); |
| 375 | let value = reviewed["intrinsic"][text(reference, "model")][field] |
| 376 | .as_u64() |
| 377 | .expect("numeric intrinsic fact"); |
| 378 | assert!( |
| 379 | value > 0 && value <= u64::from(u32::MAX), |
| 380 | "numeric intrinsic bound" |
| 381 | ); |
| 382 | catalog_constants.push_str(&format!("pub const {name}: u32 = {value};\n")); |
| 383 | } |
| 384 | for (name, value) in reviewed["groups"] |
| 385 | .as_object() |
| 386 | .expect("model rosters") |
| 387 | .iter() |
| 388 | .collect::<BTreeMap<_, _>>() |
| 389 | { |
| 390 | assert!( |
| 391 | name.chars() |
| 392 | .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'), |
| 393 | "invalid catalog group" |
| 394 | ); |
| 395 | let values = value |
| 396 | .as_array() |
| 397 | .expect("model roster array") |
| 398 | .iter() |
| 399 | .map(|v| quoted(v.as_str().expect("model roster string"))) |
| 400 | .collect::<Vec<_>>() |
| 401 | .join(", "); |
| 402 | catalog_constants.push_str(&format!("pub const {name}: &[&str] = &[{values}];\n")); |
| 403 | } |
| 404 | for (name, provider, endpoint) in [ |
| 405 | ( |
| 406 | "OPENCODE_ZEN_RESPONSES_MODELS", |
| 407 | "opencode-zen", |
| 408 | Some("responses"), |
| 409 | ), |
| 410 | ( |
| 411 | "OPENCODE_ZEN_MESSAGES_MODELS", |
| 412 | "opencode-zen", |
| 413 | Some("messages"), |
| 414 | ), |
| 415 | ("OPENCODE_ZEN_CHAT_MODELS", "opencode-zen", Some("chat")), |
| 416 | ("OPENCODE_GO_MODELS", "opencode-go", None), |
| 417 | ("MODELSTUDIO_TEXT_MODELS", "modelstudio-token-plan", None), |
| 418 | ("CODEWHALE_FALLBACK_MODELS", "codewhale", None), |
| 419 | ] { |
| 420 | let values = reviewed["transports"] |
| 421 | .as_array() |
| 422 | .expect("transport rows") |
| 423 | .iter() |
| 424 | .filter(|row| { |
| 425 | row["provider"] == provider |
| 426 | && endpoint.is_none_or(|endpoint| row["endpoint_key"] == endpoint) |
| 427 | }) |
| 428 | .map(|row| quoted(text(row, "id"))) |
| 429 | .collect::<Vec<_>>() |
| 430 | .join(", "); |
| 431 | catalog_constants.push_str(&format!("pub const {name}: &[&str] = &[{values}];\n")); |
| 432 | } |
| 433 | // Alias/completion entries refer to existing defaults or model groups; |
| 434 | // the source owner never repeats a descriptor-owned default value. |
| 435 | let mut values = BTreeMap::new(); |
| 436 | for (name, value) in constants { |
| 437 | values.insert(name.as_str(), value.as_str().expect("compatibility value")); |
| 438 | } |
| 439 | for (name, reference) in refs { |
| 440 | let provider = rows |
| 441 | .iter() |
| 442 | .find(|row| row["id"] == reference["provider"]) |
| 443 | .expect("constant provider"); |
| 444 | let field = text(reference, "field"); |
| 445 | let value = if field == "credential_url" { |
| 446 | &provider["credential_help"][field] |
| 447 | } else { |
| 448 | &provider[field] |
| 449 | }; |
| 450 | values.insert(name.as_str(), value.as_str().expect("constant value")); |
| 451 | } |
| 452 | for (name, value) in reviewed["constants"] |
| 453 | .as_object() |
| 454 | .expect("catalog constants") |
| 455 | { |
| 456 | assert!( |
| 457 | values |
| 458 | .insert(name, value.as_str().expect("catalog value")) |
| 459 | .is_none(), |
| 460 | "duplicate data constant" |
| 461 | ); |
| 462 | } |
| 463 | let resolve = |value: &str| -> String { |
| 464 | match value.strip_prefix('$') { |
| 465 | Some(name) => values |
| 466 | .get(name) |
| 467 | .unwrap_or_else(|| panic!("missing constant {name}")) |
| 468 | .to_string(), |
| 469 | None => value.to_string(), |
| 470 | } |
| 471 | }; |
| 472 | let mut groups: BTreeMap<String, Vec<String>> = reviewed["groups"] |
| 473 | .as_object() |
| 474 | .expect("groups") |
| 475 | .iter() |
| 476 | .map(|(name, value)| { |
| 477 | ( |
| 478 | name.clone(), |
| 479 | value |
| 480 | .as_array() |
| 481 | .expect("group") |
| 482 | .iter() |
| 483 | .map(|v| resolve(v.as_str().expect("group entry"))) |
| 484 | .collect(), |
| 485 | ) |
| 486 | }) |
| 487 | .collect(); |
| 488 | for (name, provider, endpoint) in [ |
| 489 | ( |
| 490 | "OPENCODE_ZEN_RESPONSES_MODELS", |
| 491 | "opencode-zen", |
| 492 | Some("responses"), |
| 493 | ), |
| 494 | ( |
| 495 | "OPENCODE_ZEN_MESSAGES_MODELS", |
| 496 | "opencode-zen", |
| 497 | Some("messages"), |
| 498 | ), |
| 499 | ("OPENCODE_ZEN_CHAT_MODELS", "opencode-zen", Some("chat")), |
| 500 | ("OPENCODE_GO_MODELS", "opencode-go", None), |
| 501 | ("MODELSTUDIO_TEXT_MODELS", "modelstudio-token-plan", None), |
| 502 | ("CODEWHALE_FALLBACK_MODELS", "codewhale", None), |
| 503 | ] { |
| 504 | groups.insert( |
| 505 | name.into(), |
| 506 | reviewed["transports"] |
| 507 | .as_array() |
| 508 | .expect("transports") |
| 509 | .iter() |
| 510 | .filter(|row| { |
| 511 | row["provider"] == provider |
| 512 | && endpoint.is_none_or(|endpoint| row["endpoint_key"] == endpoint) |
| 513 | }) |
| 514 | .map(|row| text(row, "id").to_string()) |
| 515 | .collect(), |
| 516 | ); |
| 517 | } |
| 518 | catalog_constants.push_str("#[must_use]\npub fn compatibility_alias(group: &str, model: &str) -> Option<&'static str> { match (group, model) {\n"); |
| 519 | for (group, aliases) in reviewed["compatibility_aliases"] |
| 520 | .as_object() |
| 521 | .expect("aliases") |
| 522 | { |
| 523 | let mut selectors = BTreeSet::new(); |
| 524 | for (alias, target) in aliases.as_object().expect("alias map") { |
| 525 | let alias = resolve(alias); |
| 526 | let target = resolve(target.as_str().expect("alias target")); |
| 527 | assert!( |
| 528 | selectors.insert(alias.clone()), |
| 529 | "duplicate resolved selector {alias}" |
| 530 | ); |
| 531 | catalog_constants.push_str(&format!( |
| 532 | "({}, {}) => Some({}),\n", |
| 533 | quoted(group), |
| 534 | quoted(&alias), |
| 535 | quoted(&target) |
| 536 | )); |
| 537 | } |
| 538 | } |
| 539 | catalog_constants.push_str("_ => None, } }\n#[must_use]\npub fn completion_names(provider: &str) -> &'static [&'static str] { match provider {\n"); |
| 540 | for (provider, roster) in reviewed["completion_rosters"] |
| 541 | .as_object() |
| 542 | .expect("completion rosters") |
| 543 | { |
| 544 | assert!( |
| 545 | rows.iter().any(|row| row["id"] == provider.as_str()) || provider == text(legacy, "id"), |
| 546 | "unknown completion provider" |
| 547 | ); |
| 548 | let mut list = Vec::new(); |
| 549 | for entry in roster.as_array().expect("completion roster") { |
| 550 | let entry = entry.as_str().expect("completion entry"); |
| 551 | if let Some(group) = entry.strip_prefix('@') { |
| 552 | list.extend( |
| 553 | groups |
| 554 | .get(group) |
| 555 | .unwrap_or_else(|| panic!("missing group {group}")) |
| 556 | .iter() |
| 557 | .cloned(), |
| 558 | ); |
| 559 | } else { |
| 560 | list.push(resolve(entry)); |
| 561 | } |
| 562 | } |
| 563 | // Zen's logical default can be an alias of a documented row. Preserve |
| 564 | // its original stable, case-insensitive first-occurrence projection. |
| 565 | if provider == "opencode-zen" { |
| 566 | let mut seen = BTreeSet::new(); |
| 567 | list.retain(|id| seen.insert(id.to_ascii_lowercase())); |
| 568 | } |
| 569 | assert!( |
| 570 | !matches!(provider.as_str(), "antigravity" | "custom") || list.is_empty(), |
| 571 | "retired/custom completion rows" |
| 572 | ); |
| 573 | catalog_constants.push_str(&format!( |
| 574 | "{} => &[{}],\n", |
| 575 | quoted(provider), |
| 576 | list.iter() |
| 577 | .map(|v| quoted(v)) |
| 578 | .collect::<Vec<_>>() |
| 579 | .join(", ") |
| 580 | )); |
| 581 | } |
| 582 | catalog_constants.push_str("_ => &[], } }\n"); |
| 583 | fs::write(directory.join("catalog_constants.rs"), catalog_constants) |
| 584 | .expect("write catalog constants"); |
| 585 | } |
| 586 |