Merge remote-tracking branch 'origin/main' into review/pr-56
This commit is contained in:
@@ -138,6 +138,29 @@ export function getApiKey(
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Hosts such as OMP may pass the literal env-var name "$COMMANDCODE_API_KEY"
|
||||
// (or "COMMANDCODE_API_KEY") as the "resolved" registry key instead of the
|
||||
// actual credential. Treat those as unresolved.
|
||||
export const COMMAND_CODE_PLACEHOLDER_KEYS = new Set([
|
||||
"$COMMANDCODE_API_KEY",
|
||||
"COMMANDCODE_API_KEY",
|
||||
])
|
||||
|
||||
/**
|
||||
* Pick the real API key from a host registry value and/or the env/auth-file
|
||||
* fallback, never returning a literal placeholder or an empty/whitespace value.
|
||||
* Pure/testable.
|
||||
*/
|
||||
export function pickCommandCodeApiKey(
|
||||
registryKey: string | undefined,
|
||||
hostKey: string | undefined,
|
||||
): string | undefined {
|
||||
const trimmed = typeof registryKey === "string" ? registryKey.trim() : undefined
|
||||
if (!trimmed) return hostKey
|
||||
if (COMMAND_CODE_PLACEHOLDER_KEYS.has(trimmed)) return hostKey
|
||||
return trimmed
|
||||
}
|
||||
|
||||
export function textContent(message: { content?: unknown }): string {
|
||||
return recordArray(message.content)
|
||||
.filter((part) => part.type === "text")
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { getConfiguredApiKey } from "./api-key.ts"
|
||||
import { pickCommandCodeApiKey } from "./converters.ts"
|
||||
import { fetchCommandCodeQuota, redactValue } from "./quota.ts"
|
||||
import { formatQuota } from "./quota-format.ts"
|
||||
|
||||
export interface QuotaCommandContext {
|
||||
waitForIdle?: () => Promise<void>
|
||||
modelRegistry?: {
|
||||
getApiKeyForProvider?: (provider: string) => Promise<string | undefined>
|
||||
}
|
||||
ui: {
|
||||
notify(message: string, type?: "info" | "warning" | "error"): void
|
||||
}
|
||||
}
|
||||
|
||||
interface QuotaCommandApi {
|
||||
registerCommand(
|
||||
name: string,
|
||||
options: {
|
||||
description: string
|
||||
handler: (args: string, ctx: QuotaCommandContext) => Promise<void>
|
||||
},
|
||||
): void
|
||||
}
|
||||
|
||||
interface RegisterQuotaCommandOptions {
|
||||
apiBase: string
|
||||
headers?: Record<string, string>
|
||||
getConfiguredKey?: () => string | undefined
|
||||
fetchQuota?: typeof fetchCommandCodeQuota
|
||||
}
|
||||
|
||||
export function registerCommandCodeQuota(
|
||||
pi: QuotaCommandApi,
|
||||
options: RegisterQuotaCommandOptions,
|
||||
): void {
|
||||
const getConfiguredKey = options.getConfiguredKey ?? getConfiguredApiKey
|
||||
const fetchQuota = options.fetchQuota ?? fetchCommandCodeQuota
|
||||
|
||||
pi.registerCommand("commandcode-quota", {
|
||||
description: "Show Command Code account usage and quota",
|
||||
handler: async (_args, ctx) => {
|
||||
await ctx.waitForIdle?.()
|
||||
const registryKey = await ctx.modelRegistry?.getApiKeyForProvider?.("commandcode")
|
||||
const apiKey = pickCommandCodeApiKey(registryKey, getConfiguredKey())
|
||||
if (!apiKey) {
|
||||
ctx.ui.notify(
|
||||
"Command Code quota requires an API key. Run /login and select Command Code, or set COMMANDCODE_API_KEY.",
|
||||
"warning",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const result = await fetchQuota({
|
||||
apiKey,
|
||||
baseUrl: options.apiBase,
|
||||
extraHeaders: options.headers,
|
||||
})
|
||||
if (!result.ok) {
|
||||
ctx.ui.notify(redactValue(result.error.message), "error")
|
||||
return
|
||||
}
|
||||
ctx.ui.notify(formatQuota(result.quota), "info")
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import type {
|
||||
CommandCodeCredits,
|
||||
CommandCodeQuota,
|
||||
CommandCodeSubscription,
|
||||
CommandCodeWindowLimit,
|
||||
} from "./quota-types.ts"
|
||||
|
||||
export function formatWindowLimits(
|
||||
limits: readonly CommandCodeWindowLimit[],
|
||||
now: () => number = Date.now,
|
||||
): string[] {
|
||||
const labels: Record<CommandCodeWindowLimit["window"], string> = {
|
||||
fiveHour: "5-hour",
|
||||
weekly: "Weekly",
|
||||
}
|
||||
|
||||
return limits.map((limit) => {
|
||||
const used = limit.used.toFixed(2)
|
||||
const cap = limit.cap.toFixed(2)
|
||||
const percent = limit.cap > 0 ? Math.round((limit.used / limit.cap) * 100) : 0
|
||||
const reset = limit.resetAt === null ? "" : ` (resets ${formatResetClock(limit.resetAt, now)})`
|
||||
return `${labels[limit.window]}: ${used} / ${cap} credits (${percent}% used)${reset}`
|
||||
})
|
||||
}
|
||||
|
||||
function formatResetClock(resetAtSeconds: number, now: () => number): string {
|
||||
const date = new Date(resetAtSeconds * 1000)
|
||||
if (Number.isNaN(date.getTime())) return "unknown"
|
||||
const diffMs = date.getTime() - now()
|
||||
if (diffMs <= 0) return "soon"
|
||||
const minutes = Math.ceil(diffMs / 60_000)
|
||||
if (minutes < 60) return `in ${minutes}m`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const remainingMinutes = minutes % 60
|
||||
if (hours < 24) {
|
||||
return remainingMinutes > 0 ? `in ${hours}h ${remainingMinutes}m` : `in ${hours}h`
|
||||
}
|
||||
const days = Math.floor(hours / 24)
|
||||
return days === 1 ? "in 1 day" : `in ${days} days`
|
||||
}
|
||||
|
||||
function creditsDetail(credits: CommandCodeCredits | null): string | undefined {
|
||||
if (!credits) return undefined
|
||||
const parts = [
|
||||
`monthly $${credits.monthlyCredits.toFixed(2)}`,
|
||||
`purchased $${credits.purchasedCredits.toFixed(2)}`,
|
||||
]
|
||||
if (credits.freeCredits > 0) parts.push(`free $${credits.freeCredits.toFixed(2)}`)
|
||||
return `Sources: ${parts.join(" / ")}`
|
||||
}
|
||||
|
||||
function subscriptionLine(subscription: CommandCodeSubscription): string {
|
||||
const plan = (subscription.planId ?? "Unknown").replace(/[_-]+/g, " ").trim()
|
||||
const status = subscription.status ? ` (${subscription.status})` : ""
|
||||
return `Plan: ${plan}${status}`
|
||||
}
|
||||
|
||||
function formatTokens(tokens: number): string {
|
||||
if (tokens >= 1_000_000_000) return `${(tokens / 1_000_000_000).toFixed(1)}B`
|
||||
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`
|
||||
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`
|
||||
return String(tokens)
|
||||
}
|
||||
|
||||
export function formatQuota(quota: CommandCodeQuota, now: () => number = Date.now): string {
|
||||
const lines: string[] = []
|
||||
const remaining = quota.credits?.remainingCredits ?? 0
|
||||
const spent = quota.summary?.totalCost ?? 0
|
||||
const pool = remaining + spent
|
||||
|
||||
if (quota.credits || quota.summary) {
|
||||
lines.push("Credits")
|
||||
lines.push(` Remaining: $${remaining.toFixed(2)} of $${pool.toFixed(2)}`)
|
||||
lines.push(` Used: $${spent.toFixed(2)}`)
|
||||
lines.push(` ${pool > 0 ? Math.round((spent / pool) * 100) : 0}% used`)
|
||||
}
|
||||
|
||||
const detail = creditsDetail(quota.credits)
|
||||
if (detail) lines.push(detail)
|
||||
if (quota.subscription) lines.push(subscriptionLine(quota.subscription))
|
||||
|
||||
if (quota.summary) {
|
||||
lines.push("")
|
||||
lines.push(quota.subscription?.currentPeriodStart ? "Usage (billing period)" : "Usage")
|
||||
lines.push(` Cost: $${quota.summary.totalCost.toFixed(2)}`)
|
||||
lines.push(` Requests: ${quota.summary.totalCount.toLocaleString("en-US")}`)
|
||||
if (quota.summary.totalTokens !== undefined) {
|
||||
lines.push(` Tokens: ${formatTokens(quota.summary.totalTokens)}`)
|
||||
}
|
||||
}
|
||||
|
||||
lines.push("")
|
||||
lines.push("Account")
|
||||
lines.push(` ${quota.account.keyName ?? quota.account.login}`)
|
||||
|
||||
const limits = quota.credits?.windowLimits ?? []
|
||||
if (limits.length > 0) {
|
||||
lines.push("")
|
||||
lines.push("Usage windows:")
|
||||
lines.push(...formatWindowLimits(limits, now).map((line) => ` ${line}`))
|
||||
}
|
||||
|
||||
if ((quota.unavailable?.length ?? 0) > 0) {
|
||||
lines.push("")
|
||||
lines.push(`Unavailable: ${quota.unavailable?.join(", ")}`)
|
||||
}
|
||||
|
||||
lines.push("")
|
||||
lines.push("Full detail: https://commandcode.ai/usage")
|
||||
return lines.join("\n")
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
export interface CommandCodeWindowLimit {
|
||||
window: "fiveHour" | "weekly"
|
||||
used: number
|
||||
cap: number
|
||||
resetAt: number | null
|
||||
}
|
||||
|
||||
export interface CommandCodeCredits {
|
||||
monthlyCredits: number
|
||||
purchasedCredits: number
|
||||
freeCredits: number
|
||||
remainingCredits: number
|
||||
windowLimits: CommandCodeWindowLimit[]
|
||||
}
|
||||
|
||||
export interface CommandCodeSubscription {
|
||||
planId: string | null
|
||||
status: string | null
|
||||
currentPeriodStart: string | null
|
||||
currentPeriodEnd: string | null
|
||||
}
|
||||
|
||||
export interface CommandCodeUsageSummary {
|
||||
totalCost: number
|
||||
totalCount: number
|
||||
totalTokens?: number
|
||||
}
|
||||
|
||||
export type CommandCodeQuotaSection = "credits" | "subscription" | "usage"
|
||||
|
||||
export interface CommandCodeQuota {
|
||||
account: {
|
||||
login: string
|
||||
orgId: string | null
|
||||
keyName?: string
|
||||
}
|
||||
credits: CommandCodeCredits | null
|
||||
subscription: CommandCodeSubscription | null
|
||||
summary: CommandCodeUsageSummary | null
|
||||
unavailable?: readonly CommandCodeQuotaSection[]
|
||||
}
|
||||
|
||||
export type CommandCodeQuotaErrorKind = "config" | "http" | "network" | "timeout"
|
||||
|
||||
export type CommandCodeQuotaResult =
|
||||
| { ok: true; quota: CommandCodeQuota }
|
||||
| { ok: false; error: { message: string; kind: CommandCodeQuotaErrorKind } }
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
import { redactCommandCodeErrorText } from "./overflow.ts"
|
||||
import type {
|
||||
CommandCodeCredits,
|
||||
CommandCodeQuotaResult,
|
||||
CommandCodeQuotaSection,
|
||||
CommandCodeSubscription,
|
||||
CommandCodeUsageSummary,
|
||||
CommandCodeWindowLimit,
|
||||
} from "./quota-types.ts"
|
||||
|
||||
export const DEFAULT_API_BASE = "https://api.commandcode.ai"
|
||||
export const QUOTA_TIMEOUT_MS = 15_000
|
||||
|
||||
interface FetchOptions {
|
||||
apiKey: string
|
||||
baseUrl?: string
|
||||
fetchImpl?: typeof fetch
|
||||
timeoutMs?: number
|
||||
extraHeaders?: Record<string, string>
|
||||
}
|
||||
|
||||
interface HttpErrorShape {
|
||||
__httpError: true
|
||||
message: string
|
||||
status: number
|
||||
body: string
|
||||
}
|
||||
|
||||
interface QuotaErrorShape {
|
||||
__quotaError: true
|
||||
kind: "timeout" | "network"
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function numberValue(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined
|
||||
}
|
||||
|
||||
function stringValue(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.length > 0 ? value : undefined
|
||||
}
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
|
||||
function normalizeResetAt(value: unknown): number | null {
|
||||
let timestamp: number | undefined
|
||||
if (typeof value === "number" && Number.isFinite(value)) timestamp = value
|
||||
if (typeof value === "string" && value.length > 0) {
|
||||
const trimmed = value.trim()
|
||||
timestamp = /^\d+$/.test(trimmed) ? Number(trimmed) : Date.parse(trimmed)
|
||||
}
|
||||
if (timestamp === undefined || !Number.isFinite(timestamp) || timestamp < 0) return null
|
||||
return timestamp >= 1e12 ? Math.round(timestamp / 1000) : timestamp
|
||||
}
|
||||
|
||||
export function windowLimitsFromCredits(value: unknown): CommandCodeWindowLimit[] {
|
||||
if (!isRecord(value)) return []
|
||||
const limits: CommandCodeWindowLimit[] = []
|
||||
for (const [window, entry] of [
|
||||
["fiveHour", value.fiveHour],
|
||||
["weekly", value.weekly],
|
||||
] as const) {
|
||||
if (!isRecord(entry)) continue
|
||||
const used = numberValue(entry.used)
|
||||
const cap = numberValue(entry.cap)
|
||||
if (used === undefined || cap === undefined || (used === 0 && cap === 0)) continue
|
||||
limits.push({ window, used, cap, resetAt: normalizeResetAt(entry.resetAt) })
|
||||
}
|
||||
return limits
|
||||
}
|
||||
|
||||
function parseCredits(value: unknown): CommandCodeCredits | null {
|
||||
if (!isRecord(value) || !isRecord(value.credits)) return null
|
||||
const credits = value.credits
|
||||
const monthlyCredits = numberValue(credits.monthlyCredits)
|
||||
const purchasedCredits = numberValue(credits.purchasedCredits)
|
||||
const freeCredits = numberValue(credits.freeCredits)
|
||||
if (monthlyCredits === undefined && purchasedCredits === undefined && freeCredits === undefined) {
|
||||
return null
|
||||
}
|
||||
const monthly = monthlyCredits ?? 0
|
||||
const purchased = purchasedCredits ?? 0
|
||||
const free = freeCredits ?? 0
|
||||
return {
|
||||
monthlyCredits: monthly,
|
||||
purchasedCredits: purchased,
|
||||
freeCredits: free,
|
||||
remainingCredits: monthly + purchased + free,
|
||||
windowLimits: windowLimitsFromCredits(value.windowLimits),
|
||||
}
|
||||
}
|
||||
|
||||
function parseSubscription(value: unknown): CommandCodeSubscription | null {
|
||||
if (!isRecord(value) || !isRecord(value.data)) return null
|
||||
const data = value.data
|
||||
const planId = stringValue(data.planId)
|
||||
const status = stringValue(data.status)
|
||||
const currentPeriodStart = stringValue(data.currentPeriodStart)
|
||||
const currentPeriodEnd = stringValue(data.currentPeriodEnd)
|
||||
if (!planId && !status && !currentPeriodStart && !currentPeriodEnd) return null
|
||||
return {
|
||||
planId: planId ?? null,
|
||||
status: status ?? null,
|
||||
currentPeriodStart: currentPeriodStart ?? null,
|
||||
currentPeriodEnd: currentPeriodEnd ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
function parseSummary(value: unknown): CommandCodeUsageSummary | null {
|
||||
if (!isRecord(value)) return null
|
||||
const totalCost = numberValue(value.totalCost)
|
||||
const totalCount = numberValue(value.totalCount)
|
||||
if (totalCost === undefined || totalCount === undefined) return null
|
||||
const totalTokens = numberValue(value.totalTokens) ?? numberValue(value.tokens)
|
||||
return { totalCost, totalCount, ...(totalTokens === undefined ? {} : { totalTokens }) }
|
||||
}
|
||||
|
||||
function parseWhoami(value: unknown): {
|
||||
login: string
|
||||
orgId: string | null
|
||||
keyName?: string
|
||||
} | null {
|
||||
if (!isRecord(value)) return null
|
||||
const org = isRecord(value.org) ? value.org : undefined
|
||||
const user = isRecord(value.user) ? value.user : undefined
|
||||
const login =
|
||||
(org ? stringValue(org.login) : undefined) ??
|
||||
(user ? (stringValue(user.userName) ?? stringValue(user.name)) : undefined)
|
||||
if (!login) return null
|
||||
const orgId = org ? stringValue(org.id) : undefined
|
||||
const keyName = user ? (stringValue(user.keyName) ?? stringValue(user.displayName)) : undefined
|
||||
return { login, orgId: orgId ?? null, ...(keyName ? { keyName } : {}) }
|
||||
}
|
||||
|
||||
function buildUrl(path: string, params: Record<string, string | undefined>): string {
|
||||
const search = new URLSearchParams()
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value) search.set(key, value)
|
||||
}
|
||||
const query = search.toString()
|
||||
return `${path}${query ? `?${query}` : ""}`
|
||||
}
|
||||
|
||||
function isHttpError(value: unknown): value is HttpErrorShape {
|
||||
return (
|
||||
isRecord(value) &&
|
||||
value.__httpError === true &&
|
||||
typeof value.message === "string" &&
|
||||
typeof value.status === "number" &&
|
||||
typeof value.body === "string"
|
||||
)
|
||||
}
|
||||
|
||||
function isQuotaError(value: unknown): value is QuotaErrorShape {
|
||||
return (
|
||||
isRecord(value) &&
|
||||
value.__quotaError === true &&
|
||||
(value.kind === "timeout" || value.kind === "network")
|
||||
)
|
||||
}
|
||||
|
||||
function isBlockingHttpError(error: HttpErrorShape): boolean {
|
||||
return error.status === 401 || error.status === 403
|
||||
}
|
||||
|
||||
function httpFailure(error: HttpErrorShape, context: string): CommandCodeQuotaResult {
|
||||
const detail = error.body.trim().slice(0, 200)
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
kind: "http",
|
||||
message: redactValue(
|
||||
`${context} request failed (${error.status}): ${detail || error.message}`,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
class QuotaTimeoutError extends Error {}
|
||||
|
||||
export async function fetchCommandCodeQuota(
|
||||
options: FetchOptions,
|
||||
): Promise<CommandCodeQuotaResult> {
|
||||
if (!options.apiKey) {
|
||||
return { ok: false, error: { message: "No Command Code API key found", kind: "config" } }
|
||||
}
|
||||
|
||||
const baseUrl = options.baseUrl ?? DEFAULT_API_BASE
|
||||
const fetchImpl = options.fetchImpl ?? fetch
|
||||
const timeoutMs = options.timeoutMs ?? QUOTA_TIMEOUT_MS
|
||||
const overallController = new AbortController()
|
||||
const overallTimer = setTimeout(() => overallController.abort(), timeoutMs)
|
||||
const headers = {
|
||||
accept: "application/json",
|
||||
Authorization: `Bearer ${options.apiKey}`,
|
||||
...options.extraHeaders,
|
||||
}
|
||||
|
||||
const request = async (path: string): Promise<unknown> => {
|
||||
if (overallController.signal.aborted) throw new QuotaTimeoutError()
|
||||
try {
|
||||
const response = await fetchImpl(`${baseUrl}${path}`, {
|
||||
method: "GET",
|
||||
headers,
|
||||
signal: overallController.signal,
|
||||
})
|
||||
if (!response.ok) {
|
||||
return {
|
||||
__httpError: true,
|
||||
message:
|
||||
response.status === 401 || response.status === 403
|
||||
? "Command Code rejected the API key"
|
||||
: response.statusText,
|
||||
status: response.status,
|
||||
body: await response.text().catch(() => ""),
|
||||
} satisfies HttpErrorShape
|
||||
}
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
if (overallController.signal.aborted) throw new QuotaTimeoutError()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const safeRequest = async (path: string): Promise<unknown> => {
|
||||
try {
|
||||
return await request(path)
|
||||
} catch (error) {
|
||||
return {
|
||||
__quotaError: true,
|
||||
kind: error instanceof QuotaTimeoutError ? "timeout" : "network",
|
||||
} satisfies QuotaErrorShape
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const whoamiRaw = await request("/alpha/whoami")
|
||||
if (isHttpError(whoamiRaw)) return httpFailure(whoamiRaw, "whoami")
|
||||
const account = parseWhoami(whoamiRaw)
|
||||
if (!account) {
|
||||
return {
|
||||
ok: false,
|
||||
error: { kind: "http", message: "Command Code returned an unrecognized account response" },
|
||||
}
|
||||
}
|
||||
|
||||
const orgId = account.orgId ?? undefined
|
||||
const [creditsRaw, subscriptionRaw] = await Promise.all([
|
||||
safeRequest(buildUrl("/alpha/billing/credits", { orgId })),
|
||||
safeRequest(buildUrl("/alpha/billing/subscriptions", { orgId })),
|
||||
])
|
||||
if (isHttpError(creditsRaw) && isBlockingHttpError(creditsRaw)) {
|
||||
return httpFailure(creditsRaw, "credits")
|
||||
}
|
||||
if (isHttpError(subscriptionRaw) && isBlockingHttpError(subscriptionRaw)) {
|
||||
return httpFailure(subscriptionRaw, "subscription")
|
||||
}
|
||||
|
||||
const unavailable: CommandCodeQuotaSection[] = []
|
||||
const credits =
|
||||
isHttpError(creditsRaw) || isQuotaError(creditsRaw) ? null : parseCredits(creditsRaw)
|
||||
if (!credits) unavailable.push("credits")
|
||||
const subscription =
|
||||
isHttpError(subscriptionRaw) || isQuotaError(subscriptionRaw)
|
||||
? null
|
||||
: parseSubscription(subscriptionRaw)
|
||||
if (!subscription) unavailable.push("subscription")
|
||||
|
||||
const summaryRaw = await safeRequest(
|
||||
buildUrl("/alpha/usage/summary", {
|
||||
orgId,
|
||||
since: subscription?.currentPeriodStart ?? undefined,
|
||||
}),
|
||||
)
|
||||
if (isHttpError(summaryRaw) && isBlockingHttpError(summaryRaw)) {
|
||||
return httpFailure(summaryRaw, "summary")
|
||||
}
|
||||
const summary =
|
||||
isHttpError(summaryRaw) || isQuotaError(summaryRaw) ? null : parseSummary(summaryRaw)
|
||||
if (!summary) unavailable.push("usage")
|
||||
|
||||
if (!credits && !subscription && !summary) {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
kind: overallController.signal.aborted ? "timeout" : "http",
|
||||
message: overallController.signal.aborted
|
||||
? "Command Code quota request timed out"
|
||||
: "Command Code returned no recognized usage data for the account",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
quota: {
|
||||
account,
|
||||
credits,
|
||||
subscription,
|
||||
summary,
|
||||
...(unavailable.length > 0 ? { unavailable } : {}),
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof QuotaTimeoutError || overallController.signal.aborted) {
|
||||
return {
|
||||
ok: false,
|
||||
error: { message: "Command Code quota request timed out", kind: "timeout" },
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
message: redactValue(`Failed to fetch Command Code quota: ${errorMessage(error)}`),
|
||||
kind: "network",
|
||||
},
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(overallTimer)
|
||||
}
|
||||
}
|
||||
|
||||
export function redactValue(value: string): string {
|
||||
return redactCommandCodeErrorText(value)
|
||||
.replace(
|
||||
/("\s*(?:api[-_ ]?key|apikey|access[-_ ]?token|refresh[-_ ]?token|token|secret|password|authorization)\s*"\s*:\s*")([^"]{8,})/gi,
|
||||
"$1[redacted]",
|
||||
)
|
||||
.trim()
|
||||
}
|
||||
Reference in New Issue
Block a user