LiteLLM 用量接入实战:CodexBar 虚拟密钥方案的数据源、配置与安全边界
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
导读
本文以 CodexBar 仓库中的 LiteLLM 接入文档为主线,系统讲解如何为 LiteLLM 代理网关配置用量统计:从虚拟密钥(virtual key)加代理 Base URL 的认证模型,到/key/info、/user/info、/team/info三类管理端点的数据读取链路,再到 HTTPS 校验、密钥存储等安全设计。读完本文,你将掌握在 CodexBar 中为 LiteLLM 配置 API Key 与 Base URL 的完整步骤、理解个人预算与团队预算如何映射为菜单栏指标,并能依据源码与测试定位常见配置与权限问题。
LiteLLM 在 CodexBar 中的定位
LiteLLM 是一款流行的 LLM 代理网关,通过虚拟密钥(virtual key)对外统一暴露多个上游模型,同时集中管理每个密钥对应的预算与消费。CodexBar 将 LiteLLM 作为一个可选的用量统计 Provider,其核心思路是:不依赖主密钥(master key),而是让虚拟密钥通过 LiteLLM 自带的管理端点读取自身的身份、花费与预算数据。这一点在文档中被明确表述为:
LiteLLM uses a virtual key plus the proxy base URL. The key reads its own identity and budget data through LiteLLM's authenticated information endpoints.
从源码角度看,该 Provider 由三部分组成(Sources/CodexBarCore/Providers/LiteLLM/):
LiteLLMProviderDescriptor.swift:Provider 的注册描述、凭据适配、取数策略与菜单栏展示规则;LiteLLMSettingsReader.swift:读取并清洗LITELLM_API_KEY与LITELLM_BASE_URL两个配置源;LiteLLMUsageFetcher.swift:实现三类管理端点的请求、解析与快照转换,是数据链路的执行核心。
此外,Sources/CodexBar/Providers/LiteLLM/LiteLLMProviderImplementation.swift 负责在应用侧呈现设置表单字段并监听配置变化。
配置方式:图形界面与配置文件双通道
CodexBar 支持两种等价配置途径。
方式一:图形界面
在Settings -> Providers -> LiteLLM中填写两个字段(对应源码中的settingsFields实现):
| 字段 | 说明 | 占位示例 |
|---|---|---|
| API key | LiteLLM 虚拟密钥,用于读取该密钥自身的花费与预算 | sk-… |
| Base URL | LiteLLM 代理 Base URL;允许带/v1后缀,管理端点会自动剥离 | https://litellm.example.com |
方式二:~/.codexbar/config.json
{ "id": "litellm", "enabled": true, "apiKey": "<LITELLM_API_KEY>", "enterpriseHost": "https://litellm.example.com" }方式三:环境变量
export LITELLM_API_KEY=sk-... export LITELLM_BASE_URL=https://litellm.example.com两个环境变量的键名在源码中硬编码于 LiteLLMSettingsReader.swift:
public static let apiKeyEnvironmentKey = "LITELLM_API_KEY" public static let baseURLEnvironmentKey = "LITELLM_BASE_URL"值得注意的细节:环境变量的读取会先做空白与引号清理——源码中的cleaned(_:)会trimmingCharacters去掉首尾空白,并且如果值被成对的双引号或单引号包裹,也会一并剥离(对应测试settings reader trims quoted environment values,见 Tests/CodexBarTests/LiteLLMUsageFetcherTests.swift)。因此LITELLM_API_KEY='sk-test'这类带引号的写法也能被正确解析。
另外,CodexBar 还支持在 token-account 存储中保存多把 LiteLLM API Key(描述器中的TokenAccountSupport注明 "Store multiple LiteLLM API keys."),通过环境注入方式把选定密钥传给取数流程。
Base URL 的/v1处理与校验规则
/v1后缀自动剥离
LiteLLM 的代理地址通常形如https://host/v1,而管理端点(/key/info等)并不在/v1前缀之下。CodexBar 在构造管理端点 URL 时会对 Base URL 做归一化:managementBaseURL(_:)会检查路径的最后一段是否为v1,若是则移除该段后再拼接管理路径(LiteLLMUsageFetcher.swift)。
该行为有测试直接验证(management urls accept root or v1 base urls):
https://litellm.example.com→https://litellm.example.com/key/infohttps://litellm.example.com/v1→https://litellm.example.com/key/infohttps://gateway.example.com/litellm/v1/→https://gateway.example.com/litellm/user/info?user_id=user-123(嵌套路径同样支持,只剥离最后一段v1)
协议与凭证校验
文档明确:Base URL 必须满足以下条件,否则被视为非法并被拒绝取数:
- 必须使用 HTTPS,除非该地址指向回环地址(loopback)、私网地址(RFC 1918)、链路本地地址(link-local)或 IPv6 唯一本地地址(unique-local),或者是一个
.localmDNS 主机名; - 不得内嵌凭证(如
https://user:pass@host),因为 API Key 会以 Bearer token 形式发送到该地址; - 纯 HTTP 仅对自建代理可用,且限回环、RFC 1918、链路本地、IPv6 唯一本地网络。
这些规则由ProviderEndpointOverrideValidator().validatedURLAllowingPrivateNetworkHTTP(raw)统一实施(LiteLLMSettingsReader.swift),与其他 Provider 的端点覆盖校验共用同一套逻辑。
值得一提的设计是:当 Base URL 配置了但未通过校验时,CodexBar 会明确抛出LiteLLMUsageError.invalidEndpointOverride(LITELLM_BASE_URL),并提示使用 HTTPS 或仅对回环/私网地址使用 HTTP、且不得内嵌凭证(见 LiteLLMUsageError)。为了做到这一点,hasBaseURLOverride(environment:)专门区分"从未配置"与"已配置但被拒绝"两种状态,取数策略据此决定抛invalidEndpointOverride还是missingBaseURL(LiteLLMProviderDescriptor.swift),避免 Provider 静默不可用让用户无从排查。
此外文档还提到一条约束:原生取数器(native fetcher)仍是权威路径;配置的插件源(plugin origins)覆盖 HTTPS 与回环 HTTP,但在没有更宽泛的主机网络策略时,并不覆盖既有的私网与.localHTTP 契约。即插件化替代方案目前不能完全替代原生取数器对私网 HTTP 场景的支持。
数据源与取数流程
三阶段请求链路
文档给出的调用序列,与LiteLLMUsageFetcher.fetchUsage的实现完全一致(LiteLLMUsageFetcher.swift):
GET /key/info:读取当前虚拟密钥自身的user_id与team_id。注意源码注释明确指出:"Virtual keys may read their own metadata; omit?key=to avoid requiring or exposing a master key."——即请求不带?key=参数,从而避免暴露主密钥。GET /user/info?user_id=<user_id>:当密钥绑定了用户时,读取个人花费、预算与团队列表。GET /team/info?team_id=<team_id>:当密钥是"仅团队"密钥(无user_id只有team_id)时,读取团队花费与预算。
所有请求均为GET,请求头固定为:
Authorization: Bearer <apiKey> Accept: application/json源码中request(url:apiKey:)的实现(LiteLLMUsageFetcher.swift)还确认:CodexBar 从不请求也不存储 LiteLLM master key,仅使用虚拟密钥本身的 Bearer 认证。发送前还会对 API Key 做空白清理(fetchUsage中先trimmingCharacters再校验非空)。
响应字段映射
/key/info的响应解析为LiteLLMKeyInfoSnapshot,关注字段:
| JSON 字段 | 快照属性 | 说明 |
|---|---|---|
info.key_name | keyName | 密钥名称(脱敏展示用) |
info.spend | spendUSD | 该密钥累计花费(USD) |
info.expires | expiresAt | 密钥过期时间 |
info.user_id | userID | 绑定的用户 ID |
info.team_id | teamID | 绑定的团队 ID |
/user/info的响应解析关注user_info下的user_email、user_alias、max_budget、spend、budget_reset_at以及metadata.preferred_username,账户邮箱会按"邮箱 > 别名 > preferred_username"的优先级取第一个非空值(firstNonEmpty)。团队信息则从teams数组中按team_id精确匹配/key/info中声明的团队(preferredTeam)。
/team/info的响应解析关注team_info下的team_alias、team_id、max_budget、spend、budget_reset_at、budget_duration。
身份一致性校验
CodexBar 会把/key/info返回的user_id/team_id与后续/user/info、/team/info响应中的 ID 做交叉比对,不一致时直接抛parseFailed("user_id did not match /key/info")或parseFailed("team_id did not match /key/info")(LiteLLMUsageFetcher.swift)。这一设计保证展示的预算确实属于当前密钥,防止越权或串号数据被误展示。
用量窗口的映射逻辑
个人密钥:个人窗口为主、团队窗口为辅
对于绑定了用户的密钥:
- 主窗口(primary):个人花费与个人预算(
user_info.max_budget); - 次窗口(secondary):与
/key/info团队 ID精确匹配的团队预算; - 菜单栏自动指标:由于团队预算是该密钥实际被执行的约束,自动模式(
.automatic)下菜单栏优先展示团队预算窗口。
这一逻辑在描述器的menuBarWindowResolver中实现:自动模式下依次选择exhausted(primary, secondary) ?? secondary ?? primary(LiteLLMProviderDescriptor.swift),即优先展示已耗尽的窗口,否则优先团队窗口。
仅团队密钥:团队预算为唯一窗口
当/key/info没有user_id只有team_id时,CodexBar 直接走/team/info分支,个人窗口为空,团队预算成为唯一用量窗口(providerCostSnapshot中period显示为 "Team budget")。
无预算配置:保留花费可见性
当 LiteLLM 没有为该用户或团队配置预算(max_budget为空或为 0)时,CodexBar 仍以"API 花费行"(API-spend row)展示实际花费,而不是隐藏整个 Provider。这在providerCostSnapshot()中体现为limit: 0、period显示为 "Personal spend" 或 "Team spend"(LiteLLMUsageFetcher.swift)。对应测试preserves personal spend when no budget is configured验证了used == 12.5, limit == 0的行为。
快照中的其他信息
LiteLLMUsageSnapshot.toUsageSnapshot()还会填充:
subscriptionExpiresAt← 密钥过期时间(info.expires);identity.accountEmail← 解析出的邮箱/别名;identity.accountOrganization← 团队别名(team_alias);identity.loginMethod←"api"。
菜单卡片中,ProviderCostPresentation依据limit <= 0选择apiSpend样式,否则为hidden(即预算场景下花费并入主/次窗口展示,不重复显示花费行)。
权限要求与错误排查
虚拟密钥所需的最小权限
要让 CodexBar 正确读取用量,虚拟密钥必须被允许访问:
- 自身的
/key/info数据; - 对应
user_id的/user/info数据(用户绑定的密钥); - 或对应
team_id的/team/info数据(仅团队密钥)。
任何一端返回 401/403 都会导致取数失败。测试fetch surfaces rejected virtual key验证了 401 响应会抛出LiteLLMUsageError.apiError("HTTP 401: ..."),且只发起一次请求(Tests/CodexBarTests/LiteLLMUsageFetcherTests.swift)。
常见错误与对应提示
| 场景 | 错误类型 | 提示文案 |
|---|---|---|
| 未配置 API Key | missingCredentials | Missing LiteLLM API key. Set apiKey in~/.codexbar/config.jsonorLITELLM_API_KEY. |
| 未配置 Base URL | missingBaseURL | Missing LiteLLM base URL. Set enterpriseHost in~/.codexbar/config.jsonorLITELLM_BASE_URL. |
| Base URL 非法(HTTP 公网 / 内嵌凭证) | invalidEndpointOverride | 提示改用 HTTPS,或仅对回环/私网地址与.local主机使用 HTTP,且不得内嵌凭证 |
/key/info无user_id与team_id | missingUserID | LiteLLM key info did not include a user_id or team_id. |
| HTTP 非 2xx | apiError | LiteLLM API error: HTTP <status>: <响应摘要前 500 字节> |
安全边界
文档的 Security 一节虽然简短,但结合源码可以确认以下事实:
- 密钥按秘密对待:LiteLLM 密钥只存于 Provider 配置或 token-account 存储(后者支持多密钥管理),不落盘到其他位置;
- 密钥只发送给配置的 Base URL:所有请求统一走
Authorization: Bearer头,且 URL 校验禁止内嵌凭证,避免密钥被当作 URL 的一部分泄露到日志或 Referer; - 不使用主密钥:
/key/info请求有意省略?key=参数,既满足虚拟密钥读取自身信息的需要,又从设计上杜绝了主密钥的获取与存储; - 私网 HTTP 白名单:自建代理在回环、RFC 1918、链路本地与 IPv6 唯一本地网络可继续使用明文 HTTP,兼顾本地部署的便利性与公网传输的保密性。
小结
CodexBar 的 LiteLLM 接入以"虚拟密钥自读信息"为设计主线:一份 Base URL 加一把虚拟密钥,即可通过三个管理端点获得个人/团队的花费与预算,并自动映射为菜单栏主次窗口与预算/花费两种展示形态。配置上支持图形界面、~/.codexbar/config.json与环境变量三种途径;安全上通过 URL 校验、禁止内嵌凭证、不使用主密钥以及 ID 交叉比对,把密钥暴露面控制在最小。若需继续深入,建议阅读 LiteLLMUsageFetcher.swift 的完整解析逻辑与 Tests/CodexBarTests/LiteLLMUsageFetcherTests.swift 的六组测试用例,它们覆盖了个人/团队密钥、无预算场景、/v1归一化、引号清理、401 拒绝等全部关键路径。
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考