Merge remote-tracking branch 'origin/main' into review/pr-56
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Add `/commandcode-quota` with live credits, plan, usage totals, and rolling-limit diagnostics from Command Code's alpha usage endpoints.
|
||||
- Add `zai-org/GLM-5.3` with its verified reasoning efforts and display pricing.
|
||||
- Prefer Command Code's Provider API (`/provider/v1/chat/completions` and `/provider/v1/messages`) and automatically fall back to the existing `/alpha/generate` transport only when the Provider API returns `403 upgrade_required` for a Go-plan account.
|
||||
- Remember the detected transport for the running process, re-detect it when credentials change, prevent stale in-flight requests from overwriting the new credential's transport, and never fall back for unrelated authentication, permission, rate-limit, network, or server failures.
|
||||
|
||||
@@ -129,6 +129,9 @@ While pi is running, use these provider commands without restarting:
|
||||
|
||||
- `/commandcode-refresh` fetches and re-registers the current model catalog. Overlapping refreshes are coalesced, and a failed refresh keeps the last valid catalog active.
|
||||
- `/commandcode-status` shows redacted discovery diagnostics, including the source, model count, timestamps, cache path, endpoint, and warning.
|
||||
- `/commandcode-quota` shows your Command Code account usage and quota in a dashboard-style layout: credits remaining and used with a percentage, monthly/purchased/free sources, the current plan, available usage totals, the API key name, and the 5-hour and weekly usage windows.
|
||||
|
||||
The `commandcode-quota` command reads from the Command Code alpha usage endpoints (the same ones the `cmd` CLI `/usage` command uses): `whoami`, `billing/credits`, `billing/subscriptions`, and `usage/summary`. It authenticates with the same API key the provider already uses. If the command cannot reach those endpoints or an endpoint schema changes, unavailable sections are reported explicitly instead of being displayed as zero usage. Output is plain text (via `ui.notify`) so it works across pi and compatible hosts such as OMP.
|
||||
|
||||
Set `COMMANDCODE_ZDR=1` to send Command Code's documented `x-cmd-zdr: 1` zero-data-retention header.
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
import { getApiKey as getOAuthApiKey, login, refreshToken } from "./src/oauth.ts"
|
||||
import { normalizeCommandCodeMessage } from "./src/overflow.ts"
|
||||
import { MODEL_COSTS, ZERO_MODEL_COST } from "./src/pricing.ts"
|
||||
import { registerCommandCodeQuota } from "./src/quota-command.ts"
|
||||
import { createCommandCodeRuntime } from "./src/runtime.ts"
|
||||
import { createCommandCodeTransportRouter } from "./src/transport.ts"
|
||||
|
||||
@@ -124,6 +125,11 @@ export default async function (pi: ExtensionAPI) {
|
||||
return normalized ? { message: normalized.message } : undefined
|
||||
})
|
||||
|
||||
registerCommandCodeQuota(pi, {
|
||||
apiBase: legacyApiBase(apiBase),
|
||||
headers: commandCodeHeaders(),
|
||||
})
|
||||
|
||||
const runtime = createCommandCodeRuntime<ProviderConfig, ExtensionCommandContext>(pi, {
|
||||
endpoint: modelsUrl,
|
||||
cachePath: modelsCachePath,
|
||||
|
||||
+3
-2
@@ -29,13 +29,14 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run typecheck && tsx tests/test-package-manifest.ts && tsx tests/test-api-key.ts && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-runtime.ts && tsx tests/test-pricing.ts && tsx tests/test-cost.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-overflow.ts && tsx tests/test-stream.ts && tsx tests/test-retry.ts && tsx tests/test-transport.ts && node tests/test-pi-isolated.mjs && node tests/test-pi-authenticated.mjs && node tests/test-pi-local.mjs && node tests/test-omp-compat.mjs",
|
||||
"test": "npm run typecheck && tsx tests/test-package-manifest.ts && tsx tests/test-api-key.ts && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-runtime.ts && tsx tests/test-pricing.ts && tsx tests/test-cost.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-overflow.ts && tsx tests/test-stream.ts && tsx tests/test-quota.ts && tsx tests/test-quota-command.ts && tsx tests/test-retry.ts && tsx tests/test-transport.ts && node tests/test-pi-isolated.mjs && node tests/test-pi-authenticated.mjs && node tests/test-pi-local.mjs && node tests/test-omp-compat.mjs",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"format:check": "prettier --check '**/*.{ts,mjs,json,md}'",
|
||||
"format": "prettier --write '**/*.{ts,mjs,json,md}'",
|
||||
"pi:isolated": "node scripts/pi-isolated.mjs",
|
||||
"pi:authenticated": "node scripts/pi-authenticated.mjs",
|
||||
"test:unit": "tsx tests/test-api-key.ts && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-runtime.ts && tsx tests/test-pricing.ts && tsx tests/test-cost.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-overflow.ts && tsx tests/test-stream.ts && tsx tests/test-retry.ts && tsx tests/test-transport.ts",
|
||||
"test:quota": "tsx tests/test-quota.ts && tsx tests/test-quota-command.ts",
|
||||
"test:unit": "tsx tests/test-api-key.ts && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-runtime.ts && tsx tests/test-pricing.ts && tsx tests/test-cost.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-overflow.ts && tsx tests/test-stream.ts && tsx tests/test-quota.ts && tsx tests/test-quota-command.ts && tsx tests/test-retry.ts && tsx tests/test-transport.ts",
|
||||
"test:api-key": "tsx tests/test-api-key.ts",
|
||||
"test:models": "tsx tests/test-models.ts",
|
||||
"test:runtime": "tsx tests/test-runtime.ts",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -165,7 +165,13 @@ function runOmp(args, timeoutMs = 30_000) {
|
||||
try {
|
||||
console.log("[omp-compat] list models through real extension")
|
||||
modelListRequestCount = 0
|
||||
const result = await runOmp(["-e", EXT_PATH, "--list-models"])
|
||||
// Prefer the flag form `omp -e EXT --list-models`; Homebrew's `omp`
|
||||
// distribution only exposes the `omp models` subcommand, so fall back to
|
||||
// that form when the flag invocation is not recognized.
|
||||
let result = await runOmp(["-e", EXT_PATH, "--list-models"])
|
||||
if (result.code !== 0) {
|
||||
result = await runOmp(["models", "-e", EXT_PATH])
|
||||
}
|
||||
assert.equal(result.code, 0, result.stderr)
|
||||
const listOutput = result.stdout || result.stderr
|
||||
assert.match(listOutput, /commandcode/)
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
mapFinishReason,
|
||||
messagesToCC,
|
||||
parseStreamEventLine,
|
||||
pickCommandCodeApiKey,
|
||||
projectSlugFromPath,
|
||||
textContent,
|
||||
toJsonSchema,
|
||||
@@ -106,6 +107,36 @@ describe("error redaction", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("pickCommandCodeApiKey()", () => {
|
||||
it("falls back to the host key for a placeholder registry value", () => {
|
||||
assert.equal(pickCommandCodeApiKey("$COMMANDCODE_API_KEY", "file-key"), "file-key")
|
||||
assert.equal(pickCommandCodeApiKey("COMMANDCODE_API_KEY", "file-key"), "file-key")
|
||||
})
|
||||
|
||||
it("returns undefined when only a placeholder is provided (no fallback)", () => {
|
||||
assert.equal(pickCommandCodeApiKey("$COMMANDCODE_API_KEY", undefined), undefined)
|
||||
})
|
||||
|
||||
it("prefers a real registry key over the host fallback", () => {
|
||||
assert.equal(pickCommandCodeApiKey("real-registry-key", "file-key"), "real-registry-key")
|
||||
})
|
||||
|
||||
it("falls back to the host key when the registry has none", () => {
|
||||
assert.equal(pickCommandCodeApiKey(undefined, "file-key"), "file-key")
|
||||
assert.equal(pickCommandCodeApiKey(undefined, undefined), undefined)
|
||||
})
|
||||
|
||||
it("falls back to the host key for empty or whitespace registry values", () => {
|
||||
assert.equal(pickCommandCodeApiKey("", "file-key"), "file-key")
|
||||
assert.equal(pickCommandCodeApiKey(" ", "file-key"), "file-key")
|
||||
assert.equal(pickCommandCodeApiKey(" ", undefined), undefined)
|
||||
})
|
||||
|
||||
it("trims a real registry key", () => {
|
||||
assert.equal(pickCommandCodeApiKey(" real-registry-key ", "file-key"), "real-registry-key")
|
||||
})
|
||||
})
|
||||
|
||||
describe("projectSlugFromPath()", () => {
|
||||
it("matches the official CLI-style slug from an absolute working directory", () => {
|
||||
assert.equal(
|
||||
@@ -359,7 +390,7 @@ describe("toJsonSchema()", () => {
|
||||
if (!outputProperties || typeof outputProperties !== "object") {
|
||||
throw new Error("expected object properties")
|
||||
}
|
||||
assert.ok(Object.prototype.hasOwnProperty.call(outputProperties, "__proto__"))
|
||||
assert.ok(Object.hasOwn(outputProperties, "__proto__"))
|
||||
assert.deepEqual(Object.getOwnPropertyDescriptor(outputProperties, "__proto__")?.value, {
|
||||
type: "string",
|
||||
})
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
|
||||
import { registerCommandCodeQuota, type QuotaCommandContext } from "../src/quota-command.ts"
|
||||
import type { CommandCodeQuotaResult } from "../src/quota-types.ts"
|
||||
|
||||
class CommandApiDouble {
|
||||
handler?: (args: string, ctx: QuotaCommandContext) => Promise<void>
|
||||
|
||||
registerCommand(
|
||||
name: string,
|
||||
options: {
|
||||
description: string
|
||||
handler: (args: string, ctx: QuotaCommandContext) => Promise<void>
|
||||
},
|
||||
): void {
|
||||
assert.equal(name, "commandcode-quota")
|
||||
assert.match(options.description, /usage and quota/)
|
||||
this.handler = options.handler
|
||||
}
|
||||
}
|
||||
|
||||
function context(registryKey: string | undefined) {
|
||||
const notifications: Array<{ message: string; type?: "info" | "warning" | "error" }> = []
|
||||
let waited = false
|
||||
const value = {
|
||||
async waitForIdle() {
|
||||
waited = true
|
||||
},
|
||||
modelRegistry: {
|
||||
async getApiKeyForProvider(provider: string) {
|
||||
assert.equal(provider, "commandcode")
|
||||
return registryKey
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
notify(message: string, type?: "info" | "warning" | "error") {
|
||||
notifications.push({ message, type })
|
||||
},
|
||||
},
|
||||
} satisfies QuotaCommandContext
|
||||
return { value, notifications, waited: () => waited }
|
||||
}
|
||||
|
||||
const quotaResult: CommandCodeQuotaResult = {
|
||||
ok: true,
|
||||
quota: {
|
||||
account: { login: "alice", orgId: null },
|
||||
credits: null,
|
||||
subscription: null,
|
||||
summary: { totalCost: 1, totalCount: 2 },
|
||||
},
|
||||
}
|
||||
|
||||
describe("commandcode-quota command", () => {
|
||||
it("registers the command and resolves OMP placeholders through the fallback key", async () => {
|
||||
const pi = new CommandApiDouble()
|
||||
let requestKey = ""
|
||||
let requestBase = ""
|
||||
registerCommandCodeQuota(pi, {
|
||||
apiBase: "https://api.commandcode.ai",
|
||||
getConfiguredKey: () => "fallback-key",
|
||||
fetchQuota: async (options) => {
|
||||
requestKey = options.apiKey
|
||||
requestBase = options.baseUrl ?? ""
|
||||
return quotaResult
|
||||
},
|
||||
})
|
||||
|
||||
assert.ok(pi.handler)
|
||||
const ctx = context("$COMMANDCODE_API_KEY")
|
||||
await pi.handler("", ctx.value)
|
||||
assert.equal(ctx.waited(), true)
|
||||
assert.equal(requestKey, "fallback-key")
|
||||
assert.equal(requestBase, "https://api.commandcode.ai")
|
||||
assert.equal(ctx.notifications.at(-1)?.type, "info")
|
||||
assert.match(ctx.notifications.at(-1)?.message ?? "", /Requests: 2/)
|
||||
})
|
||||
|
||||
it("warns without calling the endpoint when no API key is available", async () => {
|
||||
const pi = new CommandApiDouble()
|
||||
let called = false
|
||||
registerCommandCodeQuota(pi, {
|
||||
apiBase: "https://api.commandcode.ai",
|
||||
getConfiguredKey: () => undefined,
|
||||
fetchQuota: async () => {
|
||||
called = true
|
||||
return quotaResult
|
||||
},
|
||||
})
|
||||
|
||||
assert.ok(pi.handler)
|
||||
const ctx = context(undefined)
|
||||
await pi.handler("", ctx.value)
|
||||
assert.equal(called, false)
|
||||
assert.equal(ctx.notifications.at(-1)?.type, "warning")
|
||||
assert.match(ctx.notifications.at(-1)?.message ?? "", /requires an API key/)
|
||||
})
|
||||
|
||||
it("redacts endpoint failures before notifying the host", async () => {
|
||||
const pi = new CommandApiDouble()
|
||||
registerCommandCodeQuota(pi, {
|
||||
apiBase: "https://api.commandcode.ai",
|
||||
getConfiguredKey: () => "real-key",
|
||||
fetchQuota: async () => ({
|
||||
ok: false,
|
||||
error: { kind: "http", message: "api_key=supersecretvalue123456 failed" },
|
||||
}),
|
||||
})
|
||||
|
||||
assert.ok(pi.handler)
|
||||
const ctx = context("real-key")
|
||||
await pi.handler("", ctx.value)
|
||||
assert.equal(ctx.notifications.at(-1)?.type, "error")
|
||||
assert.doesNotMatch(ctx.notifications.at(-1)?.message ?? "", /supersecret/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,417 @@
|
||||
/**
|
||||
* Unit tests for the Command Code quota layer (src/quota.ts).
|
||||
*
|
||||
* These are hermetic: no pi runtime and no network. Fetching is exercised with
|
||||
* a mocked `fetchImpl`, while parsing and formatting are pure function checks.
|
||||
*/
|
||||
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
|
||||
import { formatQuota, formatWindowLimits } from "../src/quota-format.ts"
|
||||
import {
|
||||
DEFAULT_API_BASE,
|
||||
fetchCommandCodeQuota,
|
||||
redactValue,
|
||||
windowLimitsFromCredits,
|
||||
} from "../src/quota.ts"
|
||||
import type {
|
||||
CommandCodeCredits,
|
||||
CommandCodeQuota,
|
||||
CommandCodeWindowLimit,
|
||||
} from "../src/quota-types.ts"
|
||||
|
||||
function jsonResponse(body: unknown, status = 200): Response {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { "content-type": "application/json" },
|
||||
})
|
||||
}
|
||||
|
||||
function okFetch(handlers: Record<string, unknown>) {
|
||||
const urls: string[] = []
|
||||
const fetchImpl = async (input: RequestInfo | URL): Promise<Response> => {
|
||||
const url = String(input)
|
||||
urls.push(url)
|
||||
for (const [needle, body] of Object.entries(handlers)) {
|
||||
if (url.includes(needle)) return jsonResponse(body)
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`)
|
||||
}
|
||||
return { fetchImpl, urls: () => urls }
|
||||
}
|
||||
|
||||
describe("Command Code quota", () => {
|
||||
it("parses window limits from the credits windowLimits object", () => {
|
||||
const limits = windowLimitsFromCredits({
|
||||
limited: true,
|
||||
// resetAt as reported by the live API: milliseconds since epoch.
|
||||
fiveHour: { used: 8, cap: 14, resetAt: 1_700_000_000_000 },
|
||||
weekly: { used: 30, cap: 35, resetAt: 1_700_000_000_000 },
|
||||
})
|
||||
assert.deepEqual(limits, [
|
||||
{ window: "fiveHour", used: 8, cap: 14, resetAt: 1_700_000_000 },
|
||||
{ window: "weekly", used: 30, cap: 35, resetAt: 1_700_000_000 },
|
||||
])
|
||||
})
|
||||
|
||||
it("skips empty window limit entries", () => {
|
||||
const limits = windowLimitsFromCredits({
|
||||
limited: false,
|
||||
fiveHour: { used: 0, cap: 0, resetAt: null },
|
||||
weekly: { used: 0, cap: 0, resetAt: null },
|
||||
})
|
||||
assert.deepEqual(limits, [])
|
||||
})
|
||||
|
||||
it("parses resetAt as numeric string or ISO timestamp string", () => {
|
||||
const limits = windowLimitsFromCredits({
|
||||
fiveHour: { used: 1, cap: 2, resetAt: "1700000000000" },
|
||||
weekly: { used: 1, cap: 2, resetAt: "2023-11-14T22:13:20.000Z" },
|
||||
})
|
||||
// numeric ms string -> epoch seconds; ISO string -> epoch seconds
|
||||
assert.equal(limits[0]?.resetAt, 1_700_000_000)
|
||||
assert.equal(limits[1]?.resetAt, 1_700_000_000)
|
||||
})
|
||||
|
||||
it("renders valid zero usage without claiming an unknown billing period", () => {
|
||||
const quota: CommandCodeQuota = {
|
||||
account: { login: "alice", orgId: null },
|
||||
credits: null,
|
||||
subscription: null,
|
||||
summary: { totalCost: 0, totalCount: 0 },
|
||||
}
|
||||
const output = formatQuota(quota, () => 1_700_000_000_000)
|
||||
assert.match(output, /Usage\n/)
|
||||
assert.doesNotMatch(output, /billing period/)
|
||||
assert.match(output, /Requests: 0/)
|
||||
})
|
||||
|
||||
it("formats window limits with percentage and reset clock", () => {
|
||||
const limits: CommandCodeWindowLimit[] = [
|
||||
{ window: "fiveHour", used: 7, cap: 14, resetAt: 1_700_000_000 },
|
||||
{ window: "weekly", used: 0, cap: 35, resetAt: null },
|
||||
]
|
||||
const lines = formatWindowLimits(limits)
|
||||
assert.match(lines[0] ?? "", /^5-hour: 7\.00 \/ 14\.00 credits \(50% used\) \(resets/)
|
||||
assert.match(lines[1] ?? "", /^Weekly: 0\.00 \/ 35\.00 credits \(0% used\)/)
|
||||
})
|
||||
|
||||
it("uses the injected clock for the reset countdown", () => {
|
||||
const limit: CommandCodeWindowLimit = {
|
||||
window: "fiveHour",
|
||||
used: 7,
|
||||
cap: 14,
|
||||
resetAt: 1_700_000_000, // seconds since epoch
|
||||
}
|
||||
// now() shortly before reset -> a short "in Nm" countdown
|
||||
const soon = formatWindowLimits([limit], () => 1_699_999_000 * 1000)[0]
|
||||
assert.match(soon ?? "", /\(resets in \d+m\)/)
|
||||
// already past reset -> "soon"
|
||||
const past = formatWindowLimits([limit], () => 1_700_100_000 * 1000)[0]
|
||||
assert.match(past ?? "", /\(resets soon\)/)
|
||||
})
|
||||
|
||||
it("fetches and normalizes the full quota snapshot", async () => {
|
||||
const { fetchImpl, urls } = okFetch({
|
||||
whoami: { user: { userName: "alice" }, org: { id: "org_1", login: "alice-inc" } },
|
||||
credits: {
|
||||
credits: {
|
||||
monthlyCredits: 40,
|
||||
purchasedCredits: 10,
|
||||
freeCredits: 5,
|
||||
planId: "pro",
|
||||
},
|
||||
windowLimits: {
|
||||
fiveHour: { used: 8, cap: 16, resetAt: 1_700_000_000_000 },
|
||||
weekly: { used: 20, cap: 40, resetAt: null },
|
||||
},
|
||||
},
|
||||
subscriptions: {
|
||||
data: {
|
||||
planId: "pro",
|
||||
status: "active",
|
||||
currentPeriodStart: "2026-01-01T00:00:00Z",
|
||||
currentPeriodEnd: "2026-02-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
summary: { totalCost: 12.34, totalCount: 1500 },
|
||||
})
|
||||
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
|
||||
assert.equal(result.ok, true)
|
||||
if (!result.ok) return
|
||||
|
||||
assert.equal(result.quota.account.login, "alice-inc")
|
||||
assert.equal(result.quota.account.orgId, "org_1")
|
||||
assert.deepEqual(result.quota.credits?.remainingCredits, 55)
|
||||
assert.equal(result.quota.credits?.windowLimits.length, 2)
|
||||
assert.equal(result.quota.subscription?.planId, "pro")
|
||||
assert.equal(result.quota.summary?.totalCost, 12.34)
|
||||
|
||||
// Regression: requested URLs must carry the base exactly once (no
|
||||
// double prefix), and all hit the alpha usage endpoints.
|
||||
const fetched = urls()
|
||||
assert.equal(fetched.length, 4)
|
||||
for (const url of fetched) {
|
||||
assert.ok(
|
||||
/^https:\/\/api\.commandcode\.ai\/alpha\//.test(url),
|
||||
`expected base-prefixed alpha URL, got: ${url}`,
|
||||
)
|
||||
assert.equal((url.match(/https:\/\//g) ?? []).length, 1)
|
||||
assert.equal(url.includes(`${DEFAULT_API_BASE}${DEFAULT_API_BASE}`), false)
|
||||
}
|
||||
})
|
||||
|
||||
it("rejects unrecognized successful endpoint schemas instead of displaying zero usage", async () => {
|
||||
const fetchImpl = async (input: RequestInfo | URL): Promise<Response> => {
|
||||
const url = String(input)
|
||||
if (url.includes("whoami")) return jsonResponse({ user: { userName: "alice" }, org: null })
|
||||
return jsonResponse({ changed: "schema" })
|
||||
}
|
||||
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
|
||||
assert.equal(result.ok, false)
|
||||
if (result.ok) return
|
||||
assert.equal(result.error.kind, "http")
|
||||
assert.match(result.error.message, /no recognized usage data/i)
|
||||
})
|
||||
|
||||
it("degrades gracefully when individual billing endpoints fail", async () => {
|
||||
const fetchImpl = async (input: RequestInfo | URL): Promise<Response> => {
|
||||
const url = String(input)
|
||||
if (url.includes("whoami")) return jsonResponse({ user: { userName: "alice" }, org: null })
|
||||
if (url.includes("summary")) return jsonResponse({ totalCost: 3.0, totalCount: 10 })
|
||||
if (url.includes("credits") || url.includes("subscriptions")) {
|
||||
return jsonResponse({ error: "boom" }, 500)
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`)
|
||||
}
|
||||
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
|
||||
assert.equal(result.ok, true)
|
||||
if (!result.ok) return
|
||||
assert.equal(result.quota.credits, null)
|
||||
assert.equal(result.quota.summary?.totalCost, 3.0)
|
||||
assert.deepEqual(result.quota.unavailable, ["credits", "subscription"])
|
||||
assert.match(formatQuota(result.quota), /Unavailable: credits, subscription/)
|
||||
// Optional aggregate tokens are parsed when the summary reports them.
|
||||
assert.equal(result.quota.summary?.totalTokens, undefined)
|
||||
})
|
||||
|
||||
it("degrades on thrown network failures from optional endpoints, not just HTTP 5xx", async () => {
|
||||
const fetchImpl = async (input: RequestInfo | URL): Promise<Response> => {
|
||||
const url = String(input)
|
||||
if (url.includes("whoami")) return jsonResponse({ user: { userName: "alice" }, org: null })
|
||||
if (url.includes("summary")) return jsonResponse({ totalCost: 3.0, totalCount: 10 })
|
||||
if (url.includes("credits")) throw new Error("network down")
|
||||
if (url.includes("subscriptions")) return jsonResponse({ data: { planId: "pro" } })
|
||||
throw new Error(`Unexpected URL: ${url}`)
|
||||
}
|
||||
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
|
||||
assert.equal(result.ok, true)
|
||||
if (!result.ok) return
|
||||
assert.equal(result.quota.credits, null)
|
||||
assert.equal(result.quota.subscription?.planId, "pro")
|
||||
assert.equal(result.quota.summary?.totalCost, 3.0)
|
||||
assert.deepEqual(result.quota.unavailable, ["credits"])
|
||||
})
|
||||
|
||||
it("fails the command when the summary endpoint rejects auth/permission", async () => {
|
||||
const fetchImpl = async (input: RequestInfo | URL): Promise<Response> => {
|
||||
const url = String(input)
|
||||
if (url.includes("whoami")) return jsonResponse({ user: { userName: "alice" }, org: null })
|
||||
if (url.includes("credits")) return jsonResponse({ credits: { monthlyCredits: 5 } })
|
||||
if (url.includes("subscriptions")) return jsonResponse({ data: { planId: "pro" } })
|
||||
if (url.includes("summary")) return jsonResponse({ error: "nope" }, 403)
|
||||
throw new Error(`Unexpected URL: ${url}`)
|
||||
}
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
|
||||
assert.equal(result.ok, false)
|
||||
if (result.ok) return
|
||||
assert.equal(result.error.kind, "http")
|
||||
assert.match(result.error.message, /summary/)
|
||||
})
|
||||
|
||||
it("does not treat 429 on billing endpoints as fatal", async () => {
|
||||
const fetchImpl = async (input: RequestInfo | URL): Promise<Response> => {
|
||||
const url = String(input)
|
||||
if (url.includes("whoami")) return jsonResponse({ user: { userName: "alice" }, org: null })
|
||||
if (url.includes("summary")) return jsonResponse({ totalCost: 3.0, totalCount: 10 })
|
||||
if (url.includes("credits") || url.includes("subscriptions")) {
|
||||
return jsonResponse({ error: "rate limited" }, 429)
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`)
|
||||
}
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
|
||||
assert.equal(result.ok, true)
|
||||
if (!result.ok) return
|
||||
assert.equal(result.quota.credits, null)
|
||||
assert.equal(result.quota.summary?.totalCost, 3.0)
|
||||
})
|
||||
|
||||
it("sends extra headers (ZDR) on quota requests", async () => {
|
||||
let sent: Headers | undefined
|
||||
const fetchImpl = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
sent = (init?.headers as Headers) ?? undefined
|
||||
const url = String(input)
|
||||
if (url.includes("whoami")) return jsonResponse({ user: { userName: "alice" }, org: null })
|
||||
if (url.includes("credits")) return jsonResponse({ credits: { monthlyCredits: 5 } })
|
||||
if (url.includes("subscriptions")) return jsonResponse({ data: { planId: "pro" } })
|
||||
if (url.includes("summary")) return jsonResponse({ totalCost: 1, totalCount: 1 })
|
||||
throw new Error(`Unexpected URL: ${url}`)
|
||||
}
|
||||
const result = await fetchCommandCodeQuota({
|
||||
apiKey: "cc_test_key",
|
||||
fetchImpl,
|
||||
extraHeaders: { "x-cmd-zdr": "1" },
|
||||
})
|
||||
assert.equal(result.ok, true)
|
||||
const headers = new Headers(sent)
|
||||
assert.equal(headers.get("x-cmd-zdr"), "1")
|
||||
})
|
||||
|
||||
it("parses optional token count and key name when present", async () => {
|
||||
const { fetchImpl } = okFetch({
|
||||
whoami: { user: { userName: "alice", keyName: "Pi Agent" }, org: null },
|
||||
credits: { credits: { monthlyCredits: 5, purchasedCredits: 0, freeCredits: 0 } },
|
||||
subscriptions: { data: { planId: "pro", status: "active" } },
|
||||
summary: { totalCost: 1.06, totalCount: 654, totalTokens: 74_200_000 },
|
||||
})
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
|
||||
assert.equal(result.ok, true)
|
||||
if (!result.ok) return
|
||||
assert.equal(result.quota.summary?.totalTokens, 74_200_000)
|
||||
assert.equal(result.quota.account.keyName, "Pi Agent")
|
||||
})
|
||||
|
||||
it("rejects missing API keys as a config error", async () => {
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "" })
|
||||
assert.equal(result.ok, false)
|
||||
if (result.ok) return
|
||||
assert.equal(result.error.kind, "config")
|
||||
})
|
||||
|
||||
it("fails with a config-style error when the API key is rejected", async () => {
|
||||
const fetchImpl = async (input: RequestInfo | URL): Promise<Response> => {
|
||||
if (String(input).includes("whoami")) return jsonResponse({ error: "unauthorized" }, 401)
|
||||
throw new Error(`Unexpected URL: ${input}`)
|
||||
}
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_bad_key", fetchImpl })
|
||||
assert.equal(result.ok, false)
|
||||
if (result.ok) return
|
||||
assert.equal(result.error.kind, "http")
|
||||
assert.match(result.error.message, /401/)
|
||||
})
|
||||
|
||||
it("formats a complete quota snapshot into readable output", () => {
|
||||
const quota: CommandCodeQuota = {
|
||||
account: { login: "alice-inc", orgId: "org_1" },
|
||||
credits: {
|
||||
monthlyCredits: 40,
|
||||
purchasedCredits: 10,
|
||||
freeCredits: 5,
|
||||
remainingCredits: 55,
|
||||
windowLimits: [
|
||||
{ window: "fiveHour", used: 8, cap: 16, resetAt: null },
|
||||
{ window: "weekly", used: 20, cap: 40, resetAt: null },
|
||||
],
|
||||
} satisfies CommandCodeCredits,
|
||||
subscription: {
|
||||
planId: "pro",
|
||||
status: "active",
|
||||
currentPeriodStart: "2026-01-01T00:00:00Z",
|
||||
currentPeriodEnd: "2026-02-01T00:00:00Z",
|
||||
},
|
||||
summary: { totalCost: 12.34, totalCount: 1500 },
|
||||
}
|
||||
|
||||
const output = formatQuota(quota, () => 1_700_000_000_000)
|
||||
assert.doesNotMatch(output, /Command Code quota —/)
|
||||
assert.match(output, /Credits/)
|
||||
assert.match(output, /Remaining: \$55\.00 of \$67\.34/)
|
||||
assert.match(output, /Used: \$12\.34/)
|
||||
assert.match(output, /Sources: monthly \$40\.00 \/ purchased \$10\.00 \/ free \$5\.00/)
|
||||
assert.match(output, /Plan: pro \(active\)/)
|
||||
assert.match(output, /Usage \(billing period\)/)
|
||||
assert.match(output, /Cost: \$12\.34/)
|
||||
assert.match(output, /Requests: 1,500/)
|
||||
assert.match(output, /Account/)
|
||||
assert.match(output, /alice-inc/)
|
||||
assert.match(output, /5-hour: 8\.00 \/ 16\.00 credits/)
|
||||
assert.match(output, /Weekly: 20\.00 \/ 40\.00 credits/)
|
||||
assert.match(output, /https:\/\/commandcode\.ai\/usage/)
|
||||
})
|
||||
|
||||
it("redacts token-like values from error messages", () => {
|
||||
// 16+ char run after a credential key is redacted by the shared redactor.
|
||||
assert.equal(redactValue("api_key=abcdefghijklmnop123456"), "api_key=[redacted]")
|
||||
assert.equal(redactValue("Bearer user_12345678901234 failed"), "Bearer [redacted] failed")
|
||||
})
|
||||
|
||||
it("redacts named credential fields and short tokens from error bodies", () => {
|
||||
// Credential key-value forms (with = or : separator) are redacted.
|
||||
assert.equal(redactValue("api_key=abc123"), "api_key=[redacted]")
|
||||
assert.equal(
|
||||
redactValue("authorization=Basic abc:def failed"),
|
||||
"authorization=[redacted] abc:def failed",
|
||||
)
|
||||
assert.equal(redactValue("user_123456789 failed"), "[redacted] failed")
|
||||
assert.equal(redactValue("cc_abcdefghijkl failed"), "[redacted] failed")
|
||||
assert.equal(
|
||||
redactValue("token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.secret"),
|
||||
"token=[redacted]",
|
||||
)
|
||||
})
|
||||
|
||||
it("redacts JSON-quoted credential fields in error bodies", () => {
|
||||
assert.equal(
|
||||
redactValue('{"apiKey":"sk-abcdefghijklmnop123456","ok":true}'),
|
||||
'{"apiKey":"[redacted]","ok":true}',
|
||||
)
|
||||
assert.equal(
|
||||
redactValue('{"error":"bad","access_token":"opaque-internal-token-12345"}'),
|
||||
'{"error":"bad","access_token":"[redacted]"}',
|
||||
)
|
||||
assert.equal(
|
||||
redactValue('{"authorization":"Bearer user_1234"}'),
|
||||
'{"authorization":"[redacted]"}',
|
||||
)
|
||||
})
|
||||
|
||||
it("redacts thrown network errors from the outer catch path", async () => {
|
||||
const fetchImpl = async (_input: RequestInfo | URL): Promise<Response> => {
|
||||
throw new Error("connection reset by proxy api_key=supersecretvalue123456")
|
||||
}
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl })
|
||||
assert.equal(result.ok, false)
|
||||
if (result.ok) return
|
||||
assert.doesNotMatch(result.error.message, /supersecretvalue123456/)
|
||||
assert.match(result.error.kind, /network/)
|
||||
})
|
||||
|
||||
it("honors the overall deadline once it has already fired (no phase starts after abort)", async () => {
|
||||
const start = Date.now()
|
||||
const fetchImpl = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
const url = String(input)
|
||||
if (url.includes("whoami")) {
|
||||
// Never resolve; let the per-request controller abort it at timeoutMs.
|
||||
return new Promise<Response>((_resolve, reject) => {
|
||||
init?.signal?.addEventListener("abort", () =>
|
||||
reject(Object.assign(new Error("aborted"), { name: "AbortError" })),
|
||||
)
|
||||
})
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`)
|
||||
}
|
||||
|
||||
const result = await fetchCommandCodeQuota({ apiKey: "cc_test_key", fetchImpl, timeoutMs: 30 })
|
||||
const elapsed = Date.now() - start
|
||||
assert.equal(result.ok, false)
|
||||
if (result.ok) return
|
||||
assert.equal(result.error.kind, "timeout")
|
||||
// The overall deadline governs the whole command; no phase may add ~30ms on top.
|
||||
assert.ok(elapsed < 200, `elapsed ${elapsed}ms exceeded overall deadline`)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user