CodexBar Provider System Architecture: A Deep Dive into the Multi-Layer Design
CodexBar uses a five-layer provider architecture that separates identity (UsageProvider enum), static configuration (ProviderDescriptor), runtime behavior (ProviderImplementation), data fetching strategies (ProviderFetchPlan), and UI lifecycle management (ProviderRuntime) to abstract OpenAI, Claude, Gemini, and other AI services into a unified system.
The steipete/CodexBar repository implements a macOS menu bar application for monitoring AI API usage across multiple services. Understanding the CodexBar provider system architecture reveals how the app cleanly separates concerns between data models, business logic, and UI presentation. This layered design enables rapid addition of new AI providers while maintaining type safety through Swift's concurrency features and immutable state management.
The Five-Layer Architecture Overview
CodexBar treats every AI service—OpenAI, Claude, Gemini, Cursor, and others—as a provider. The architecture divides provider logic into five distinct layers:
- Identity – Stable enumeration for persistence (
UsageProvider) - Descriptor & Registration – Static metadata and global registry (
ProviderDescriptor,ProviderDescriptorRegistry) - Implementation – Runtime behavior contract (
ProviderImplementation) - Fetching – Data acquisition strategies (
ProviderFetchPlan,ProviderFetchStrategy) - Runtime – UI-side lifecycle management (
ProviderRuntime)
Each layer corresponds to specific source files and protocols, ensuring that static data, network logic, and UI presentation remain decoupled.
Layer 1: Provider Identity via UsageProvider
The UsageProvider enum in Sources/CodexBarCore/Providers/Providers.swift defines the canonical list of supported services. It uses a raw String representation to ensure values survive across app launches and can be persisted in WidgetSnapshot.
public enum UsageProvider: String, CaseIterable, Sendable, Codable {
case codex, openai, azureopenai, claude, cursor, gemini, clawrouter
// ... additional cases
}
Each case acts as a stable identifier. When the app stores user preferences (e.g., selected provider), it saves the raw string value, not the descriptor or implementation instance.
Layer 2: Descriptor and Registration
The ProviderDescriptor struct in Sources/CodexBarCore/Providers/ProviderDescriptor.swift aggregates all static information required by the app: metadata, branding, token-cost configuration, fetch plans, and CLI settings.
public struct ProviderDescriptor: Sendable {
public let id: UsageProvider
public let metadata: ProviderMetadata
public let branding: ProviderBranding
public let tokenCost: ProviderTokenCostConfig
public let fetchPlan: ProviderFetchPlan
public let cli: ProviderCLIConfig
}
Descriptors are immutable value types created once per provider. The ProviderDescriptorRegistry manages these instances as a thread-safe singleton. On first access (ensureBootstrapped), the registry iterates over UsageProvider.allCases and populates a dictionary keyed by the enum:
private static let descriptorsByID: [UsageProvider: ProviderDescriptor] = [
.codex: CodexProviderDescriptor.descriptor,
.openai: OpenAIAPIProviderDescriptor.descriptor,
// ... additional mappings
]
This design enables fast lookup via ProviderDescriptorRegistry.descriptor(for:) and maintains a reverse CLI name map (cliNameMap) for command-line alias resolution.
Layer 3: Provider Implementation Protocol
Concrete providers implement the ProviderImplementation protocol defined in Sources/CodexBar/Providers/Shared/ProviderImplementation.swift. This protocol defines the contract between the UI layer and provider logic:
presentation(context:)– ReturnsProviderPresentationwith detail lines and iconsisAvailable(context:)– Determines if the provider can be used (e.g., token present, binaries installed)sourceMode(context:)– Declares preferred fetch mode (auto,web,cli)makeRuntime()– Returns an optionalProviderRuntimefor UI-side actionsrunLoginFlow(context:)– Executes provider-specific authentication
Default implementations are no-ops, allowing concrete providers to override only necessary methods. For example, OpenAIAPIProviderImplementation and ClaudeProviderImplementation provide specific overrides for credential detection and login flows.
Layer 4: Fetching Pipeline
Data gathering is abstracted through ProviderFetchPlan and ProviderFetchStrategy in Sources/CodexBarCore/Providers/ProviderFetchPlan.swift. A fetch plan is a composite of one or more strategies:
public protocol ProviderFetchStrategy: Sendable {
var id: String { get }
var kind: ProviderFetchKind { get }
func isAvailable(_ context: ProviderFetchContext) async -> Bool
func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult
func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool
}
The ProviderFetchContext bundles runtime dependencies: environment variables, settings snapshots, fetcher objects, and token updaters. During a refresh cycle (descriptor.fetchOutcome(context:)), the plan:
- Checks each strategy's
isAvailableasynchronously - Executes the first available strategy
- Falls back to subsequent strategies if
shouldFallbackreturns true - Returns a
ProviderFetchOutcomecontaining the result and diagnostic attempts
This strategy pattern allows a single provider to try multiple data sources—web scraping, CLI tools (like Antigravity), or OAuth APIs—without hardcoding fallback logic in the UI layer.
Layer 5: Runtime and UI Integration
The ProviderRuntime protocol in Sources/CodexBar/Providers/Shared/ProviderRuntime.swift operates on the main actor and handles UI-specific concerns. Instantiated via ProviderImplementation.makeRuntime(), it receives a ProviderRuntimeContext containing the provider enum, global SettingsStore, and UsageStore.
Runtime implementations manage:
- Background refresh scheduling
- Menu bar status updates
- Settings change observation
- Action handlers (
forceSessionRefresh,openAIWebAccessToggled)
This separation keeps UI logic (menus, progress bars) distinct from data-fetching logic (ProviderFetchStrategy), ensuring that long-running network operations never block the main thread.
End-to-End Data Flow
The CodexBar provider system architecture orchestrates the following flow during normal operation:
- Bootstrap –
ProviderDescriptorRegistryinitializes on first access, creating immutable descriptors for allUsageProvidercases - Menu Construction –
StatusItemControllerqueries descriptors for branding and costs, then callsProviderImplementation.presentationfor each enabled provider - Refresh Trigger – The controller creates a
ProviderFetchContextand callsdescriptor.fetchOutcome(context:) - Strategy Execution – The
ProviderFetchPlanexecutes available strategies (web scrapers or CLI helpers) until one succeeds - State Update – Fetched
UsageSnapshotandCreditsSnapshotobjects are stored inUsageStore - UI Update –
ProviderRuntimecallbacks trigger menu bar refreshes and visual updates
All moving parts remain stateless except for UsageStore and SettingsStore, making unit testing straightforward through mocked contexts.
Adding a New Provider to CodexBar
Extending the CodexBar provider system architecture requires changes across three layers:
// Step 1: Extend UsageProvider in Sources/CodexBarCore/Providers/Providers.swift
public enum UsageProvider: String, CaseIterable, Sendable, Codable {
case codex, openai, // ... existing cases
case myai // New provider
}
// Step 2: Create descriptor in MyAIProviderDescriptor.swift
public struct MyAIProviderDescriptor {
public static let descriptor: ProviderDescriptor = {
let metadata = ProviderMetadata(
id: .myai,
displayName: "MyAI",
sessionLabel: "Session",
weeklyLabel: "Weekly",
supportsCredits: true,
cliName: "myai",
defaultEnabled: false
)
let fetchPlan = ProviderFetchPlan(strategies: [
MyAIWebStrategy(),
MyAICLIStrategy()
])
return ProviderDescriptor(
id: .myai,
metadata: metadata,
branding: ProviderBranding(color: .purple, iconName: "myai"),
tokenCost: ProviderTokenCostConfig(supportsTokenCost: false),
fetchPlan: fetchPlan,
cli: ProviderCLIConfig(name: "myai", versionDetector: nil, aliases: [])
)
}()
}
// Step 3: Register in ProviderDescriptorRegistry
private static let descriptorsByID: [UsageProvider: ProviderDescriptor] = [
// ... existing entries
.myai: MyAIProviderDescriptor.descriptor
]
Finally, implement ProviderImplementation and optionally ProviderRuntime if the provider requires custom UI handling or login flows.
Summary
- CodexBar provider system architecture uses five distinct layers—Identity, Descriptor, Implementation, Fetching, and Runtime—to separate static data from runtime behavior
- UsageProvider provides stable string-based identifiers for persistence in
Sources/CodexBarCore/Providers/Providers.swift - ProviderDescriptorRegistry offers thread-safe, global access to immutable provider metadata in
Sources/CodexBarCore/Providers/ProviderDescriptor.swift - ProviderImplementation defines the behavior contract for UI presentation and availability checks in
Sources/CodexBar/Providers/Shared/ProviderImplementation.swift - ProviderFetchStrategy enables composable data acquisition with automatic fallback support in
Sources/CodexBarCore/Providers/ProviderFetchPlan.swift - ProviderRuntime isolates main-actor UI logic from async data fetching in
Sources/CodexBar/Providers/Shared/ProviderRuntime.swift
Frequently Asked Questions
What is the difference between ProviderDescriptor and ProviderImplementation in CodexBar?
ProviderDescriptor is a value type containing static configuration (names, colors, fetch plans) that never changes during runtime. ProviderImplementation is a protocol defining dynamic behavior—such as login flows, availability checks, and UI presentation logic—that varies based on system state and user settings. The descriptor answers "what is this provider," while the implementation answers "how does it behave right now."
How does CodexBar handle different data sources for the same provider?
Each ProviderDescriptor contains a ProviderFetchPlan composed of multiple ProviderFetchStrategy objects. When refreshing data, the plan iterates through strategies—such as web scraping, CLI execution, or API calls—checking isAvailable for each. If a strategy fails and returns true from shouldFallback, the plan automatically tries the next strategy, allowing seamless fallback from web UI scraping to CLI tools without UI-layer modifications.
Is the ProviderDescriptorRegistry thread-safe?
Yes. The registry implements a thread-safe singleton pattern using a store dictionary protected by a lock (likely NSLock or actor isolation). The ensureBootstrapped method safely initializes descriptors on first access across multiple threads, and all subsequent lookups through descriptor(for:) are read-only operations on the immutable descriptor copies.
How do I add authentication to a custom CodexBar provider?
Implement the ProviderImplementation protocol's runLoginFlow(context:) method in your concrete implementation. This method receives a ProviderContext containing access to settings stores and can present custom UI or handle OAuth flows. Return true if the login requires an immediate refresh of usage data. If your provider needs persistent background tasks or menu bar interactions, also implement makeRuntime() to return a custom ProviderRuntime instance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →