use costroid_core::{AuthMethod, DataSource, ProviderCapabilityView, ProviderStatus};
use crate::app::{color_of, ASH, BONE};
use crate::format::{kind_label, provider_label, provider_state_word};
pub fn draw(
ui: &mut egui::Ui,
capabilities: &[ProviderCapabilityView],
statuses: &[ProviderStatus],
) {
ui.horizontal(|ui| {
ui.add_space(8.0);
ui.label(
egui::RichText::new("providers")
.monospace()
.strong()
.color(color_of(BONE)),
);
});
if capabilities.is_empty() {
text_line(ui, "no providers to describe", ASH);
return;
}
for capability in capabilities {
let status = find_status(statuses, capability);
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.add_space(8.0);
ui.label(
egui::RichText::new(provider_label(capability.provider))
.monospace()
.strong()
.color(color_of(BONE)),
);
ui.label(
egui::RichText::new(format!(" ({})", provider_state_word(status)))
.monospace()
.color(color_of(ASH)),
);
});
text_line(
ui,
&format!(" api cost {}", data_source_phrase(capability.api_cost)),
ASH,
);
text_line(
ui,
&format!(" quota {}", quota_phrase(capability)),
ASH,
);
text_line(
ui,
&format!(" model mix {}", data_source_phrase(capability.model_mix)),
ASH,
);
text_line(
ui,
&format!(" auth {}", auth_phrase(capability.auth)),
ASH,
);
if let Some(note) = status.and_then(|status| status.message.as_deref()) {
text_line(ui, &format!(" note: {}", sanitize(note)), ASH);
}
}
}
fn find_status<'a>(
statuses: &'a [ProviderStatus],
capability: &ProviderCapabilityView,
) -> Option<&'a ProviderStatus> {
statuses
.iter()
.find(|status| status.provider == capability.provider)
}
fn data_source_phrase(source: DataSource) -> &'static str {
match source {
DataSource::LocalArtifact => "from local logs",
DataSource::SanctionedHook => "from the statusLine capture; run setup-statusline",
DataSource::SanctionedOauth | DataSource::ApiKey => "via your connected key",
DataSource::Unavailable => "no sanctioned source",
}
}
fn auth_phrase(auth: AuthMethod) -> &'static str {
match auth {
AuthMethod::None => "no login required",
AuthMethod::Oauth => "sanctioned OAuth",
AuthMethod::ApiKey => "your own API key",
}
}
fn quota_phrase(capability: &ProviderCapabilityView) -> String {
let source = data_source_phrase(capability.subscription_quota);
if capability.quota_kinds.is_empty() {
source.to_string()
} else {
let kinds = capability
.quota_kinds
.iter()
.map(|kind| kind_label(*kind))
.collect::<Vec<_>>()
.join(", ");
format!("{source} ({kinds})")
}
}
fn sanitize(value: &str) -> String {
value.chars().filter(|ch| !ch.is_control()).collect()
}
fn text_line(ui: &mut egui::Ui, text: &str, ink: [u8; 4]) {
ui.horizontal(|ui| {
ui.add_space(8.0);
ui.label(egui::RichText::new(text).monospace().color(color_of(ink)));
});
}
#[cfg(feature = "connect")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConnectionEntry {
pub vendor: String,
pub state: ConnectionState,
}
#[cfg(feature = "connect")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionState {
Connected {
org: Option<String>,
},
NotConnected,
Unavailable(String),
}
#[cfg(feature = "connect")]
pub fn gather_connections() -> Vec<ConnectionEntry> {
use costroid_connect::{ConnectionRegistry, CredentialStore};
let store = match CredentialStore::new() {
Ok(store) => store,
Err(_) => return Vec::new(),
};
let registry = match ConnectionRegistry::open() {
Ok(registry) => registry,
Err(_) => return Vec::new(),
};
connection_entries(&store, ®istry)
}
#[cfg(feature = "connect")]
fn connection_entries(
store: &costroid_connect::CredentialStore,
registry: &costroid_connect::ConnectionRegistry,
) -> Vec<ConnectionEntry> {
use costroid_connect::ApiVendor;
let mut entries = Vec::new();
for vendor in ApiVendor::ALL {
let state = match vendor {
ApiVendor::Gemini => {
ConnectionState::Unavailable(costroid_core::GEMINI_UNAVAILABLE_MESSAGE.to_string())
}
ApiVendor::Anthropic | ApiVendor::OpenAI => {
let connected = registry.is_connected(vendor).unwrap_or(false)
&& store
.retrieve(vendor)
.map(|key| key.is_some())
.unwrap_or(false);
if connected {
let org = registry.label(vendor).ok().flatten().map(format_org_label);
ConnectionState::Connected { org }
} else {
ConnectionState::NotConnected
}
}
};
entries.push(ConnectionEntry {
vendor: vendor.to_string(),
state,
});
}
entries
}
#[cfg(feature = "connect")]
fn format_org_label(label: costroid_connect::OrgLabel) -> String {
let name = sanitize(&label.name);
match label.id {
Some(id) => format!("{name} ({})", sanitize(&id)),
None => name,
}
}
#[cfg(feature = "connect")]
pub fn draw_connection_lane(ui: &mut egui::Ui, connections: &[ConnectionEntry]) {
if connections.is_empty() {
return;
}
ui.add_space(4.0);
text_line(ui, "connections (your own usage API keys)", ASH);
for entry in connections {
let detail = match &entry.state {
ConnectionState::Connected { org: Some(org) } => {
format!("connected - organization {org}")
}
ConnectionState::Connected { org: None } => "connected".to_string(),
ConnectionState::NotConnected => "not connected".to_string(),
ConnectionState::Unavailable(message) => sanitize(message),
};
text_line(ui, &format!(" {:<10} {detail}", entry.vendor), ASH);
}
}
#[cfg(test)]
mod tests {
use super::*;
use costroid_core::{AuthMethod, DataSource, LimitKind, ProviderId, ProviderStatusKind};
fn capability(provider: ProviderId) -> ProviderCapabilityView {
ProviderCapabilityView {
provider,
api_cost: DataSource::LocalArtifact,
subscription_quota: DataSource::SanctionedHook,
model_mix: DataSource::LocalArtifact,
auth: AuthMethod::None,
quota_kinds: vec![LimitKind::FiveHour, LimitKind::Weekly],
}
}
fn unavailable_capability(provider: ProviderId) -> ProviderCapabilityView {
ProviderCapabilityView {
provider,
api_cost: DataSource::Unavailable,
subscription_quota: DataSource::Unavailable,
model_mix: DataSource::LocalArtifact,
auth: AuthMethod::None,
quota_kinds: Vec::new(),
}
}
fn status(
provider: ProviderId,
kind: ProviderStatusKind,
message: Option<&str>,
) -> ProviderStatus {
ProviderStatus {
provider,
status: kind,
files: 0,
usage_events: 0,
focus_rows: 0,
limit_windows: 0,
message: message.map(str::to_string),
}
}
#[test]
fn lane_phrases_are_honest_and_ascii() {
assert_eq!(
data_source_phrase(DataSource::Unavailable),
"no sanctioned source"
);
assert_eq!(
data_source_phrase(DataSource::LocalArtifact),
"from local logs"
);
assert_eq!(auth_phrase(AuthMethod::ApiKey), "your own API key");
assert_eq!(
quota_phrase(&unavailable_capability(ProviderId::Cursor)),
"no sanctioned source"
);
assert!(quota_phrase(&capability(ProviderId::ClaudeCode)).contains("(5h, wk)"));
}
#[test]
fn sanitize_strips_control_chars() {
assert_eq!(sanitize("ok\u{0007}\nname"), "okname");
}
#[test]
fn headless_draw_covers_empty_and_populated() {
let ctx = egui::Context::default();
crate::fonts::install(&ctx);
let caps = vec![
capability(ProviderId::ClaudeCode),
unavailable_capability(ProviderId::Cursor),
];
let statuses = vec![
status(ProviderId::ClaudeCode, ProviderStatusKind::Available, None),
status(
ProviderId::Cursor,
ProviderStatusKind::Detected,
Some("BETA - usage unavailable - no sanctioned source"),
),
];
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
draw(ui, &caps, &statuses);
});
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
draw(ui, &[], &[]);
});
}
#[cfg(feature = "connect")]
#[test]
fn connection_lane_draws_each_state() {
let ctx = egui::Context::default();
crate::fonts::install(&ctx);
let entries = vec![
ConnectionEntry {
vendor: "anthropic".to_string(),
state: ConnectionState::Connected {
org: Some("Acme (org-123)".to_string()),
},
},
ConnectionEntry {
vendor: "openai".to_string(),
state: ConnectionState::NotConnected,
},
ConnectionEntry {
vendor: "gemini".to_string(),
state: ConnectionState::Unavailable("unavailable - no sanctioned API".to_string()),
},
];
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
draw_connection_lane(ui, &entries);
draw_connection_lane(ui, &[]); });
}
}