Skip to content

Commit cf2d7cd

Browse files
feat(statusline): add modelLabelAliases config for long model identifiers (#1219)
* feat(statusline): add modelLabelAliases config for long model identifiers Adds a model_label_aliases hash map to the statusline config that lets users define short display labels for long model identifiers (like AWS Bedrock Inference Profile ARNs). When a model's display_name matches a key in the aliases, the corresponding value is used instead in the statusline output. Example config: { "commands": { "statusline": { "modelLabelAliases": { "arn:aws:bedrock:us-east-1:123:inference-profile/abc": "claude-opus" } } } } Closes #896 * chore: regenerate config-schema.json with modelLabelAliases * test(statusline): cover modelLabelAliases resolution and parsing Extract the alias lookup in render_statusline into a small resolve_model_label helper so the mapping is unit-testable without the cost-aggregation machinery, and add tests: - resolve_model_label maps a Bedrock ARN display name to its alias and falls back to the original display name when no alias matches. - StatuslineSpecificOptions::from_map parses modelLabelAliases and ignores non-string values. * docs(statusline): document modelLabelAliases config Add the modelLabelAliases option to the statusline config example and a dedicated "Model Label Aliases" section explaining how to map long model identifiers (e.g. AWS Bedrock inference profile ARNs) to short labels. --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> Co-authored-by: ryoppippi <1560508+ryoppippi@users.noreply.github.com>
1 parent b2d1baa commit cf2d7cd

8 files changed

Lines changed: 180 additions & 3 deletions

File tree

apps/ccusage/config-schema.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,14 @@
947947
"markdownDescription": "Show statusline debug information.",
948948
"type": "boolean"
949949
},
950+
"modelLabelAliases": {
951+
"additionalProperties": {
952+
"type": "string"
953+
},
954+
"description": "Map model identifiers to short display labels.",
955+
"markdownDescription": "Map model identifiers to short display labels.",
956+
"type": "object"
957+
},
950958
"noCache": {
951959
"default": false,
952960
"description": "Disable statusline cache.",
@@ -1187,6 +1195,14 @@
11871195
"markdownDescription": "Cost calculation mode.",
11881196
"type": "string"
11891197
},
1198+
"modelLabelAliases": {
1199+
"additionalProperties": {
1200+
"type": "string"
1201+
},
1202+
"description": "Map model identifiers to short display labels.",
1203+
"markdownDescription": "Map model identifiers to short display labels.",
1204+
"type": "object"
1205+
},
11901206
"noCache": {
11911207
"description": "Disable statusline cache.",
11921208
"markdownDescription": "Disable statusline cache.",
@@ -2646,6 +2662,14 @@
26462662
"markdownDescription": "Show statusline debug information.",
26472663
"type": "boolean"
26482664
},
2665+
"modelLabelAliases": {
2666+
"additionalProperties": {
2667+
"type": "string"
2668+
},
2669+
"description": "Map model identifiers to short display labels.",
2670+
"markdownDescription": "Map model identifiers to short display labels.",
2671+
"type": "object"
2672+
},
26492673
"noCache": {
26502674
"default": false,
26512675
"description": "Disable statusline cache.",

docs/guide/config-files.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,10 @@ For a namespaced command, options are applied in this order:
364364
"statusline": {
365365
"offline": true,
366366
"cache": true,
367-
"refreshInterval": 2
367+
"refreshInterval": 2,
368+
"modelLabelAliases": {
369+
"arn:aws:bedrock:ap-northeast-1:012345678910:application-inference-profile/abcde12345": "claude-opus-4-6"
370+
}
368371
}
369372
}
370373
}

docs/guide/statusline.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,45 @@ bun x ccusage statusline --visual-burn-rate emoji
289289
- ⚠️ Moderate (Yellow)
290290
- 🚨 High (Red)
291291

292+
### Model Label Aliases
293+
294+
Some setups report very long model identifiers. For example, AWS Bedrock
295+
inference profiles surface full ARNs such as
296+
`arn:aws:bedrock:ap-northeast-1:012345678910:application-inference-profile/abcde12345`,
297+
which gets truncated in the statusline and hides which model is in use.
298+
299+
Use `modelLabelAliases` in your configuration file to map a model identifier to
300+
a short display label. When the active model matches a key, the statusline shows
301+
the alias instead:
302+
303+
```json
304+
{
305+
"commands": {
306+
"statusline": {
307+
"modelLabelAliases": {
308+
"arn:aws:bedrock:ap-northeast-1:012345678910:application-inference-profile/abcde12345": "claude-opus-4-6"
309+
}
310+
}
311+
}
312+
}
313+
```
314+
315+
With the alias above, the statusline changes from:
316+
317+
```text
318+
🤖 arn:aws:bedrock:ap-northeast-1:012345678910:application-inference-profile/abcde12345 | 💰 ...
319+
```
320+
321+
to:
322+
323+
```text
324+
🤖 claude-opus-4-6 | 💰 ...
325+
```
326+
327+
Keys are matched exactly against the model's display name. Models without a
328+
matching alias are shown unchanged. See the [Configuration Guide](/guide/configuration)
329+
for more details.
330+
292331
## Troubleshooting
293332

294333
### No Output Displayed

rust/crates/ccusage-cli/src/types.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::path::PathBuf;
1+
use std::{collections::HashMap, path::PathBuf};
22

33
pub struct Cli {
44
pub command: Option<Command>,
@@ -108,6 +108,7 @@ pub struct StatuslineArgs {
108108
pub timezone: Option<String>,
109109
pub config: Option<PathBuf>,
110110
pub debug: bool,
111+
pub model_label_aliases: HashMap<String, String>,
111112
}
112113

113114
#[derive(Clone)]
@@ -163,6 +164,7 @@ impl Default for StatuslineArgs {
163164
timezone: None,
164165
config: None,
165166
debug: false,
167+
model_label_aliases: HashMap::new(),
166168
}
167169
}
168170
}

rust/crates/ccusage/src/commands/mod.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,22 @@ pub(crate) fn run_statusline(args: StatuslineArgs) -> Result<()> {
384384
Ok(())
385385
}
386386

387+
/// Resolve the model label shown in the statusline.
388+
///
389+
/// Looks up the model's `display_name` in the user-configured alias map and
390+
/// returns the matching short label when present. Long identifiers such as AWS
391+
/// Bedrock inference profile ARNs can therefore be displayed as a concise name.
392+
/// Falls back to the original `display_name` when no alias matches.
393+
fn resolve_model_label<'a>(
394+
aliases: &'a std::collections::HashMap<String, String>,
395+
display_name: &'a str,
396+
) -> &'a str {
397+
aliases
398+
.get(display_name)
399+
.map(String::as_str)
400+
.unwrap_or(display_name)
401+
}
402+
387403
fn render_statusline(
388404
hook: &StatuslineHook,
389405
args: &StatuslineArgs,
@@ -498,9 +514,11 @@ fn render_statusline(
498514
.unwrap_or_else(|| "N/A".to_string())
499515
};
500516

517+
let model_label = resolve_model_label(&args.model_label_aliases, &hook.model.display_name);
518+
501519
Ok(format!(
502520
"🤖 {} | 💰 {} session / {} today / {}{} | 🧠 {}",
503-
hook.model.display_name,
521+
model_label,
504522
session_display,
505523
format_currency(today_cost),
506524
block_info,
@@ -909,4 +927,29 @@ mod tests {
909927
Some("stale status")
910928
);
911929
}
930+
931+
#[test]
932+
fn resolves_model_label_from_alias_map() {
933+
let mut aliases = std::collections::HashMap::new();
934+
aliases.insert(
935+
"arn:aws:bedrock:ap-northeast-1:012345678910:application-inference-profile/abcde12345"
936+
.to_string(),
937+
"claude-opus-4-6".to_string(),
938+
);
939+
940+
assert_eq!(
941+
resolve_model_label(
942+
&aliases,
943+
"arn:aws:bedrock:ap-northeast-1:012345678910:application-inference-profile/abcde12345"
944+
),
945+
"claude-opus-4-6"
946+
);
947+
}
948+
949+
#[test]
950+
fn falls_back_to_display_name_when_no_alias_matches() {
951+
let aliases = std::collections::HashMap::new();
952+
953+
assert_eq!(resolve_model_label(&aliases, "Opus 4.1"), "Opus 4.1");
954+
}
912955
}

rust/crates/ccusage/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ pub(crate) fn apply_config_to_statusline_args(args: &mut StatuslineArgs, config:
347347
if let Some(debug) = options.debug {
348348
args.debug = debug;
349349
}
350+
if let Some(aliases) = options.model_label_aliases {
351+
args.model_label_aliases = aliases;
352+
}
350353
}
351354
}
352355

rust/crates/ccusage/src/config_schema.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![allow(dead_code)]
22

