OAuth token management for LLM providers (Claude and OpenAI). Handles loading, refreshing, and persisting OAuth tokens so that tcode can authenticate against provider APIs using subscription credentials.
The crate is split into a shared generic core and provider-specific submodules:
auth/src/
lib.rs # OAuthTokens, TokenRefresher trait, BaseTokenManager<R>, OAuthTokenManager trait
claude/
mod.rs # ClaudeRefresher, TokenManager type alias, load_token_manager()
usage.rs # Claude subscription usage (rate-limit windows)
openai/
mod.rs # OpenAiRefresher, TokenManager type alias, load_token_manager()
usage.rs # OpenAI subscription usage
Serializable struct holding access_token, refresh_token, expires_at (unix timestamp), and an optional account_id (used by OpenAI). Knows whether it is expired or about to expire (within a 5-minute buffer).
Provider-specific token refresh logic. Each provider implements a single async method:
async fn refresh(&self, client: &reqwest::Client, refresh_token: &str) -> Result<OAuthTokens>;Implementations: ClaudeRefresher (calls the Anthropic token endpoint) and OpenAiRefresher (calls the OpenAI token endpoint).
Generic, thread-safe (Arc<RwLock<OAuthTokens>>) token manager parameterized over a TokenRefresher. Handles:
- Loading tokens from a JSON file on disk
- Persisting tokens with 0600 permissions
- Double-checked-locking refresh (avoids redundant concurrent refreshes)
- Implements
llm_rs::llm::TokenProvider, so it plugs directly into LLM backends
Each provider module exposes a TokenManager type alias (e.g., claude::TokenManager = BaseTokenManager<ClaudeRefresher>).
Extends TokenProvider with HTTP client access and formatted usage fetching. Implemented by both provider TokenManager types so the server can treat them uniformly.
Token files are profile-aware and mirror tcode's config profile naming:
| Provider | No Profile | Profile work |
|---|---|---|
| Claude | ~/.tcode/auth/claude_tokens.json |
~/.tcode/auth/claude_tokens-work.json |
| OpenAI | ~/.tcode/auth/openai_tokens.json |
~/.tcode/auth/openai_tokens-work.json |
TokenManager::storage_path(profile) is the source of truth for this naming. TokenManager::load(profile) loads only the resolved file for that profile and does not fall back to the unsuffixed default file.
- Initial login.
tcode claude-authortcode openai-authruns the PKCE OAuth flow, exchanges the authorization code for tokens, and saves them via the provider'sTokenManager. Withtcode -p <profile> ..., the same auth commands save to the matching profile-specific token file. - Startup. tcode calls the provider's
TokenManager::load(profile)to load persisted tokens from disk for the active profile. - Runtime. The
TokenManageris passed to the LLM backend as aTokenProvider. Each API request callsget_access_token(), which transparently refreshes if needed. - Usage display. The server periodically calls the provider's usage module to fetch rate-limit status for the TUI status bar.