CodexBar 中 Vertex AI Provider 的实现剖析:gcloud ADC 凭证、Cloud Monitoring 配额查询与 Claude 日志成本识别
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
CodexBar 的 Vertex AI Provider 通过 gcloud Application Default Credentials(ADC)完成免登录认证,调用 Cloud Monitoring timeSeries API 计算配额使用率,并借助本地 Claude Code 日志(~/.claude/projects/)中的vrtx标识与@格式模型名识别 Vertex AI 的 token 成本。读完本文,你能完整理解从凭证加载、token 刷新、监控指标过滤到配额序列匹配与成本归类的整条数据链路,并能独立排查「无配额数据」「无成本数据」「认证失败」三类典型问题。
数据源与取数路径
Vertex AI 的用量获取只有一条路径:OAuth via gcloud ADC,即从 gcloud 配置目录读取application_default_credentials.json,再基于 Cloud Monitoring 的 time-series 指标计算配额用量。这一点在 Provider 描述文件中可以直接印证——取数管线只注册了一个策略:
fetchPlan: ProviderFetchPlan( sourceModes: [.auto, .oauth], pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [VertexAIOAuthFetchStrategy()] })),见 VertexAIProviderDescriptor.swift。由于不存在第二个策略,该 Provider 的usesAccountFallback也为false,认证失败时不会走任何账号回退逻辑,只能依靠策略自身的shouldFallback判定(凭证缺失、401/403 时视为可回退,即直接标记为不可用)。
OAuth 凭证:ADC 文件的定位与解析
认证命令
按 docs/vertexai.md 的说明,前置条件只有两条命令:
# 1) 完成 OAuth 登录,生成 application_default_credentials.json gcloud auth application-default login # 2) 指定默认项目 gcloud config set project PROJECT_IDCodexBar 内置的登录流程会弹出一个提示窗口,点击 "Open Terminal" 后实际执行的是带完整 scope 的版本(见 VertexAILoginFlow.swift):
gcloud auth application-default login \ --scopes=openid,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform登录窗口关闭后,StatusItemController会等待 2 秒并触发一次store.refresh(),无需手动刷新。
凭证文件查找顺序
VertexAIOAuthCredentials.swift 中的credentialsFilePath()按以下优先级定位 ADC 文件:
- 环境变量
GOOGLE_APPLICATION_CREDENTIALS指向的显式路径(可指向服务账号 JSON); - 环境变量
CLOUDSDK_CONFIG指定的 gcloud 配置目录下的application_default_credentials.json; - 默认位置
~/.config/gcloud/application_default_credentials.json。
用户凭证解析时要求client_id、client_secret、refresh_token三者齐全,缺失则分别抛出missingClientCredentials/missingTokens错误;access_token允许缺失(随后走刷新流程),token_expiry字段以 ISO8601 格式解析,email则从id_token的 JWT payload 中直接解码提取(避免额外网络请求)。
项目 ID 的解析顺序
loadProjectId()的取值链路是:
- 读取 gcloud 的默认配置(
~/.config/gcloud/configurations/config_default,或CLOUDSDK_CONFIG下的同名文件),解析 INI 风格的project = PROJECT_ID行——这正是gcloud config set project写入的内容; - 配置文件不存在或无 project 行时,依次回退到环境变量
GOOGLE_CLOUD_PROJECT、GCLOUD_PROJECT、CLOUDSDK_CORE_PROJECT。
两者都拿不到项目 ID 时,取数会直接抛出noProject错误,提示执行gcloud config set project PROJECT_ID。
服务账号支持
从源码结构看,ADC 文件若包含client_email+private_key(服务账号特征),loadForFetch不会尝试解析刷新令牌,而是通过子进程执行:
gcloud auth application-default print-access-token由 gcloud 自身完成签名换 token,CodexBar 只负责清洗输出并构造一个 50 分钟有效期的临时凭证。这意味着服务账号路径依赖本机 gcloud CLI 可用。
Token 刷新
VertexAITokenRefresher.swift 实现了标准 OAuth 刷新:
- 触发条件:
needsRefresh,即距token_expiry不足 5 分钟(无过期时间则视为需要刷新); - 端点:
POST https://oauth2.googleapis.com/token,grant_type=refresh_token,携带client_id/client_secret/refresh_token; - 错误映射:
invalid_grant→ refresh token 过期,unauthorized_client→ refresh token 被吊销,两者都提示重新执行gcloud auth application-default login; - 刷新成功后更新
access_token、按expires_in(默认 3600 秒)重算过期时间,并尝试从新的id_token刷新账号 email。
需要说明的是,凭证刷新结果只在内存中缓存(save()是空实现),应用不会修改 gcloud 自己的凭证文件。
API 端点:Cloud Monitoring timeSeries
配额数据来自两个监控指标,过滤器在 VertexAIUsageFetcher.swift 中硬编码:
| 指标 | 过滤条件 |
|---|---|
用量serviceruntime.googleapis.com/quota/allocation/usage | resource.type="consumer_quota" AND resource.label.service="aiplatform.googleapis.com" |
限额serviceruntime.googleapis.com/quota/limit | 同上 |
请求构造为GET https://monitoring.googleapis.com/v3/projects/{PROJECT_ID}/timeSeries,关键查询参数为:
filter:上表两个过滤器之一;interval.startTime/interval.endTime:最近24 小时窗口(usageWindowSeconds = 24 * 60 * 60);aggregation.alignmentPeriod=3600s、aggregation.perSeriesAligner=ALIGN_MAX:按 1 小时对齐取峰值;view=FULL,并循环处理nextPageToken直到取完所有分页。
响应按 HTTP 状态码分类处理:401 →unauthorized(提示重跑gcloud auth application-default login),403 →forbidden(提示检查 IAM 中 Cloud Monitoring 的访问权限),其余非 200 一律包装响应体为invalidResponse。
配额序列的匹配与聚合逻辑
原始 timeSeries 不能直接使用,makeQuotaUsageResponse完成了文档中 "Mapping" 一节描述的全部语义:
1. 构建序列键。每条序列提取为三元组QuotaKey(quotaMetric, limitName, location):
quotaMetric取自metric.labels["quota_metric"],回退到resource.labels["quota_id"];limitName取自metric.labels["limit_name"](用量序列通常没有,为空串);location取自resource.labels["location"],缺省为global。
2. 按键聚合。同一键的多条序列取点值最大值(ALIGN_MAX之外的二次保护,兼容doubleValue与int64Value两种点值类型)。
3. 用量与限额配对。matchingLimit采用两级策略:
- 精确匹配:用量键与限额键三元组完全一致且限额 > 0;
- 唯一候选回退:当用量序列没有
limit_name时,在同quotaMetric+ 同location的限额中,若恰好只有 1 个候选则采用,存在多个候选(区域限额歧义)则放弃该序列。
4. 汇报最大值。对所有成功配对的序列计算usage / limit * 100,最终requestsUsedPercent为其中的最大值——即文档所述 "Reports the highest usage percent across matched series"。两侧序列均缺失或无一配对时抛noData。
这个noData在策略层被刻意降级:VertexAIProviderDescriptor.swift 中捕获noData后将 usage 置空,继续返回快照——因为本地 token 成本不依赖 Cloud Monitoring,即使近 24 小时没有 Vertex 请求,成本面板仍可用。取数成功后UsageSnapshot.identity会携带账号 email 与项目 ID(loginMethod: "gcloud"),用于菜单栏展示身份。
对应的回归测试见 VertexAIUsageFetcherTests.swift,fixtures 覆盖了精确命名匹配、无 limit_name 匹配与区域限额歧义三种场景,例如 exact-named-usage.json 与 ambiguous-regional-limits.json。
Token 成本跟踪:如何从 Claude 日志中识别 Vertex AI 条目
Vertex AI 上的 Claude 使用记录与直接调用 Anthropic API 的记录写入同一批本地文件(~/.claude/projects/下的.jsonl)。区分两者是成本归类的核心,识别逻辑集中在 CostUsageScanner+Claude.swift 的isVertexAIUsageEntry,实际是三层判定(代码中的顺序比文档更细,文档强调的后两层完全一致):
第一层:vrtx消息/请求 ID(代码中标注为 Primary)。Vertex AI 的 message ID 与 request ID 带vrtx前缀,例如msg_vrtx_0154LUXjFVzQGUca3yK2RUeo、req_vrtx_011CWjK86SWeFuXqZKUtgB1H。只要message.id或顶层requestId包含_vrtx_即判为 Vertex AI 条目。这是最可靠的信号,因为不受模型名归一化影响。
第二层:模型名@版本分隔符。文档标注的 primary 判据:
- Vertex AI:
claude-opus-4-5@20251101 - Anthropic API:
claude-opus-4-5-20251101
实现上modelNameLooksVertex要求模型名以claude-开头且包含@。注意文档的告警依然成立:如果 Claude Code 在写日志时把模型名归一化为-格式且没有vrtxID 或元数据兜底,条目将无法与直接 API 用量区分。
第三层:元数据字段(fallback)。对整条记录做递归遍历,命中即判定:
- 键名或值包含
vertex(大小写不敏感的字节级扫描,避免 Unicode 组合字符干扰),顶层键名同时匹配gcp; - 命中一组预定义「提供者类」键(
provider、platform、backend、api_provider、source、vendor、client等)且其值为vertex标记——即文档中metadata.provider: "vertexai"这类场景。
扫描器还提供vertexAIOnly/excludeVertexAI两种过滤模式,供成本口径选择「仅统计 Vertex AI」或「排除 Vertex AI」。分类器行为有专门测试:CostUsageClaudeVertexClassifierTests.swift。
看到 Vertex AI 成本的前置条件
按文档要求,需要在 CodexBar 中同时满足:
- Settings → Providers 中启用Vertex AI(该 Provider 默认关闭,
defaultEnabled: false); - Settings → General 中开启 "Show cost summary";
- Claude Code 实际走 Vertex AI(例如
cv别名设置ANTHROPIC_MODEL=claude-opus-4-5@20251101),且日志保留@格式或vrtxID。
启用后,菜单卡片会以内联 token 成本仪表盘呈现(描述文件中supportsTokenCost: true、supportsInlineTokenCostDashboard: true);若无成本数据,提示文案为 "No Vertex AI cost data found in Claude logs. Ensure entries include Vertex metadata."。
故障排查
结合文档的三条排查项与源码错误类型,完整对照如下:
| 症状 | 可能原因 | 处理 |
|---|---|---|
| 无配额数据 | 项目未开启 Cloud Monitoring API,或账号缺少相应 IAM 权限 | 确认所选项目可用 Cloud Monitoring;403 时检查 IAM 权限 |
| 无配额数据(且无报错) | 近 24 小时无 Vertex 请求 | 属正常noData降级,本地 token 成本仍会展示 |
| 无成本数据 | ~/.claude/projects/不存在或缺少带 Vertex 标记的.jsonl | 检查 Claude Code 日志目录,确认模型名含@或存在vrtxID / vertex 元数据 |
| 认证问题 | ADC 文件缺失、refresh token 过期或被吊销 | 重跑gcloud auth application-default login;服务账号场景检查 gcloud CLI 可用性 |
No Google Cloud project configured | 未设置默认项目 | gcloud config set project PROJECT_ID,或设置GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT/CLOUDSDK_CORE_PROJECT |
相关实现与测试入口:凭证解析 VertexAIOAuthCredentials.swift(测试 VertexAIOAuthCredentialsTests.swift)、监控取数与匹配 VertexAIUsageFetcher.swift、成本分类 CostUsageScanner+Claude.swift。
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考