feat(commandcode): quota usage dashboard, CLI-parity request, and connect timeout fixes

- Add CommandCode usage handler mirroring the official CLI /usage
  (whoami → credits/subscriptions → summary) with 5h/weekly/monthly
  quota rows, registered in services/usage.js and registry config
- Parse commandcode quota rows in ProviderLimits (remainingPercentage + $ unit)
- Match official CLI request shape: x-command-code-version 1.10.0,
  User-Agent cli, and config.environment '${platform}-${arch}, Node.js ${version}'
- Fix connect timeout unit confusion: both profile and provider pages
  now use ms with a 1s minimum guard (prevents 60ms footgun)
- Fix fetchT0 ReferenceError in base.js error path and log fetch
  diagnostics only on upstream failure
- Quota Tracker defaults to the Active account filter
- Ignore .commandcode/ CLI local state
This commit is contained in:
2026-08-04 22:34:13 +07:00
parent fcd3dcb409
commit c5ce1ef140
11 changed files with 483 additions and 14 deletions

3
.gitignore vendored
View File

@@ -86,3 +86,6 @@ graphify-out/*
.codegraph/
.PR/
.next-analyze/*
# CommandCode CLI local state (auth/taste/projects)
.commandcode/

View File

@@ -136,10 +136,11 @@ export class BaseExecutor {
const timeoutMs = await resolveProviderTimeoutMs(this.provider, this.config?.timeoutMs, FETCH_CONNECT_TIMEOUT_MS);
const connectTimer = setTimeout(() => connectCtrl.abort(new Error("fetch connect timeout")), timeoutMs);
const mergedSignal = signal ? AbortSignal.any([signal, connectCtrl.signal]) : connectCtrl.signal;
let fetchT0 = 0;
try {
const bodyStr = JSON.stringify(transformedBody);
const fetchT0 = Date.now();
fetchT0 = Date.now();
dbg("FETCH", `${this.provider.toUpperCase()}${url} | body=${bodyStr.length}B | connectTimeout=${timeoutMs}ms`);
const response = await proxyAwareFetch(url, {
method: "POST",
@@ -165,6 +166,11 @@ export class BaseExecutor {
clearTimeout(connectTimer);
lastError = error;
const isConnectTimeout = connectCtrl.signal.aborted && error.name === "AbortError";
// Error diagnostic — only logs on actual upstream failure. Distinguishes
// undici connect timeout (UND_ERR_CONNECT_TIMEOUT), DNS (ENOTFOUND),
// refused (ECONNREFUSED) vs our own connectCtrl abort (AbortError).
const cause = error?.cause || {};
console.log(`[FETCH-DIAG] ${this.provider} fetch error | name=${error.name} | code=${error.code ?? cause?.code ?? "none"} | msg=${String(error.message).slice(0, 120)} | connectTimeout=${timeoutMs}ms | elapsed=${Date.now() - fetchT0}ms`);
dbg("FETCH", `${this.provider.toUpperCase()}${error.name}: ${error.message}${isConnectTimeout ? " (connect timeout)" : ""}`);
// Connect timeout is internal — convert to retryable network error, don't propagate AbortError
if (error.name === "AbortError" && !isConnectTimeout) throw error;

View File

@@ -23,9 +23,24 @@ export default {
format: "commandcode",
forceStream: true,
headers: {
"x-command-code-version": "0.25.7",
"x-command-code-version": "1.10.0",
"x-cli-environment": "cli",
"User-Agent": "cli",
},
// Quota/billing endpoints (same alpha API the official CLI /usage calls).
// whoami resolves orgId; credits+subscription+usage/summary then report the
// 5-hour/weekly windows, plan, and period credits. See services/usage/commandcode.js.
usage: {
baseUrl: "https://api.commandcode.ai",
whoamiUrl: "/alpha/whoami",
creditsUrl: "/alpha/billing/credits",
subscriptionsUrl: "/alpha/billing/subscriptions",
summaryUrl: "/alpha/usage/summary",
},
},
features: {
usage: true,
usageApikey: true,
},
models: [
{ id: "deepseek/deepseek-v4-pro", name: "DeepSeek V4 Pro" },

View File

@@ -15,6 +15,7 @@ import { getXaiUsage } from "./usage/xai.js";
import { getGrokCliUsage } from "./usage/grok-cli.js";
import { getKimiUsage } from "./usage/kimi.js";
import { getDeepseekUsage } from "./usage/deepseek.js";
import { getCommandCodeUsage } from "./usage/commandcode.js";
import {
getQwenUsage,
getIflowUsage,
@@ -51,6 +52,7 @@ const USAGE_HANDLERS = {
"grok-cli": (c) => getGrokCliUsage(c.accessToken, c.providerSpecificData, c.proxyOptions),
kimi: (c) => getKimiUsage(c.accessToken, c.apiKey, c.proxyOptions, c.providerSpecificData),
deepseek: (c) => getDeepseekUsage(c.apiKey, c.proxyOptions),
commandcode: (c) => getCommandCodeUsage(c.apiKey, c.proxyOptions),
};
export async function getUsageForProvider(connection, proxyOptions = null) {

View File

@@ -0,0 +1,203 @@
/**
* CommandCode usage handler
*
* Mirrors the official command-code CLI /usage command: it calls the alpha API
* to surface the 5-hour + weekly usage windows, the subscription plan, and the
* credits consumed in the current billing period.
*
* GET /alpha/whoami → org.id (org-scoped billing; null for personal)
* GET /alpha/billing/credits → { credits: { monthlyCredits, purchasedCredits,
* freeCredits }, windowLimits: { fiveHour, weekly } }
* GET /alpha/billing/subscriptions → { data: { planId, currentPeriodStart, ... } }
* GET /alpha/usage/summary?since= → period token/cost totals
*
* The CLI fetches whoami first (for orgId), then credits + subscription in
* parallel, then the summary with since = currentPeriodStart. We keep the same
* order/dependencies: window limits live on credits, and the plan period start
* determines the summary window.
*/
import { proxyAwareFetch } from "../../utils/proxyFetch.js";
import { U, parseResetTime } from "./shared.js";
const USAGE = U("commandcode");
const BASE = USAGE.baseUrl || "https://api.commandcode.ai";
const WHOAMI_URL = BASE + (USAGE.whoamiUrl || "/alpha/whoami");
const CREDITS_URL = BASE + (USAGE.creditsUrl || "/alpha/billing/credits");
const SUBSCRIPTIONS_URL =
BASE + (USAGE.subscriptionsUrl || "/alpha/billing/subscriptions");
const SUMMARY_URL = BASE + (USAGE.summaryUrl || "/alpha/usage/summary");
function buildHeaders(token) {
return {
Authorization: `Bearer ${token}`,
Accept: "application/json",
};
}
/** Build a normalized quota row. `unit` is "$" — the API reports currency credits. */
function makeQuota({ used, total, resetAt, unlimited = false, unit = "$" }) {
const safeTotal = Math.max(0, Number(total) || 0);
const safeUsed = Math.max(0, Number(used) || 0);
if (unlimited || safeTotal === 0) {
return {
used: safeUsed,
total: 0,
remainingPercentage: unlimited ? 100 : 0,
resetAt: resetAt || null,
unit,
unlimited: true,
};
}
const remaining = Math.max(0, safeTotal - safeUsed);
const remainingPercentage = (remaining / safeTotal) * 100;
return {
used: safeUsed,
total: safeTotal,
remainingPercentage,
resetAt: resetAt || null,
unit,
unlimited: false,
};
}
/**
* @param {string} apiKey - commandcode API key (user_...)
* @param {object|null} proxyOptions
*/
export async function getCommandCodeUsage(apiKey, proxyOptions = null) {
if (!apiKey) {
return { message: "CommandCode credential not available." };
}
const headers = buildHeaders(apiKey);
try {
// whoami resolves the org id (billing is org-scoped; null for personal).
const whoamiRes = await proxyAwareFetch(
WHOAMI_URL,
{ method: "GET", headers },
proxyOptions,
);
if (whoamiRes.status === 401 || whoamiRes.status === 403) {
return { message: "CommandCode credential invalid or expired." };
}
if (!whoamiRes.ok) {
return { message: `CommandCode whoami API error (${whoamiRes.status}).` };
}
const whoami = await whoamiRes.json().catch(() => null);
const orgId = whoami?.org?.id ?? null;
const orgQuery = orgId ? `?orgId=${encodeURIComponent(orgId)}` : "";
const [creditsRes, subsRes] = await Promise.all([
proxyAwareFetch(
CREDITS_URL + orgQuery,
{ method: "GET", headers },
proxyOptions,
),
proxyAwareFetch(
SUBSCRIPTIONS_URL + orgQuery,
{ method: "GET", headers },
proxyOptions,
),
]);
if (
creditsRes.status === 401 ||
creditsRes.status === 403 ||
subsRes.status === 401 ||
subsRes.status === 403
) {
return { message: "CommandCode credential invalid or expired." };
}
if (!creditsRes.ok) {
return {
message: `CommandCode credits API error (${creditsRes.status}).`,
};
}
const credits = await creditsRes.json().catch(() => null);
const subs = await subsRes.json().catch(() => null);
const subData = subs?.data;
const planId = subData?.planId ?? null;
const periodStart = subData?.currentPeriodStart ?? null;
// Summary needs `since`; the CLI falls back to first-of-month when the
// subscription period start is unavailable.
const since = periodStart || firstOfMonth();
const summaryRes = await proxyAwareFetch(
`${SUMMARY_URL}?since=${encodeURIComponent(since)}`,
{ method: "GET", headers },
proxyOptions,
);
const summary = summaryRes.ok
? await summaryRes.json().catch(() => null)
: null;
const quotas = {};
const windowLimits = credits?.windowLimits || {};
const fiveHour = windowLimits.fiveHour;
if (fiveHour && Number(fiveHour.cap) > 0) {
quotas["5-hour window"] = makeQuota({
used: fiveHour.used,
total: fiveHour.cap,
resetAt: parseResetTime(fiveHour.resetAt),
});
}
const weekly = windowLimits.weekly;
if (weekly && Number(weekly.cap) > 0) {
quotas["Weekly window"] = makeQuota({
used: weekly.used,
total: weekly.cap,
resetAt: parseResetTime(weekly.resetAt),
});
}
// Monthly credits consumed this billing period (from summary when present,
// else the credits object's monthlyCredits as a fallback).
const monthlyUsed =
typeof summary?.totalCredits === "number"
? summary.totalCredits
: typeof credits?.credits?.monthlyCredits === "number"
? credits.credits.monthlyCredits
: 0;
const monthlyTotal =
typeof credits?.credits?.monthlyCredits === "number"
? credits.credits.monthlyCredits
: 0;
if (monthlyTotal > 0 || monthlyUsed > 0) {
quotas["Monthly credits"] = makeQuota({
used: monthlyUsed,
total: monthlyTotal,
resetAt: periodStart ? undefined : null,
});
}
if (Object.keys(quotas).length === 0) {
return {
plan: planId || "CommandCode",
message: "CommandCode connected, but no quota was reported.",
quotas: {},
};
}
return {
plan: planId || "CommandCode",
quotas,
periodBasis: summary?.periodBasis || "billing-period",
};
} catch (error) {
return { message: `CommandCode usage error: ${error.message}` };
}
}
function firstOfMonth() {
const now = new Date();
return new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
}

