Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/cortexkit-provider-usage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions crates/cortexkit-provider-usage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ fn account_info_is_empty(value: &Option<AccountInfo>) -> 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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub account: Option<String>,
/// Which retrieval path produced this (e.g. "oauth") — observability only.
Expand All @@ -155,6 +164,7 @@ impl ProviderUsage {
pub fn healthy(provider: &str, account: Option<String>, source: &str, usage: Usage) -> Self {
Self {
provider: provider.to_string(),
api_provider: None,
account,
source: Some(source.to_string()),
account_info: None,
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
}