feat: live multi-provider quota panel (#1278)
* feat: live provider quota panel (Claude + Codex) Adds Claude and Codex plan usage to the context window percentage circle tooltip, querying their respective APIs directly using existing CLI auth tokens. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address review feedback - restore quota trigger, guard NaN date - Re-add triggerFetch() on agent idle/error in agent status subscription - Guard formatResetsAtLabel against NaN from malformed API date strings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve composer conflict and stub subscribe in websocket tests * feat(quota): add cursor, copilot, zai, grok, and kimi quota UI and improve type safety * feat(quota): make Claude and Codex pluggable, and map additional quota/credits metrics * security(quota): fix shell injection vulnerability by migrating exec to execFile * revert: restore original scripts/dev.ps1 and packages/desktop/scripts/dev.ps1 * Fix i18n resource test expectation * Fix quota tooltip empty-provider state * Preserve zero values in Grok quota * Fix empty quota fetch state * Ignore quota frames in relay reconnect tests * Persist refreshed Codex tokens to source auth file * fix(quota): read Claude OAuth credentials from the macOS login Keychain On macOS, Claude Code stores its OAuth credential in the login Keychain (generic-password item, service "Claude Code-credentials"), not in ~/.claude/.credentials.json — that file usually does not exist there, so the Claude provider's existsSync check failed and macOS users silently got no Claude quota. Read it via the macOS `security` CLI (its ACL only trusts /usr/bin/security to decrypt; a native Keychain read would prompt), and stay read-only there since Claude Code owns the refresh/persist. Linux and Windows keep using ~/.claude/.credentials.json unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix quota keychain timeout and local host reconciliation * Fix terminal notification test quota stub * Ignore quota frames in terminal notification tests * fix(quota): bypass Claude token refresh on macOS Keychain-backed logins * fix(dev): update local dev daemon port to 6768 on Windows scripts/dev.ps1 * fix(dev): bypass Unix dev-daemon.sh on Windows in dev.ps1 * fix(client): handle and dispatch provider_quota event in DaemonClient * fix(desktop): fix PowerShell quote stripping in desktop dev.ps1 config seeding * fix(desktop): execute config update via temp file to avoid PowerShell quoting bugs * fix(desktop): fix concurrently command invocation on Windows in dev.ps1 * fix(desktop): resolve path escaping and environment setting syntax errors in dev.ps1 for Windows * Add on-demand provider usage views * Fix Cursor usage billing dates * Move provider usage renderer coverage to e2e * Use provider manifest for quota fetchers * Add provider usage fetch timeouts * Reshape provider usage fetchers --------- Co-authored-by: ABorakati <ABorakati@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> Co-authored-by: lumingjun <lumingjun@bytedance.com>
This commit is contained in:
co-authored by
ABorakati
Claude Sonnet 4.6
Mohamed Boudra
lumingjun
parent
2b0740ff84
commit
acea7f3d24
@@ -1201,6 +1201,11 @@ export const ProviderDiagnosticRequestMessageSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const ProviderUsageListRequestMessageSchema = z.object({
|
||||
type: z.literal("provider.usage.list.request"),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const ResumeAgentRequestMessageSchema = z.object({
|
||||
type: z.literal("resume_agent_request"),
|
||||
handle: AgentPersistenceHandleSchema,
|
||||
@@ -2029,6 +2034,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
GetProvidersSnapshotRequestMessageSchema,
|
||||
RefreshProvidersSnapshotRequestMessageSchema,
|
||||
ProviderDiagnosticRequestMessageSchema,
|
||||
ProviderUsageListRequestMessageSchema,
|
||||
ResumeAgentRequestMessageSchema,
|
||||
ImportAgentRequestMessageSchema,
|
||||
RefreshAgentRequestMessageSchema,
|
||||
@@ -2300,6 +2306,8 @@ export const ServerInfoStatusPayloadSchema = z
|
||||
projectRemove: z.boolean().optional(),
|
||||
// COMPAT(worktreeRestore): added in v0.1.97, drop the gate when floor >= v0.1.97
|
||||
worktreeRestore: z.boolean().optional(),
|
||||
// COMPAT(providerUsageList): added in v0.1.98, drop the gate when daemon floor >= v0.1.98.
|
||||
providerUsageList: z.boolean().optional(),
|
||||
// COMPAT(agentDetach): added in v0.1.98, remove gate after 2026-12-19 once daemon floor >= v0.1.98.
|
||||
agentDetach: z.boolean().optional(),
|
||||
})
|
||||
@@ -3830,6 +3838,61 @@ export const ProviderDiagnosticResponseMessageSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ProviderUsageToneSchema = z.enum(["default", "ok", "warning", "danger"]);
|
||||
export const ProviderUsageStatusSchema = z.enum(["available", "unavailable", "error"]);
|
||||
|
||||
export const ProviderUsageWindowSchema = z.object({
|
||||
id: z.string(),
|
||||
label: z.string(),
|
||||
usedPct: z.number().nullable().optional(),
|
||||
remainingPct: z.number().nullable().optional(),
|
||||
resetsAt: z.string().nullable().optional(),
|
||||
runsOutAt: z.string().nullable().optional(),
|
||||
shortfallPct: z.number().nullable().optional(),
|
||||
tone: ProviderUsageToneSchema.optional(),
|
||||
});
|
||||
|
||||
export const ProviderUsageBalanceSchema = z.object({
|
||||
id: z.string(),
|
||||
label: z.string(),
|
||||
used: z.number().nullable().optional(),
|
||||
remaining: z.number().nullable().optional(),
|
||||
limit: z.number().nullable().optional(),
|
||||
unit: z.enum(["usd", "credits", "requests", "tokens"]),
|
||||
resetsAt: z.string().nullable().optional(),
|
||||
tone: ProviderUsageToneSchema.optional(),
|
||||
});
|
||||
|
||||
export const ProviderUsageDetailSchema = z.object({
|
||||
id: z.string(),
|
||||
label: z.string(),
|
||||
value: z.string(),
|
||||
tone: ProviderUsageToneSchema.optional(),
|
||||
});
|
||||
|
||||
export const ProviderUsageSchema = z.object({
|
||||
providerId: z.string(),
|
||||
displayName: z.string(),
|
||||
status: ProviderUsageStatusSchema,
|
||||
planLabel: z.string().nullable(),
|
||||
sourceLabel: z.string().nullable().optional(),
|
||||
fetchedAt: z.string().nullable().optional(),
|
||||
nextRefreshAt: z.string().nullable().optional(),
|
||||
windows: z.array(ProviderUsageWindowSchema),
|
||||
balances: z.array(ProviderUsageBalanceSchema).optional(),
|
||||
details: z.array(ProviderUsageDetailSchema).optional(),
|
||||
error: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export const ProviderUsageListResponseMessageSchema = z.object({
|
||||
type: z.literal("provider.usage.list.response"),
|
||||
payload: z.object({
|
||||
requestId: z.string(),
|
||||
fetchedAt: z.string(),
|
||||
providers: z.array(ProviderUsageSchema),
|
||||
}),
|
||||
});
|
||||
|
||||
const AgentSlashCommandSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
@@ -4093,6 +4156,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
ProvidersSnapshotUpdateMessageSchema,
|
||||
RefreshProvidersSnapshotResponseMessageSchema,
|
||||
ProviderDiagnosticResponseMessageSchema,
|
||||
ProviderUsageListResponseMessageSchema,
|
||||
ListCommandsResponseSchema,
|
||||
ListTerminalsResponseSchema,
|
||||
TerminalsChangedSchema,
|
||||
@@ -4228,6 +4292,15 @@ export type RefreshProvidersSnapshotResponseMessage = z.infer<
|
||||
export type ProviderDiagnosticResponseMessage = z.infer<
|
||||
typeof ProviderDiagnosticResponseMessageSchema
|
||||
>;
|
||||
export type ProviderUsageTone = z.infer<typeof ProviderUsageToneSchema>;
|
||||
export type ProviderUsageStatus = z.infer<typeof ProviderUsageStatusSchema>;
|
||||
export type ProviderUsage = z.infer<typeof ProviderUsageSchema>;
|
||||
export type ProviderUsageWindow = z.infer<typeof ProviderUsageWindowSchema>;
|
||||
export type ProviderUsageBalance = z.infer<typeof ProviderUsageBalanceSchema>;
|
||||
export type ProviderUsageDetail = z.infer<typeof ProviderUsageDetailSchema>;
|
||||
export type ProviderUsageListResponseMessage = z.infer<
|
||||
typeof ProviderUsageListResponseMessageSchema
|
||||
>;
|
||||
export type ChatCreateResponse = z.infer<typeof ChatCreateResponseSchema>;
|
||||
export type ChatListResponse = z.infer<typeof ChatListResponseSchema>;
|
||||
export type ChatInspectResponse = z.infer<typeof ChatInspectResponseSchema>;
|
||||
|
||||
Reference in New Issue
Block a user