3+
use std::collections::HashMap;
4+
35
use serde::de::DeserializeOwned;
46
use serde::Deserialize;
57
use serde_json::{json, Map, Value};
@@ -432,6 +434,8 @@ pub(crate) struct StatuslineSpecificOptions {
432434
pub(crate) timezone: Option<String>,
433435
/// Show statusline debug information.
434436
pub(crate) debug: Option<bool>,
437+
/// Map model identifiers to short display labels.
438+
pub(crate) model_label_aliases: Option<HashMap<String, String>>,
435439
}
436440

437441
#[derive(Debug, Default, Deserialize, JsonSchema)]
@@ -581,6 +585,7 @@ impl StatuslineSpecificOptions {
581585
context_medium_threshold: u64_option(map, "contextMediumThreshold"),
582586
timezone: string_option(map, "timezone"),
583587
debug: bool_option(map, "debug"),
588+
model_label_aliases: hashmap_option(map, "modelLabelAliases"),
584589
}
585590
}
586591
}
@@ -720,6 +725,21 @@ where
720725
serde_json::from_value(map.get(key)?.clone()).ok()
721726
}
722727

728+
fn hashmap_option(map: &Map<String, Value>, key: &str) -> Option<HashMap<String, String>> {
729+
let obj = map.get(key)?.as_object()?;
730+
let mut result = HashMap::new();
731+
for (k, v) in obj {
732+
if let Some(s) = v.as_str() {
733+
result.insert(k.clone(), s.to_string());
734+
}
735+
}
736+
if result.is_empty() {
737+
None
738+
} else {
739+
Some(result)
740+
}
741+
}
742+
723743
fn enrich_schema(value: &mut Value) {
724744
match value {
725745
Value::Object(map) => {
@@ -917,6 +937,7 @@ mod tests {
917937
use serde_json::{json, Value};
918938

919939
use super::generate_config_schema_json;
940+
use super::StatuslineSpecificOptions;
920941

921942
#[test]
922943
fn schema_option_sets_expose_expected_keys() {
@@ -969,6 +990,7 @@ mod tests {
969990
"contextMediumThreshold",
970991
"costSource",
971992
"debug",
993+
"modelLabelAliases",
972994
"noCache",
973995
"noOffline",
974996
"offline",
@@ -1248,6 +1270,39 @@ mod tests {
12481270
);
12491271
}
12501272

1273+
#[test]
1274+
fn statusline_options_parse_model_label_aliases() {
1275+
let map = serde_json::json!({
1276+
"modelLabelAliases": {
1277+
"arn:aws:bedrock:ap-northeast-1:012345678910:application-inference-profile/abcde12345": "claude-opus-4-6"
1278+
}
1279+
});
1280+
let options = StatuslineSpecificOptions::from_map(map.as_object().unwrap());
1281+
1282+
let aliases = options.model_label_aliases.unwrap();
1283+
assert_eq!(
1284+
aliases.get(
1285+
"arn:aws:bedrock:ap-northeast-1:012345678910:application-inference-profile/abcde12345"
1286+
),
1287+
Some(&"claude-opus-4-6".to_string())
1288+
);
1289+
}
1290+
1291+
#[test]
1292+
fn statusline_options_ignore_non_string_alias_values() {
1293+
let map = serde_json::json!({
1294+
"modelLabelAliases": {
1295+
"valid": "short",
1296+
"invalid": 123
1297+
}
1298+
});
1299+
let options = StatuslineSpecificOptions::from_map(map.as_object().unwrap());
1300+
1301+
let aliases = options.model_label_aliases.unwrap();
1302+
assert_eq!(aliases.get("valid"), Some(&"short".to_string()));
1303+
assert!(!aliases.contains_key("invalid"));
1304+
}
1305+
12511306
#[test]
12521307
fn snapshots_schema_agent_specific_option_edges() {
12531308
if running_in_schema_generator_test_binary() {

rust/crates/ccusage/src/snapshots/ccusage__config_schema__tests__snapshots_schema_agent_specific_option_edges.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ expression: "json!({\n \"rootRef\": schema[\"$ref\"], \"rootProperties\":\n
4747
"markdownDescription": "Show statusline debug information.",
4848
"type": "boolean"
4949
},
50+
"modelLabelAliases": {
51+
"additionalProperties": {
52+
"type": "string"
53+
},
54+
"description": "Map model identifiers to short display labels.",
55+
"markdownDescription": "Map model identifiers to short display labels.",
56+
"type": "object"
57+
},
5058
"noCache": {
5159
"default": false,
5260
"description": "Disable statusline cache.",

0 commit comments

Comments
 (0)