Skip to content
This repository was archived by the owner on Jul 30, 2026. It is now read-only.
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
31 changes: 30 additions & 1 deletion crates/tokscale-cli/src/tui/ui/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,9 @@ fn get_single_provider_display_name(provider: &str) -> String {
"meta" => "Meta".to_string(),
"mistral" => "Mistral".to_string(),
"cohere" => "Cohere".to_string(),
"microsoft" => "Microsoft".to_string(),
"opencode" => "OpenCode".to_string(),
"owl" => "Owl".to_string(),
"github-copilot" => "GitHub Copilot".to_string(),
"commandcode" => "Command".to_string(),
"unisound" => "UniSound".to_string(),
_ => provider.to_string(),
Expand Down Expand Up @@ -321,6 +321,9 @@ mod tests {
("zai", "Z.AI"),
("zai-coding-plan", "Z.AI"),
("zai-coding-pln", "Z.AI"),
("z.ai", "Z.AI"),
("bigmodel.cn", "Z.AI"),
("open.bigmodel.cn", "Z.AI"),
("zhipuai-coding-plan", "Z.AI"),
("zhipu", "Z.AI"),
("xiaomi-token-plan-cn", "XiaoMi"),
Expand All @@ -329,10 +332,12 @@ mod tests {
("minimax-cn-coding-plan", "MiniMax"),
("moonshotai", "Kimi"),
("moonshot-coding-plan", "Kimi"),
("moonshot-ai", "Kimi"),
("kimi", "Kimi"),
("kimi-code", "Kimi"),
("kimi-for-coding", "Kimi"),
("qwen", "Qwen"),
("qwen-portal", "Qwen"),
("qwen-coding-plan", "Qwen"),
("meituan", "Meituan"),
("longcat-coding-plan", "Meituan"),
Expand All @@ -343,6 +348,18 @@ mod tests {
("alibaba-coding-plan-cn", "Alibaba"),
("tencent-coding-plan", "Tencent"),
("tecent-coding-plan", "Tencent"),
("xai-oauth", "xAI"),
("grok", "xAI"),
("grok-oauth", "xAI"),
("vertex", "Google"),
("vertex-ai", "Google"),
("google-gemini-cli", "Google"),
("google-antigravity", "Google"),
("azure", "Microsoft"),
("azure-ai", "Microsoft"),
("azure_ai", "Microsoft"),
("github-copilot", "Microsoft"),
("copilot-chat", "Microsoft"),
("openai-pro", "OpenAI"),
("openai-owl", "Owl"),
("openai-owlc", "Owl"),
Expand Down Expand Up @@ -384,6 +401,18 @@ mod tests {
get_provider_display_name("moonshotai, kimi-for-coding"),
"Kimi"
);
assert_eq!(
get_provider_display_name("github-copilot, copilot-chat"),
"Microsoft"
);
assert_eq!(
get_provider_display_name("azure, azure-ai, azure_ai, github-copilot"),
"Microsoft"
);
assert_eq!(
get_provider_display_name("xai-oauth, grok, grok-oauth"),
"xAI"
);
assert_eq!(
get_provider_display_name("opencode, opencode-go, opencode-zen"),
"OpenCode"
Expand Down
18 changes: 9 additions & 9 deletions crates/tokscale-core/src/aggregate/accumulators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{

use crate::{
aggregate::keys::{grouped_model_bucket_key, workspace_bucket},
normalize_provider_for_grouping, ordered_clients_by_token_contribution, positive_token_total,
ordered_clients_by_token_contribution, positive_token_total,
sessionize::SessionTimeEvent,
ClientContribution, ClientContributionOrder, DailyContribution, DailyTotals, GraphResult,
GroupBy, HourlyUsage, ModelPerformance, ModelUsage, MonthlyUsage, SessionContribution,
Expand Down Expand Up @@ -45,12 +45,12 @@ impl ModelEntries {
pub(super) fn push(&mut self, msg: &UnifiedMessage) {
let group_by = &self.group_by;
let canonical_model_id = msg.model_id.to_string();
let provider = normalize_provider_for_grouping(&msg.provider_id);
let provider = msg.provider_id.as_ref();
let (workspace_group_key, workspace_key, workspace_label) = workspace_bucket(msg);
let (key, merge_clients) = grouped_model_bucket_key(
group_by,
&msg.client,
&provider,
provider,
&workspace_group_key,
&msg.session_id,
&canonical_model_id,
Expand Down Expand Up @@ -82,7 +82,7 @@ impl ModelEntries {
None
},
model: canonical_model_id.clone(),
provider: provider.clone(),
provider: provider.to_string(),
input: 0,
output: 0,
cache_read: 0,
Expand Down Expand Up @@ -362,14 +362,14 @@ impl DailyAcc {
let client = msg.client.to_string();
let model = model_id.to_string();
let key = (client.clone(), model.clone());
let provider_id = normalize_provider_for_grouping(&msg.provider_id);
let provider_id = msg.provider_id.as_ref();
let client_entry = self
.clients
.entry(key)
.or_insert_with(|| ClientContribution {
client,
model_id: model,
provider_id: provider_id.clone(),
provider_id: provider_id.to_string(),
tokens: TokenBreakdown::default(),
cost: 0.0,
messages: 0,
Expand Down Expand Up @@ -552,16 +552,16 @@ impl SessionAcc {
.saturating_add(msg.tokens.reasoning);

let client = msg.client.to_string();
let provider_id = normalize_provider_for_grouping(&msg.provider_id);
let provider_id = msg.provider_id.as_ref();
let model_id = msg.model_id.to_string();
let key = (client.clone(), provider_id.clone(), model_id.clone());
let key = (client.clone(), provider_id.to_string(), model_id.clone());
let client_entry = self
.clients
.entry(key)
.or_insert_with(|| ClientContribution {
client,
model_id,
provider_id: provider_id.clone(),
provider_id: provider_id.to_string(),
tokens: TokenBreakdown::default(),
cost: 0.0,
messages: 0,
Expand Down
3 changes: 2 additions & 1 deletion crates/tokscale-core/src/aggregate/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ impl AggregationEngine {

/// The per-message fold. `AggregationEngine` consumes finalized local-report
/// messages. Callers must run `finalize_token_priced_messages` before
/// pushing; this layer deliberately does not re-canonicalize model ids.
/// pushing; this layer deliberately does not re-canonicalize model or
/// provider ids.
/// Applies the date filter once (mirroring `filter_messages_for_report`)
/// before dispatching to enabled accumulators.
pub fn push(&mut self, msg: &UnifiedMessage) {
Expand Down
46 changes: 23 additions & 23 deletions crates/tokscale-core/src/aggregate/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use crate::{
aggregate::keys::{
daily_source_model_key, grouped_model_bucket_key, hourly_model_key, workspace_bucket,
},
normalize_provider_for_grouping, ordered_clients_by_token_contribution, sessions,
ClientContributionOrder, GroupBy, ModelPerformance, UnifiedMessage,
ordered_clients_by_token_contribution, sessions, ClientContributionOrder, GroupBy,
ModelPerformance, UnifiedMessage,
};

fn positive_unified_token_total(tokens: &crate::TokenBreakdown) -> i64 {
Expand Down Expand Up @@ -462,12 +462,12 @@ impl TuiAcc {
pub(super) fn push(&mut self, msg: &UnifiedMessage) {
let group_by = &self.group_by;
let canonical_model_id = msg.model_id.to_string();
let provider = normalize_provider_for_grouping(&msg.provider_id);
let provider = msg.provider_id.as_ref();
let (workspace_group_key, workspace_key, workspace_label) = workspace_bucket(msg);
let (key, merge_clients) = grouped_model_bucket_key(
group_by,
&msg.client,
&provider,
provider,
&workspace_group_key,
&msg.session_id,
&canonical_model_id,
Expand All @@ -480,7 +480,7 @@ impl TuiAcc {
.entry(key.clone())
.or_insert_with(|| UsageModelEntry {
model: canonical_model_id.clone(),
provider: provider.clone(),
provider: provider.to_string(),
client: msg.client.to_string(),
workspace_key: if *group_by == GroupBy::WorkspaceModel {
workspace_key.clone()
Expand Down Expand Up @@ -600,22 +600,22 @@ impl TuiAcc {
group_by,
&msg.client,
&workspace_group_key,
&provider,
provider,
&msg.session_id,
&canonical_model_id,
);
let model_info = source_entry
.models
.entry(daily_model_key)
.or_insert_with(|| DailyModelInfo {
provider: provider.clone(),
provider: provider.to_string(),
display_name: daily_source_model_display_name(
group_by,
&workspace_label,
&msg.session_id,
&canonical_model_id,
),
color_key: model_color_key(group_by, &provider, &canonical_model_id),
color_key: model_color_key(group_by, provider, &canonical_model_id),
tokens: UsageTokenBreakdown::default(),
cost: 0.0,
messages: 0,
Expand Down Expand Up @@ -647,14 +647,14 @@ impl TuiAcc {
if msg.is_turn_start {
hourly_entry.turn_count += 1;
}
let hkey = hourly_model_key(group_by, &provider, &canonical_model_id);
let hkey = hourly_model_key(group_by, provider, &canonical_model_id);
let hmodel = hourly_entry
.models
.entry(hkey)
.or_insert_with(|| HourlyModelInfo {
provider: provider.clone(),
provider: provider.to_string(),
display_name: hourly_model_display_name(group_by, &canonical_model_id),
color_key: model_color_key(group_by, &provider, &canonical_model_id),
color_key: model_color_key(group_by, provider, &canonical_model_id),
tokens: UsageTokenBreakdown::default(),
cost: 0.0,
});
Expand Down Expand Up @@ -836,7 +836,7 @@ mod tests {
}

#[test]
fn test_aggregate_messages_model_grouping_normalizes_provider_display_aliases() {
fn test_aggregate_messages_model_grouping_uses_finalized_provider_ids() {
let loader = TuiUsageHarness;
let usage = loader
.aggregate_messages(
Expand All @@ -853,7 +853,7 @@ mod tests {
make_workspace_message(
"opencode",
"mimo-v2.5-pro",
"xiaomi-token-plan-cn",
"xiaomi",
"session-2",
2.0,
None,
Expand All @@ -871,7 +871,7 @@ mod tests {
}

#[test]
fn test_aggregate_messages_client_provider_model_normalizes_provider_display_aliases() {
fn test_aggregate_messages_client_provider_model_uses_finalized_provider_ids() {
let loader = TuiUsageHarness;
let usage = loader
.aggregate_messages(
Expand All @@ -888,7 +888,7 @@ mod tests {
make_workspace_message(
"opencode",
"mimo-v2.5-pro",
"xiaomi-token-plan-cn",
"xiaomi",
"session-2",
2.0,
None,
Expand Down Expand Up @@ -957,7 +957,7 @@ mod tests {
make_workspace_message(
"opencode",
"gpt-5.5",
"azure",
"microsoft",
"session-2",
2.0,
None,
Expand All @@ -973,7 +973,7 @@ mod tests {
let daily_models = &usage.daily[0].source_breakdown["opencode"].models;
assert_eq!(daily_models.len(), 2);
assert!(daily_models.contains_key("opencode:openai:gpt-5.5"));
assert!(daily_models.contains_key("opencode:azure:gpt-5.5"));
assert!(daily_models.contains_key("opencode:microsoft:gpt-5.5"));
assert!(daily_models
.values()
.all(|model| model.display_name == "gpt-5.5"));
Expand Down Expand Up @@ -1025,15 +1025,15 @@ mod tests {
}

#[test]
fn test_aggregate_messages_normalizes_moonshot_provider_to_kimi() {
fn test_aggregate_messages_uses_finalized_kimi_provider() {
let loader = TuiUsageHarness;
let usage = loader
.aggregate_messages(
vec![
make_workspace_message(
"claude",
"kimi-for-coding",
"moonshotai",
"kimi",
"session-1",
1.0,
None,
Expand All @@ -1042,7 +1042,7 @@ mod tests {
make_workspace_message(
"claude",
"kimi-for-coding",
"kimi-for-coding",
"kimi",
"session-2",
2.0,
None,
Expand Down Expand Up @@ -1444,7 +1444,7 @@ mod tests {
UnifiedMessage::new(
"claude",
"claude-sonnet-4.5",
"github-copilot",
"microsoft",
"session-2",
1_735_689_600_000,
crate::TokenBreakdown {
Expand All @@ -1466,7 +1466,7 @@ mod tests {
assert_eq!(claude.models.len(), 2);

let anthropic_key = "claude:anthropic:claude-sonnet-4.5";
let copilot_key = "claude:github-copilot:claude-sonnet-4.5";
let copilot_key = "claude:microsoft:claude-sonnet-4.5";
let anthropic_model = claude.models.get(anthropic_key).unwrap();
assert_eq!(anthropic_model.display_name, "claude-sonnet-4.5");
assert_eq!(anthropic_model.provider, "anthropic");
Expand All @@ -1475,7 +1475,7 @@ mod tests {

let copilot_model = claude.models.get(copilot_key).unwrap();
assert_eq!(copilot_model.display_name, "claude-sonnet-4.5");
assert_eq!(copilot_model.provider, "github-copilot");
assert_eq!(copilot_model.provider, "microsoft");
assert_eq!(copilot_model.tokens.total(), 30);
assert_eq!(copilot_model.messages, 1);
}
Expand Down
8 changes: 2 additions & 6 deletions crates/tokscale-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1021,12 +1021,8 @@ fn apply_token_pricing(message: &mut UnifiedMessage, pricing: Option<&pricing::P
}

fn canonicalize_message_provider(message: &mut UnifiedMessage) {
let raw_provider = message.provider_id.trim();
let provider = provider_identity::canonical_provider(raw_provider)
.or_else(|| {
provider_identity::inferred_provider_from_model(&message.model_id).map(str::to_string)
})
.unwrap_or_else(|| "unknown".to_string());
let provider =
provider_identity::finalized_provider_id(&message.provider_id, &message.model_id);
message.provider_id = sessions::intern::intern(&provider);
}

Expand Down
Loading
Loading