fix(combo): least-used quota strategy and wildcard UI preservation (#8894)

Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip)
This commit is contained in:
Andrew B.
2026-08-06 09:08:02 -05:00
committed by GitHub
parent 714a315a1a
commit a598fbb090
7 changed files with 361 additions and 47 deletions

View File

@@ -18,10 +18,18 @@
* and orderTargetsByResetWindow shares the same rrCounters Map from ./rrState.ts
* (D7a) so reset-aware tie rotation stays consistent with round-robin routing.
*
* @changes
* - [2026-07-24] [Composer] - Exclude Antigravity accounts without stored projectId from reset-aware pool
* - [2026-07-24] [Composer] - Skip quota-exhausted and rate-limited connections in reset-aware expansion
*
* Pure leaf: this module never imports from the combo barrel.
*/
import { getRuntimeProviderProfile, type ProviderProfile } from "../accountFallback.ts";
import {
getRuntimeProviderProfile,
isAccountUnavailable,
type ProviderProfile,
} from "../accountFallback.ts";
import { PRE_SCREEN_CONCURRENCY } from "../comboConfig.ts";
import { getQuotaFetcher } from "../quotaPreflight.ts";
import { getCircuitBreaker } from "../../../src/shared/utils/circuitBreaker";
@@ -37,6 +45,8 @@ import {
type QuotaFetchCacheConfig,
} from "./quotaScoring.ts";
import { rankByHeadroom, type HeadroomSaturation } from "./headroomRanking.ts";
import { preferAntigravityConnectionsWithStoredProject } from "../antigravityProjectPersistence.ts";
import { isQuotaExhaustedForRequest } from "../../../src/domain/quotaCache.ts";
const RESET_AWARE_CONNECTION_CACHE_TTL_MS = 30_000;
const RESET_AWARE_QUOTA_FETCH_CONCURRENCY = 5;
@@ -75,9 +85,14 @@ async function getQuotaAwareConnectionsForTarget(
(async () => {
try {
const connections = await getCachedProviderConnections({ provider, isActive: true });
const activeConnections = Array.isArray(connections)
let activeConnections = Array.isArray(connections)
? (connections as Array<Record<string, unknown>>)
: [];
if (provider === "antigravity" || provider === "agy") {
activeConnections = preferAntigravityConnectionsWithStoredProject(
activeConnections
) as Array<Record<string, unknown>>;
}
if (
!resetAwareConnectionCache.has(provider) &&
resetAwareConnectionCache.size >= MAX_RESET_AWARE_CACHE
@@ -199,6 +214,18 @@ async function expandTargetsByQuotaAwareConnections(
}
for (const connectionId of connectionIds) {
const provider = getResetAwareProvider(target);
const connection = connectionById.get(connectionId);
if (
connection &&
typeof connection.rateLimitedUntil === "string" &&
isAccountUnavailable(connection.rateLimitedUntil)
) {
continue;
}
if (provider && isQuotaExhaustedForRequest(connectionId, provider, target.modelStr || null)) {
continue;
}
expandedTargets.push({
...target,
connectionId,

View File

@@ -54,6 +54,7 @@ import {
normalizeIntelligentRoutingFilter,
normalizeIntelligentRoutingConfig,
} from "@/lib/combos/intelligentRouting";
import { getComboStepTarget } from "@/lib/combos/steps";
import { resolveServerErrorMessage } from "@/lib/api/serverErrorMessage";
import { useTranslations } from "next-intl";
@@ -570,6 +571,13 @@ function normalizeModelEntry(entry) {
weight: entry.weight || 0,
};
}
if (entry?.kind === "provider-wildcard") {
return {
...entry,
model: getComboStepTarget(entry),
weight: entry.weight || 0,
};
}
return {
...entry,
model: entry.model,
@@ -580,6 +588,7 @@ function normalizeModelEntry(entry) {
function getModelString(entry) {
if (typeof entry === "string") return entry;
if (entry?.kind === "combo-ref") return entry.comboName;
if (entry?.kind === "provider-wildcard") return getComboStepTarget(entry);
return entry.model;
}
@@ -635,6 +644,37 @@ function formatComboEntryDisplay(
return `Combo → ${normalizedEntry.comboName}`;
}
if (normalizedEntry.kind === "provider-wildcard") {
const providerIdentifier = normalizedEntry.providerId;
const builderProvider = findBuilderProviderByIdentifier(builderProviders, providerIdentifier);
const providerNode = findProviderNodeByIdentifier(providerNodes, providerIdentifier);
const providerLabel =
builderProvider?.displayName || providerNode?.name || providerIdentifier || "provider";
const patternLabel = normalizedEntry.modelPattern || "*";
const wildcardLabel = `${providerLabel}/${patternLabel}`;
if (!includeConnection) {
return wildcardLabel;
}
const connectionId = normalizedEntry.connectionId || null;
const rawConnectionLabel =
(connectionId &&
builderProvider?.connections?.find((connection) => connection.id === connectionId)
?.label) ||
normalizedEntry.label ||
null;
const connectionLabel = rawConnectionLabel
? pickDisplayValue([rawConnectionLabel], showFullEmails, rawConnectionLabel)
: null;
if (connectionId) {
return `${wildcardLabel} · ${connectionLabel || `acct ${connectionId.slice(0, 8)}`}`;
}
return `${wildcardLabel} · dynamic account`;
}
const parsed = parseQualifiedModel(normalizedEntry.model);
if (!parsed) return normalizedEntry.model;
@@ -3420,15 +3460,25 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
<div className="text-[10px] text-text-muted truncate">
{entry.kind === "combo-ref"
? getI18nOrFallback(t, "builderComboRefStep", "Nested combo reference")
: entry.connectionId
? getI18nOrFallback(t, "builderPinnedAccount", "Pinned account")
: entry.providerId
? getI18nOrFallback(
t,
"builderDynamicAccountShort",
"Dynamic account"
)
: getI18nOrFallback(t, "builderLegacyEntry", "Legacy model entry")}
: entry.kind === "provider-wildcard"
? getI18nOrFallback(
t,
"builderProviderWildcard",
"All matching provider models"
)
: entry.connectionId
? getI18nOrFallback(t, "builderPinnedAccount", "Pinned account")
: entry.providerId
? getI18nOrFallback(
t,
"builderDynamicAccountShort",
"Dynamic account"
)
: getI18nOrFallback(
t,
"builderLegacyEntry",
"Legacy model entry"
)}
</div>
</div>
@@ -4542,19 +4592,25 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
"builderComboRefStep",
"Nested combo reference"
)
: entry.connectionId
? getI18nOrFallback(t, "builderPinnedAccount", "Pinned account")
: entry.providerId
? getI18nOrFallback(
t,
"builderDynamicAccountShort",
"Dynamic account"
)
: getI18nOrFallback(
t,
"builderLegacyEntry",
"Legacy model entry"
)}
: entry.kind === "provider-wildcard"
? getI18nOrFallback(
t,
"builderProviderWildcard",
"All matching provider models"
)
: entry.connectionId
? getI18nOrFallback(t, "builderPinnedAccount", "Pinned account")
: entry.providerId
? getI18nOrFallback(
t,
"builderDynamicAccountShort",
"Dynamic account"
)
: getI18nOrFallback(
t,
"builderLegacyEntry",
"Legacy model entry"
)}
{strategy === "weighted" && entry.weight > 0
? ` · ${entry.weight}%`
: ""}

View File

@@ -10,6 +10,9 @@
* - Active accounts (quota > 0%): refetch every 5 minutes
* - Exhausted accounts: refetch every 5 minutes (or immediately after resetAt passes)
*
* @changes
* - [2026-07-24] [Composer] - Scope Antigravity per-model exhaustion to exact model + family weekly windows
*
* @module domain/quotaCache
*/
@@ -236,6 +239,43 @@ export function __clearForTests() {
getState().cache.clear();
}
function resolveAntigravityQuotaWindowsForModel(
quotaNames: string[],
requestedModel: string
): string[] {
const requestedFamily = getAntigravityQuotaFamily(requestedModel);
const cleanRequestedModel = requestedModel.replace(/^(antigravity|agy)\//, "");
const bareModel = cleanRequestedModel.includes("/")
? cleanRequestedModel.slice(cleanRequestedModel.lastIndexOf("/") + 1)
: cleanRequestedModel;
if (requestedFamily === "other") {
return quotaNames.filter((windowName) => {
const bare = windowName.replace(/^(antigravity|agy)\//, "");
return bare === bareModel || bare === cleanRequestedModel;
});
}
const familyAggregates =
requestedFamily === "gemini"
? ["gemini_weekly"]
: requestedFamily === "claude"
? ["claude_gpt_weekly"]
: [];
const exactWindows = quotaNames.filter((windowName) => {
const bare = windowName.replace(/^(antigravity|agy)\//, "");
return bare === bareModel;
});
const aggregateWindows = familyAggregates.filter((key) => quotaNames.includes(key));
const scoped = [...exactWindows, ...aggregateWindows];
if (scoped.length > 0) return scoped;
return quotaNames.filter(
(windowName) => getAntigravityQuotaFamily(windowName) === requestedFamily
);
}
function isAntigravityQuotaExhausted(
connectionId: string,
entry: QuotaCacheEntry,
@@ -244,18 +284,13 @@ function isAntigravityQuotaExhausted(
if (!requestedModel) return entry.exhausted;
const quotaNames = Object.keys(entry.quotas || {});
if (quotaNames.length === 0) return entry.exhausted;
const requestedFamily = getAntigravityQuotaFamily(requestedModel);
const cleanRequestedModel = requestedModel.replace(/^(antigravity|agy)\//, "");
const matchingWindows = quotaNames.filter((windowName) => {
if (requestedFamily === "other") {
return windowName.replace(/^(antigravity|agy)\//, "") === cleanRequestedModel;
}
return getAntigravityQuotaFamily(windowName) === requestedFamily;
});
const matchingWindows = resolveAntigravityQuotaWindowsForModel(quotaNames, requestedModel);
return (
matchingWindows.length > 0 &&
matchingWindows.every(
(windowName) => getQuotaWindowStatus(connectionId, windowName, 100)?.reachedThreshold
(windowName) =>
getQuotaWindowStatus(connectionId, windowName, DEFAULT_QUOTA_THRESHOLD_PERCENT)
?.reachedThreshold
)
);
}
@@ -273,7 +308,9 @@ function isCodexQuotaExhausted(
return (
scopedWindowNames.length > 0 &&
scopedWindowNames.every(
(windowName) => getQuotaWindowStatus(connectionId, windowName, 100)?.reachedThreshold
(windowName) =>
getQuotaWindowStatus(connectionId, windowName, DEFAULT_QUOTA_THRESHOLD_PERCENT)
?.reachedThreshold
)
);
}
@@ -512,7 +549,11 @@ export function getQuotaWindowStatus(
usedPercentage,
resetAt,
// If reset time has already passed, avoid stale cached percentages blocking selection.
reachedThreshold: windowExpired ? false : usedPercentage >= thresholdPercent,
reachedThreshold: windowExpired
? false
: remainingPercentage <= 0
? true
: usedPercentage >= thresholdPercent,
};
}

View File

@@ -66,7 +66,7 @@ export interface ComboControlCenterTargetHealth {
export interface ComboControlCenterTarget {
id: string;
kind: "model" | "combo-ref";
kind: "model" | "combo-ref" | "provider-wildcard";
index: number;
label: string;
model: string;
@@ -134,6 +134,9 @@ function getStepTags(step: ComboStep): string[] {
function getStepLabel(step: ComboStep): string {
if (step.label) return step.label;
if (step.kind === "combo-ref") return `Combo → ${step.comboName}`;
if (step.kind === "provider-wildcard") {
return `All ${step.providerId}/${step.modelPattern}`;
}
return step.model;
}
@@ -153,12 +156,19 @@ export function getComboControlCenterTargets(
}
return steps.map((step, index) => {
const model = step.kind === "combo-ref" ? step.comboName : step.model;
const model =
step.kind === "combo-ref"
? step.comboName
: step.kind === "provider-wildcard"
? `${step.providerId}/${step.modelPattern}`
: step.model;
const healthEntry = healthByStepId.get(step.id) || healthByModel.get(model) || null;
const provider =
step.kind === "model"
? step.providerId || providerFromModel(step.model) || healthEntry?.provider || null
: null;
: step.kind === "provider-wildcard"
? step.providerId
: null;
return {
id: step.id,
@@ -167,7 +177,10 @@ export function getComboControlCenterTargets(
label: getStepLabel(step),
model,
provider,
connectionId: step.kind === "model" ? step.connectionId || null : null,
connectionId:
step.kind === "model" || step.kind === "provider-wildcard"
? step.connectionId || null
: null,
weight: step.weight || 0,
tags: getStepTags(step),
health: healthEntry,
@@ -252,7 +265,9 @@ export function summarizeComboControlCenter(
strategy: combo.strategy || "priority",
isActive: combo.isActive !== false,
targetCount: targets.length,
modelTargetCount: targets.filter((target) => target.kind === "model").length,
modelTargetCount: targets.filter(
(target) => target.kind === "model" || target.kind === "provider-wildcard"
).length,
nestedComboCount: targets.filter((target) => target.kind === "combo-ref").length,
providerCount: providers.size,
totalRequests,

View File

@@ -1,3 +1,10 @@
/**
* @file steps.ts
* @description Combo step normalization helpers for model, combo-ref, provider-wildcard, and routing metadata.
*
* @changes
* - [2026-07-25] [Composer] - Preserve provider-wildcard steps during combo normalization
*/
type JsonRecord = Record<string, unknown>;
export const COMBO_SCHEMA_VERSION = 2;
@@ -28,7 +35,18 @@ export interface ComboRefStep {
label?: string;
}
export type ComboStep = ComboModelStep | ComboRefStep;
export interface ComboProviderWildcardStep {
id: string;
kind: "provider-wildcard";
providerId: string;
modelPattern: string;
connectionId?: string | null;
allowedConnectionIds?: string[] | null;
weight: number;
label?: string;
}
export type ComboStep = ComboModelStep | ComboRefStep | ComboProviderWildcardStep;
type ComboCollectionLike =
| Array<{ name?: unknown } | string>
@@ -103,6 +121,18 @@ function parseProviderId(model: string): string | null {
return providerId.length > 0 ? providerId : null;
}
function parseProviderWildcardPattern(
target: string
): { providerId: string; modelPattern: string } | null {
const trimmed = target.trim();
const slashIndex = trimmed.indexOf("/");
if (slashIndex <= 0) return null;
const providerId = trimmed.slice(0, slashIndex).trim();
const modelPattern = trimmed.slice(slashIndex + 1).trim();
if (!providerId || !modelPattern.includes("*")) return null;
return { providerId, modelPattern };
}
function toFullModelString(model: string, providerId?: string | null): string {
const trimmedModel = model.trim();
if (trimmedModel.includes("/")) return trimmedModel;
@@ -124,12 +154,9 @@ function buildStepId(
index: number,
seed: string
) {
const parts = [
slugify(comboName || "combo"),
kind === "combo-ref" ? "ref" : "model",
String(index + 1),
slugify(seed),
];
const kindSlug =
kind === "combo-ref" ? "ref" : kind === "provider-wildcard" ? "wildcard" : "model";
const parts = [slugify(comboName || "combo"), kindSlug, String(index + 1), slugify(seed)];
return parts.join("-").slice(0, 200);
}
@@ -187,6 +214,11 @@ export function getComboStepTarget(
if (!isRecord(value)) return null;
if (value.kind === "combo-ref") return toTrimmedString(value.comboName);
if (value.kind === "provider-wildcard") {
const providerId = toTrimmedString(value.providerId);
const modelPattern = toTrimmedString(value.modelPattern) || "*";
return providerId ? `${providerId}/${modelPattern}` : null;
}
const rawModel = toTrimmedString(value.model);
if (!rawModel) return null;
@@ -223,6 +255,22 @@ export function normalizeComboStep(
};
}
const wildcardPattern = parseProviderWildcardPattern(target);
if (wildcardPattern) {
return {
id: buildStepId(
"provider-wildcard",
comboName,
index,
`${wildcardPattern.providerId}/${wildcardPattern.modelPattern}`
),
kind: "provider-wildcard",
providerId: wildcardPattern.providerId,
modelPattern: wildcardPattern.modelPattern,
weight: 0,
};
}
const providerId = parseProviderId(target);
return {
id: buildStepId("model", comboName, index, target),
@@ -252,6 +300,41 @@ export function normalizeComboStep(
};
}
if (value.kind === "provider-wildcard") {
const providerId =
toTrimmedString(value.providerId) ||
toTrimmedString(value.provider) ||
parseProviderId(toTrimmedString(value.model) || "");
if (!providerId) return null;
const modelPattern = toTrimmedString(value.modelPattern) || "*";
const connectionId =
value.connectionId === null ? null : toTrimmedString(value.connectionId) || undefined;
const allowedConnectionIds = Array.isArray(value.allowedConnectionIds)
? value.allowedConnectionIds
.map((connId) => toTrimmedString(connId))
.filter((connId): connId is string => !!connId)
: undefined;
return {
id:
explicitId ||
buildStepId(
"provider-wildcard",
comboName,
index,
connectionId
? `${providerId}/${modelPattern}:${connectionId}`
: `${providerId}/${modelPattern}`
),
kind: "provider-wildcard",
providerId,
modelPattern,
...(connectionId !== undefined ? { connectionId } : {}),
weight,
...(label ? { label } : {}),
...(allowedConnectionIds && allowedConnectionIds.length > 0 ? { allowedConnectionIds } : {}),
};
}
const rawModel = toTrimmedString(value.model);
if (!rawModel) return null;
const isExplicitModel = value.kind === "model";
@@ -261,6 +344,36 @@ export function normalizeComboStep(
toTrimmedString(value.provider) ||
parseProviderId(rawModel);
const wildcardPattern = parseProviderWildcardPattern(rawModel);
if (wildcardPattern) {
const connectionId =
value.connectionId === null ? null : toTrimmedString(value.connectionId) || undefined;
const allowedConnectionIds = Array.isArray(value.allowedConnectionIds)
? value.allowedConnectionIds
.map((connId) => toTrimmedString(connId))
.filter((connId): connId is string => !!connId)
: undefined;
return {
id:
explicitId ||
buildStepId(
"provider-wildcard",
comboName,
index,
connectionId
? `${wildcardPattern.providerId}/${wildcardPattern.modelPattern}:${connectionId}`
: `${wildcardPattern.providerId}/${wildcardPattern.modelPattern}`
),
kind: "provider-wildcard",
providerId: wildcardPattern.providerId,
modelPattern: wildcardPattern.modelPattern,
...(connectionId !== undefined ? { connectionId } : {}),
weight,
...(label ? { label } : {}),
...(allowedConnectionIds && allowedConnectionIds.length > 0 ? { allowedConnectionIds } : {}),
};
}
if (!isExplicitModel && shouldTreatAsComboRef(rawModel, providerId, options)) {
return {
id: explicitId || buildStepId("combo-ref", comboName, index, rawModel),

View File

@@ -22,6 +22,7 @@ type JsonRecord = Record<string, unknown>;
interface StatementLike<TRow = unknown> {
get: (...params: unknown[]) => TRow | undefined;
all: (...params: unknown[]) => TRow[];
run: (...params: unknown[]) => { changes: number };
}
@@ -253,3 +254,40 @@ export function deleteSessionModelHistory(sessionId: string, comboName: string):
.run(sessionId, comboName);
return result.changes ?? 0;
}
/**
* Get usage counts for ALL models in a session's history.
* Returns a Map<{model}> -> {count} for least-used strategy.
*
* Queries the session_model_history table and aggregates by model_str.
* Can optionally filter by connectionId if provided.
*
* @param connectionId - Optional connection ID to filter by. If not provided, returns all connections.
* @returns Promise<Map<string, number>> of model strings to their usage count.
*/
export async function getSessionModelUsageCounts(
connectionId?: string
): Promise<Map<string, number>> {
const db = getDbInstance() as any;
let sql = `SELECT model_str, COUNT(*) as count
FROM session_model_history
WHERE 1=1`;
const params: unknown[] = [];
if (connectionId) {
sql += ` AND connection_id = ?`;
params.push(connectionId);
}
sql += ` GROUP BY model_str ORDER BY count ASC`;
const rows = db.prepare(sql).all(...params) as Array<{ model_str: string; count: number }>;
const usageMap = new Map<string, number>();
rows.forEach((row: { model_str: string; count: number }) => {
usageMap.set(row.model_str, row.count);
});
return usageMap;
}

View File

@@ -57,6 +57,30 @@ test("getComboControlCenterTargets normalizes legacy, structured and nested comb
assert.equal(targets[2].label, "Combo → cheap-fallback");
});
test("getComboControlCenterTargets normalizes provider-wildcard steps", () => {
const targets = getComboControlCenterTargets({
name: "alibabafree",
models: [
{
id: "alibaba-main",
kind: "provider-wildcard",
providerId: "alibaba",
modelPattern: "*",
connectionId: "conn-main",
weight: 0,
label: "main",
},
],
});
assert.equal(targets.length, 1);
assert.equal(targets[0].kind, "provider-wildcard");
assert.equal(targets[0].model, "alibaba/*");
assert.equal(targets[0].provider, "alibaba");
assert.equal(targets[0].connectionId, "conn-main");
assert.equal(targets[0].label, "main");
});
test("summarizeComboControlCenter combines health and runtime metrics", () => {
const summary = summarizeComboControlCenter(
{