fix(usage): restore commandcode quota + timeout guards lost in master merge
The -X theirs merge of origin/master (v0.5.69) silently reverted five branch-only hunks because upstream had no conflict-region counterpart and simply won the three-way pick: * services/usage.js: re-register the commandcode USAGE handler + import. Without it the dashboard Quota Tracker fell through to 'Usage API not implemented for commandcode'. The handler module (services/usage/commandcode.js) and registry usage block survived; only the dispatch entry was dropped. * ProviderLimits/utils.js: restore parseQuotaData 'commandcode' case (currency-credit rows need unit "$" + remainingPercentage forwarding, else $0.05 balances render as 0%). * profile + providers/[id] pages: restore Math.max(1000, ...) connect timeout floors so a stray '60' is never interpreted as 60ms. * .gitignore: re-add .commandcode/ CLI local state. Verified: tests/unit/commandcode-usage.test.js (6) and usage-dispatch.test.js (2, asserts every provider routes to a real handler) pass standalone; full unit run 1742 pass / 107 fail vs 1738/111 before this fix (remaining failures pre-existing, unrelated).
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -88,4 +88,7 @@ graphify-out/*
|
||||
.next-analyze/*
|
||||
|
||||
# Kiro local workspace state
|
||||
.kiro/
|
||||
.kiro/
|
||||
|
||||
# CommandCode CLI local state (auth/taste/projects)
|
||||
.commandcode/
|
||||
@@ -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 { getOpenCodeGoUsage } from "./usage/opencode-go.js";
|
||||
import { getGroqUsage } from "./usage/groq.js";
|
||||
import { getZedUsage } from "./usage/zed.js";
|
||||
@@ -60,6 +61,7 @@ const USAGE_HANDLERS = {
|
||||
kimi: (c) => getKimiUsage(c.accessToken, c.apiKey, c.proxyOptions, c.providerSpecificData),
|
||||
"opencode-go": (c) => getOpenCodeGoUsage(c.apiKey, c.proxyOptions),
|
||||
deepseek: (c) => getDeepseekUsage(c.apiKey, c.proxyOptions),
|
||||
commandcode: (c) => getCommandCodeUsage(c.apiKey, c.proxyOptions),
|
||||
groq: (c) => getGroqUsage(c.apiKey, c.proxyOptions),
|
||||
zed: (c) => getZedUsage(c.accessToken, c.providerSpecificData, c.proxyOptions),
|
||||
};
|
||||
|
||||
@@ -295,7 +295,9 @@ export default function ProfilePage() {
|
||||
const handleGlobalTimeoutChange = async (e) => {
|
||||
const raw = e.target.value.replace(/[^0-9]/g, "");
|
||||
const numTimeout = parseInt(raw, 10);
|
||||
const patchValue = (raw !== "" && Number.isFinite(numTimeout) && numTimeout > 0) ? numTimeout : null;
|
||||
// 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) ? Math.max(1000, numTimeout) : null;
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/settings", {
|
||||
|
||||
@@ -326,7 +326,7 @@ export default function ProviderDetailPage() {
|
||||
setProviderStrategy(override.fallbackStrategy || null);
|
||||
setProviderStickyLimit(override.stickyRoundRobinLimit != null ? String(override.stickyRoundRobinLimit) : "1");
|
||||
setProviderNoAuthEnabled(override.enabled !== false);
|
||||
// Load per-provider connect timeout
|
||||
// Load per-provider connect timeout (ms)
|
||||
const timeoutCfg = (settingsData.providerTimeouts || {})[providerId] || {};
|
||||
setProviderTimeout(timeoutCfg.timeoutMs != null ? String(timeoutCfg.timeoutMs) : "");
|
||||
// Load per-provider thinking config
|
||||
@@ -478,8 +478,10 @@ export default function ProviderDetailPage() {
|
||||
delete updated[providerId];
|
||||
} else {
|
||||
const timeoutMs = parseInt(ms, 10);
|
||||
// Stored in ms; enforce a sane minimum (1s) so a stray "60" means
|
||||
// 60 SECONDS-worth of protection — never a 60ms connect timeout.
|
||||
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
||||
updated[providerId] = { timeoutMs };
|
||||
updated[providerId] = { timeoutMs: Math.max(1000, timeoutMs) };
|
||||
} else {
|
||||
delete updated[providerId];
|
||||
}
|
||||
|
||||
@@ -648,6 +648,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) {
|
||||
|
||||
Reference in New Issue
Block a user