View File

@@ -191,13 +191,17 @@ export function openaiToCommandCodeRequest(
const today = new Date().toISOString().slice(0, 10);
// environment format mirrors the official command-code CLI (getEnvironmentInfo):
// `${platform}-${arch}, Node.js ${version}` (e.g. "darwin-arm64, Node.js v24.16.0").
const environment = `${process.platform}-${process.arch}, Node.js ${process.version}`;
return {
threadId: randomUUID(),
memory: "",
config: {
workingDir: process.cwd(),
date: today,
environment: process.platform,
environment,
structure: [],
isGitRepo: false,
currentBranch: "",

View File

@@ -288,9 +288,11 @@ export default function ProfilePage() {
const handleGlobalTimeoutChange = async (e) => {
const raw = e.target.value.replace(/[^0-9]/g, "");
const numTimeout = parseInt(raw, 10);
// Enforce a sane minimum (1s) so a stray "60" never becomes a 60ms
// connect timeout — same guard as the per-provider timeout input.
const patchValue =
raw !== "" && Number.isFinite(numTimeout) && numTimeout > 0
? numTimeout
? Math.max(1000, numTimeout)
: null;
try {

View File

@@ -438,7 +438,7 @@ export default function ProviderDetailPage() {
const thinkingCfg =
(settingsData.providerThinking || {})[providerId] || {};
setThinkingMode(thinkingCfg.mode || "auto");
// Load per-provider connect timeout
// Load per-provider connect timeout (ms)
const timeoutCfg =
(settingsData.providerTimeouts || {})[providerId] || {};
setProviderTimeout(
@@ -577,15 +577,13 @@ export default function ProviderDetailPage() {
const settingsData = settingsRes.ok ? await settingsRes.json() : {};
const current = settingsData.providerTimeouts || {};
const updated = { ...current };
if (!ms || ms === "") {
delete updated[providerId];
const timeoutMs = parseInt(ms, 10);
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
// Stored in ms; enforce a sane minimum (1s) so a stray "60" means
// 60 SECONDS-worth of protection — never a 60ms connect timeout.
updated[providerId] = { timeoutMs: Math.max(1000, timeoutMs) };
} else {
const timeoutMs = parseInt(ms, 10);
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
updated[providerId] = { timeoutMs };
} else {
delete updated[providerId];
}
delete updated[providerId];
}
await fetch("/api/settings", {
method: "PATCH",

View File

@@ -147,7 +147,7 @@ export default function ProviderLimits() {
const [proxyPools, setProxyPools] = useState([]);
const [providerFilter, setProviderFilter] = useState("all");
const [providerOptions, setProviderOptions] = useState([]);
const [accountFilter, setAccountFilter] = useState("all");
const [accountFilter, setAccountFilter] = useState("active");
const [quotaSortMode, setQuotaSortMode] = useState("default");
const [quotaVisibility, setQuotaVisibility] = useState({});
const [expiringFirst, setExpiringFirst] = useState(false);

View File

@@ -564,6 +564,24 @@ export function parseQuotaData(provider, data) {
}
break;
case "commandcode":
// CommandCode reports currency credits (5-hour/weekly windows + monthly
// credits) with used/total in dollars. Forward remainingPercentage (the
// UI would otherwise render "$0.05" balances as "0%") and unit "$".
if (data.quotas) {
Object.entries(data.quotas).forEach(([name, quota]) => {
normalizedQuotas.push({
name,
used: quota.used || 0,
total: quota.total || 0,
unit: quota.unit || "$",
resetAt: quota.resetAt || null,
remainingPercentage: quota.remainingPercentage,
});
});
}
break;
default:
// Generic fallback for unknown providers
if (data.quotas) {

View File

@@ -0,0 +1,218 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: vi.fn(),
}));
import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
import { getUsageForProvider } from "../../open-sse/services/usage.js";
import {
USAGE_SUPPORTED_PROVIDERS,
USAGE_APIKEY_PROVIDERS,
} from "../../src/shared/constants/providers.js";
import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";
const BASE = "https://api.commandcode.ai";
const WHOAMI_URL = `${BASE}/alpha/whoami`;
const CREDITS_URL = `${BASE}/alpha/billing/credits`;
const SUBS_URL = `${BASE}/alpha/billing/subscriptions`;
const SUMMARY_URL = `${BASE}/alpha/usage/summary`;
function jsonResponse(body, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
const WHOAMI = { success: true, user: { id: "u1" }, org: null };
const CREDITS = {
credits: {
belowThreshold: false,
creditThreshold: 0,
monthlyCredits: 9.9,
purchasedCredits: 0,
freeCredits: 0,
},
windowLimits: {
limited: true,
exceeded: null,
fiveHour: { used: 0.05, cap: 3, exceeded: false, resetAt: 1785812386064 },
weekly: { used: 0.1, cap: 6, exceeded: false, resetAt: 1786379982640 },
},
};
const SUBS = {
success: true,
data: {
id: "sub_1",
status: "active",
orgId: null,
planId: "individual-go",
currentPeriodStart: "2026-08-03T16:38:16.000Z",
currentPeriodEnd: "2026-09-03T16:38:16.000Z",
},
};
const SUMMARY = {
totalCount: 61,
totalCost: 0.1,
totalCredits: 0.1,
totalMonthlyCredits: 0.1,
periodBasis: "billing-period",
};
describe("commandcode registry usage flags", () => {
it("is listed for apikey quota dashboard", () => {
expect(USAGE_SUPPORTED_PROVIDERS).toContain("commandcode");
expect(USAGE_APIKEY_PROVIDERS).toContain("commandcode");
});
});
describe("getUsageForProvider(commandcode)", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("fetches whoami → credits+subs → summary and maps windows + credits", async () => {
proxyAwareFetch
.mockResolvedValueOnce(jsonResponse(WHOAMI))
.mockResolvedValueOnce(jsonResponse(CREDITS))
.mockResolvedValueOnce(jsonResponse(SUBS))
.mockResolvedValueOnce(jsonResponse(SUMMARY));
const usage = await getUsageForProvider({
provider: "commandcode",
apiKey: "user_cc_test",
});
expect(usage.message).toBeUndefined();
expect(usage.plan).toBe("individual-go");
expect(usage.periodBasis).toBe("billing-period");
expect(proxyAwareFetch).toHaveBeenCalledTimes(4);
const [whoamiUrl, whoamiOpts] = proxyAwareFetch.mock.calls[0];
expect(whoamiUrl).toBe(WHOAMI_URL);
expect(whoamiOpts.headers.Authorization).toBe("Bearer user_cc_test");
// No org → credits/subscriptions called without orgId query
const creditsCall = proxyAwareFetch.mock.calls[1][0];
expect(creditsCall).toBe(CREDITS_URL);
// Summary uses currentPeriodStart as `since`
const summaryCall = proxyAwareFetch.mock.calls[3][0];
expect(summaryCall).toBe(
`${SUMMARY_URL}?since=${encodeURIComponent("2026-08-03T16:38:16.000Z")}`,
);
expect(usage.quotas["5-hour window"]).toMatchObject({
used: 0.05,
total: 3,
resetAt: new Date(1785812386064).toISOString(),
});
expect(usage.quotas["Weekly window"]).toMatchObject({
used: 0.1,
total: 6,
resetAt: new Date(1786379982640).toISOString(),
});
expect(usage.quotas["Monthly credits"]).toMatchObject({
used: 0.1,
total: 9.9,
});
});
it("adds orgId query when whoami returns an org", async () => {
proxyAwareFetch
.mockResolvedValueOnce(
jsonResponse({ success: true, org: { id: "org_1" } }),
)
.mockResolvedValueOnce(jsonResponse(CREDITS))
.mockResolvedValueOnce(jsonResponse(SUBS))
.mockResolvedValueOnce(jsonResponse(SUMMARY));
await getUsageForProvider({
provider: "commandcode",
apiKey: "user_cc_test",
});
expect(proxyAwareFetch.mock.calls[1][0]).toBe(`${CREDITS_URL}?orgId=org_1`);
expect(proxyAwareFetch.mock.calls[2][0]).toBe(`${SUBS_URL}?orgId=org_1`);
});
it("falls back to first-of-month since when subscription has no period start", async () => {
proxyAwareFetch
.mockResolvedValueOnce(jsonResponse(WHOAMI))
.mockResolvedValueOnce(jsonResponse(CREDITS))
.mockResolvedValueOnce(
jsonResponse({ success: true, data: { planId: "individual-go" } }),
)
.mockResolvedValueOnce(jsonResponse(SUMMARY));
await getUsageForProvider({
provider: "commandcode",
apiKey: "user_cc_test",
});
const since = new URL(proxyAwareFetch.mock.calls[3][0]).searchParams.get(
"since",
);
// firstOfMonth() is local-time based; assert the local date is the 1st.
const localDate = new Date(since);
expect(localDate.getDate()).toBe(1);
});
it("returns message on missing key / 401 / non-ok whoami", async () => {
const missing = await getUsageForProvider({ provider: "commandcode" });
expect(missing.message).toMatch(/credential/i);
expect(proxyAwareFetch).not.toHaveBeenCalled();
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ error: "no" }, 401));
const auth = await getUsageForProvider({
provider: "commandcode",
apiKey: "bad",
});
expect(auth.message).toMatch(/invalid|expired/i);
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ error: "x" }, 500));
const err = await getUsageForProvider({
provider: "commandcode",
apiKey: "bad",
});
expect(err.message).toMatch(/whoami/i);
});
});
describe("parseQuotaData(commandcode)", () => {
it("forwards remainingPercentage + unit for window/credit rows", () => {
const rows = parseQuotaData("commandcode", {
plan: "individual-go",
quotas: {
"5-hour window": {
used: 0.05,
total: 3,
remainingPercentage: 98.33,
resetAt: "2026-08-03T22:59:46.064Z",
unit: "$",
},
"Monthly credits": {
used: 0.1,
total: 9.9,
remainingPercentage: 98.99,
resetAt: null,
unit: "$",
},
},
});
expect(rows[0]).toMatchObject({
name: "5-hour window",
used: 0.05,
total: 3,
remainingPercentage: 98.33,
unit: "$",
});
expect(rows[1]).toMatchObject({
name: "Monthly credits",
used: 0.1,
total: 9.9,
remainingPercentage: 98.99,
});
});
});