From 2f1d4cdf881e9c4b4688556b527a690255ed00fb Mon Sep 17 00:00:00 2001 From: ualtinok Date: Mon, 20 Jul 2026 09:06:24 +0200 Subject: [PATCH] cortexkit-provider-usage 0.2.0: add optional apiProvider field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds ProviderUsage.api_provider (wire: apiProvider, camelCase, omitted when absent) carrying the canonical models.dev provider slug alongside the CodexBar provider name — e.g. "openai" for provider=="codex", "anthropic" for "claude", "google" for "gemini", "xai" for "grok". Three consumers (ALF's router, the ck CLI, astrocyte's capacity axis) each hand-roll the same CodexBar→canonical translation today; this field lets them key on one canonical name instead. Producers populate it when the canonical name is known and leave it absent for providers with no models.dev counterpart (consumers fall back to provider). Additive + skip-if-none: unpopulated entries serialize byte-identically to 0.1. --- crates/cortexkit-provider-usage/Cargo.toml | 2 +- crates/cortexkit-provider-usage/src/lib.rs | 27 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/cortexkit-provider-usage/Cargo.toml b/crates/cortexkit-provider-usage/Cargo.toml index a68f4bc..f4962d7 100644 --- a/crates/cortexkit-provider-usage/Cargo.toml +++ b/crates/cortexkit-provider-usage/Cargo.toml @@ -11,7 +11,7 @@ # says; this crate makes no guarantee about how a producer derived the numbers. [package] name = "cortexkit-provider-usage" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/crates/cortexkit-provider-usage/src/lib.rs b/crates/cortexkit-provider-usage/src/lib.rs index be7508a..b6a4d29 100644 --- a/crates/cortexkit-provider-usage/src/lib.rs +++ b/crates/cortexkit-provider-usage/src/lib.rs @@ -131,6 +131,15 @@ fn account_info_is_empty(value: &Option) -> bool { pub struct ProviderUsage { /// CodexBar provider name (e.g. "codex"), which consumers map to their own id. pub provider: String, + /// Canonical API provider identifier — the models.dev slug for the same + /// provider (e.g. "openai" when `provider == "codex"`, "anthropic" for + /// "claude", "google" for "gemini", "xai" for "grok"). Present when the + /// producer knows the canonical name; absent for providers with no models.dev + /// counterpart, where consumers fall back to `provider`. Lets every consumer + /// key on one canonical name instead of each maintaining its own + /// CodexBar-name → canonical map. + #[serde(skip_serializing_if = "Option::is_none", default)] + pub api_provider: Option, #[serde(skip_serializing_if = "Option::is_none")] pub account: Option, /// Which retrieval path produced this (e.g. "oauth") — observability only. @@ -155,6 +164,7 @@ impl ProviderUsage { pub fn healthy(provider: &str, account: Option, source: &str, usage: Usage) -> Self { Self { provider: provider.to_string(), + api_provider: None, account, source: Some(source.to_string()), account_info: None, @@ -170,6 +180,7 @@ impl ProviderUsage { pub fn degraded(provider: &str, error: impl std::fmt::Display) -> Self { Self { provider: provider.to_string(), + api_provider: None, account: None, source: None, account_info: None, @@ -276,4 +287,20 @@ mod tests { assert!(json.contains("\"error\":\"no session\"")); assert!(!json.contains("usage")); } + + #[test] + fn api_provider_is_camel_case_present_when_set_and_omitted_when_absent() { + let mut entry = ProviderUsage::healthy("codex", None, "oauth", Usage::default()); + let json = serde_json::to_string(&entry).unwrap(); + assert!( + !json.contains("apiProvider"), + "absent api_provider must be omitted" + ); + + entry.api_provider = Some("openai".to_string()); + let json = serde_json::to_string(&entry).unwrap(); + assert!(json.contains("\"apiProvider\":\"openai\"")); + let back: ProviderUsage = serde_json::from_str(&json).unwrap(); + assert_eq!(back, entry); + } }