fix(combo,usage,oauth): drain the base-reds the shard fix exposed

With the migration collision and the broken import out of the way the four unit
shards actually run, and a further layer of base-reds became visible on the pure
tip 9995bc4893. Three are production defects.

**Production defects**

- open-sse/services/combo/runtimeUnitCapacity.ts:58 called resolveComboTargets()
  WITHOUT the hidden-model snapshot, so it fell back to the default
  getHiddenModelsByProvider() — a fresh full key_value read PER nested combo-ref
  unit, on every request. #8878 threaded the snapshot through the other call
  sites and missed this one. Threaded it from executeRuntimeUnitCombo (and from
  the dispatchPrelude call site), restoring the one-snapshot-per-request
  invariant combo-hidden-leaf-routing.test.ts pins. 9/9.
- open-sse/services/usage/firecrawl.ts silently ignored its own `apiKey`
  parameter: 91bb6aa619 moved the fetch to
  fetchFirecrawlQuota(connectionId, connection), which reads the key off the
  connection record, so any caller passing the key directly got "Firecrawl API
  key not available". The explicit key is now merged into the connection passed
  down. firecrawl-usage 8/8.
- src/lib/oauth/constants/oauth.ts was missing a RAYCAST entry in PROVIDERS
  while src/lib/oauth/providers/index.ts registers `raycast` (#8895), so every
  consumer reading PROVIDERS did not know Raycast Pro exists. Also added its
  OAUTH_TEST_CONFIG entry (checkExpiry only — it is an `import_token` provider
  with refreshToken always null), which #8408's guard explicitly requires rather
  than grandfathering. oauth-providers-config 25/25, oauth-test-config-8408 2/2.

**Count / contract drift from the same batch**

- feature flags 45 -> 46, APIKEY_PROVIDERS 197 -> 198 (Raycast Pro #8895),
  unique MCP tools 107 -> 108. Each re-derived from the source of truth.
- vi + pt-BR locales: translated the 8 keys #9415 added
  (providers.newApiAggregator* and providers.modelTestQuotaTooltip) instead of
  relaxing the parity guard. i18n-vi 5/5, i18n-pt-br 3/3.
- login-bootstrap-route: #9491 added `authenticated` to the require-login
  payload so /login can redirect an active session; the three deepEqual bodies
  now carry it. 10/10.

**Flaky-by-construction, made deterministic**

tests/unit/chat-combo-live-test.test.ts asserted the early-keepalive frame with
a 100ms mocked upstream while resolveKeepaliveThreshold() is 2000ms for
openai/*. It only ever passed while unrelated handler latency happened to push
the total past the threshold — incidental, not deterministic, and it stopped
holding once the handler got faster. The mock now sleeps 2400ms so the slow path
is guaranteed and the assertion means what it says. 5/5.

typecheck:core exit 0. check:file-size (base-relative) OK.

Refs #9298
This commit is contained in:
diegosouzapw
2026-08-07 05:31:07 -03:00
parent eba6fb42d4
commit db7c066f87
14 changed files with 97 additions and 29 deletions

View File

@@ -619,6 +619,7 @@ export async function tryRuntimeUnitDispatch(args: {
nesting: nestingContext,
baseOptions: buildBaseOptions(args),
runCombo: args.runCombo,
hiddenModelsByProvider: args.hiddenModelsByProvider,
});
recordRuntimeUnitStickySuccess({
strategy,

View File

@@ -9,7 +9,12 @@
import { isAccountSemaphoreFull } from "../accountSemaphore.ts";
import { resolveComboTargets } from "./comboStructure.ts";
import { lookupPositiveCap } from "./concurrencyCaps.ts";
import type { ComboCollectionLike, ComboLike, ResolvedComboUnit } from "./types.ts";
import type {
ComboCollectionLike,
ComboLike,
HiddenModelsByProvider,
ResolvedComboUnit,
} from "./types.ts";
type CapLookup = (connectionId: string) => Promise<number | null>;
@@ -45,7 +50,12 @@ async function isConnectionAtConcurrencyCap(
export async function isRuntimeUnitAtConcurrencyCap(
unit: ResolvedComboUnit,
allCombos: ComboCollectionLike,
lookupCap: CapLookup = lookupPositiveCap
lookupCap: CapLookup = lookupPositiveCap,
// Threaded from the caller so the hidden-model snapshot resolved once per
// request is reused. Without it resolveComboTargets falls back to its default
// getHiddenModelsByProvider(), i.e. a fresh full key_value read per nested
// combo-ref unit on EVERY request (#8878 threaded the other call sites).
hiddenModelsByProvider?: HiddenModelsByProvider
): Promise<boolean> {
if (unit.kind === "model") {
if (!unit.connectionId || !unit.provider) return false;
@@ -55,7 +65,7 @@ export async function isRuntimeUnitAtConcurrencyCap(
const childCombo = findComboByName(allCombos, unit.comboName);
if (!childCombo) return false;
const targets = resolveComboTargets(childCombo, allCombos, 1);
const targets = resolveComboTargets(childCombo, allCombos, 1, hiddenModelsByProvider);
const byConnection = new Map<string, { provider: string; connectionId: string }>();
for (const target of targets) {
if (!target.connectionId || !target.provider) continue;

View File

@@ -18,6 +18,7 @@ import type {
ComboNestingContext,
HandleComboChatOptions,
HandleSingleModel,
HiddenModelsByProvider,
IsModelAvailable,
ResolvedComboRefTarget,
ResolvedComboUnit,
@@ -186,6 +187,7 @@ export async function executeRuntimeUnitCombo(args: {
nesting: ComboNestingContext;
baseOptions: HandleComboChatOptions;
runCombo: RuntimeUnitRunner;
hiddenModelsByProvider?: HiddenModelsByProvider;
}): Promise<RuntimeUnitExecutionResult> {
const maxRetries = Number(args.config.maxRetries ?? 1);
const retryDelayMs = resolveDelayMs(args.config.retryDelayMs, 2000);
@@ -197,7 +199,14 @@ export async function executeRuntimeUnitCombo(args: {
let fallbackCount = 0;
for (const unit of orderedUnits) {
if (await isRuntimeUnitAtConcurrencyCap(unit, args.allCombos)) {
if (
await isRuntimeUnitAtConcurrencyCap(
unit,
args.allCombos,
undefined,
args.hiddenModelsByProvider
)
) {
args.log.info(
"COMBO",
`Skipping ${unit.kind} ${unitDisplayName(unit)} — concurrency cap reached`

View File

@@ -5,7 +5,11 @@
* credits into the standard `{ plan, quotas }` response.
*/
import { fetchFirecrawlQuota, getFirecrawlBaseUrl, type FirecrawlQuota } from "../firecrawlQuotaFetcher.ts";
import {
fetchFirecrawlQuota,
getFirecrawlBaseUrl,
type FirecrawlQuota,
} from "../firecrawlQuotaFetcher.ts";
import { createQuotaFromUsage, parseResetTime } from "./quota.ts";
function createFirecrawlPlanQuota(q: FirecrawlQuota) {
@@ -29,7 +33,11 @@ function createFirecrawlPlanQuota(q: FirecrawlQuota) {
};
}
export async function getFirecrawlUsage(connectionId: string, apiKey?: string, connection?: Record<string, unknown>) {
export async function getFirecrawlUsage(
connectionId: string,
apiKey?: string,
connection?: Record<string, unknown>
) {
if (!connectionId) {
return { message: "Firecrawl: connection id unavailable." };
}
@@ -44,7 +52,12 @@ export async function getFirecrawlUsage(connectionId: string, apiKey?: string, c
}
try {
const live = await fetchFirecrawlQuota(connectionId, connection);
// The explicit `apiKey` argument was silently dropped when #91bb6aa619 moved
// this to fetchFirecrawlQuota(connectionId, connection): the fetcher reads the
// key off the connection record, so a caller that passes the key directly —
// without a connection carrying it — always got "API key not available".
const resolvedConnection = apiKey ? { ...(connection || {}), apiKey } : connection;
const live = await fetchFirecrawlQuota(connectionId, resolvedConnection);
if (!live) {
return { message: "Firecrawl API key not available or credit usage unavailable." };
}

View File

@@ -111,6 +111,14 @@ export const OAUTH_TEST_CONFIG = {
// Validate using token presence/expiry as a lightweight auth check.
checkExpiry: true,
},
raycast: {
// #8895 — Raycast Pro is an `import_token` provider: the token is imported
// from the local Raycast install, `refreshToken` is always null and the
// stored `expiresIn` defaults to 30 days. There is nothing to refresh, so
// the test is the expiry check on the imported token; without an entry here
// Test Connection persists testStatus="error" on a healthy account (#8408).
checkExpiry: true,
},
cline: CLINE_OAUTH_TEST_CONFIG,
// ClinePass reuses the same WorkOS OAuth flow and token lifecycle as Cline.
clinepass: CLINE_OAUTH_TEST_CONFIG,

View File

@@ -5475,13 +5475,13 @@
"newApiUserIdLabel": "ID de Usuário New-API",
"newApiUserIdPlaceholder": "ex.: 12345",
"newApiUserIdHint": "Valor do cabeçalho New-Api-User do AgentRouter, usado junto com a chave de API do console para consultar o saldo de cota.",
"newApiAggregatorToggleLabel": "Gateway Agregador",
"newApiAggregatorToggleHint": "Ativar detecção de saldo para nós agregadores New-API / One-API / Sub2API. O painel mostrará o badge de saldo e o roteamento de pré-voo de cota ignorará contas esgotadas.",
"newApiAggregatorConsoleApiKeyHint": "Token de Acesso do Sistema para o endpoint /api/user/self do agregador. Não é a chave de API de roteamento.",
"newApiAggregatorUserIdHint": "Valor do cabeçalho New-Api-User usado para consultar o saldo de cota do usuário do agregador.",
"newApiAggregatorQuotaPerUnitLabel": "Cota por Unidade",
"newApiAggregatorQuotaPerUnitHint": "Unidades de crédito New-API por $1 (padrão: 500000). Substitua se seu agregador usar uma taxa diferente.",
"featureFlagNewApiAggregatorBalanceDescription": "Ativar detecção de saldo para nós compatíveis de agregadores New-API / One-API / Sub2API",
"newApiAggregatorToggleLabel": "Gateway agregador",
"newApiAggregatorToggleHint": "Ativa a detecção de saldo para nós agregadores New-API / One-API / Sub2API. O painel passa a mostrar o selo de saldo e o roteamento com quota-preflight pula contas esgotadas.",
"newApiAggregatorConsoleApiKeyHint": "System Access Token para o endpoint /api/user/self do agregador. Não é a chave de API de roteamento.",
"newApiAggregatorUserIdHint": "Valor do cabeçalho New-Api-User usado para buscar o saldo de quota do usuário do agregador.",
"newApiAggregatorQuotaPerUnitLabel": "Quota por unidade",
"newApiAggregatorQuotaPerUnitHint": "Unidades de crédito New-API por US$ 1 (padrão: 500000). Substitua se o seu agregador usar outra taxa.",
"featureFlagNewApiAggregatorBalanceDescription": "Ativa a detecção de saldo para nós compatíveis New-API / One-API / Sub2API",
"cpaModeDisabledTitle": "Habilitar backend CLIProxyAPI para emulação OAuth mais profunda do Claude Code",
"cpaModeEnabledTitle": "Usando CLIProxyAPI para uma emulação mais profunda do Claude Code (uTLS, multi-conta, perfis de dispositivo)",
"customUserAgentHint": "Override opcional enviado upstream como cabeçalho User-Agent desta conexão.",
@@ -5597,7 +5597,7 @@
"tagGroupPlaceholder": "ex.: personal, work, team-a",
"testModel": "Test Model",
"testingModel": "Testing Model",
"modelTestQuotaTooltip": "Cota esgotada — reinicia amanhã ou precisa de recarga",
"modelTestQuotaTooltip": "Quota esgotada — reseta amanhã ou precisa de recarga",
"toggleOffShort": "OFF",
"toggleOnShort": "ON",
"tokenExpiredBadge": "Expirado",

View File

@@ -6020,7 +6020,15 @@
"ccAliasAddModelPlaceholder": "Id mô hình (ví dụ: gpt-4o)",
"ccAliasAddModelButton": "Thêm ghi đè",
"ccAliasLoadError": "Không tải được cài đặt bí danh khám phá: {error}",
"ccAliasSaveError": "Không lưu được cài đặt bí danh khám phá: {error}"
"ccAliasSaveError": "Không lưu được cài đặt bí danh khám phá: {error}",
"newApiAggregatorToggleLabel": "Cổng tổng hợp",
"newApiAggregatorToggleHint": "Bật phát hiện số dư cho các node tổng hợp New-API / One-API / Sub2API. Bảng điều khiển sẽ hiển thị huy hiệu số dư và định tuyến quota-preflight sẽ bỏ qua các tài khoản đã cạn.",
"newApiAggregatorConsoleApiKeyHint": "System Access Token cho endpoint /api/user/self của bộ tổng hợp. Không phải khóa API định tuyến.",
"newApiAggregatorUserIdHint": "Giá trị header New-Api-User dùng để lấy số dư quota của người dùng bộ tổng hợp.",
"newApiAggregatorQuotaPerUnitLabel": "Quota mỗi đơn vị",
"newApiAggregatorQuotaPerUnitHint": "Số đơn vị tín dụng New-API cho mỗi 1 USD (mặc định: 500000). Ghi đè nếu bộ tổng hợp của bạn dùng tỷ lệ khác.",
"featureFlagNewApiAggregatorBalanceDescription": "Bật phát hiện số dư cho các node tương thích New-API / One-API / Sub2API",
"modelTestQuotaTooltip": "Đã hết quota — sẽ đặt lại vào ngày mai hoặc cần nạp thêm"
},
"settings": {
"title": "Cài đặt",

View File

@@ -531,6 +531,10 @@ export const PROVIDERS = {
KIRO: "kiro",
AMAZON_Q: "amazon-q",
CURSOR: "cursor",
// #8895 — registered in src/lib/oauth/providers/index.ts but missing here, so
// every consumer reading PROVIDERS (onboarding wizard, test-connection routing)
// did not know Raycast Pro exists as an OAuth provider.
RAYCAST: "raycast",
KILOCODE: "kilocode",
CLINE: "cline",
CLINEPASS: "clinepass",

View File

@@ -250,7 +250,13 @@ test("chat completions route emits early keepalive while waiting for stream read
await seedHealthyConnection();
globalThis.fetch = async () => {
await new Promise((resolve) => setTimeout(resolve, 100));
// Must exceed resolveKeepaliveThreshold()'s DEFAULT_THRESHOLD_MS (2000ms) for
// openai/* — otherwise withEarlyStreamKeepalive takes the FAST path, forwards
// the handler response as-is and no keepalive frame is ever emitted. The old
// 100ms only worked while unrelated handler latency happened to push the
// total past the threshold, which made this assertion incidental rather than
// deterministic; it stopped holding once the handler got faster.
await new Promise((resolve) => setTimeout(resolve, 2_400));
return new Response(
[
`data: ${JSON.stringify({

View File

@@ -30,13 +30,13 @@ const {
isControlPlaneProxyDirectFallbackEnabled,
} = await import("../../src/shared/utils/featureFlags.ts");
const EXPECTED_FEATURE_FLAG_COUNT = 45;
const EXPECTED_FEATURE_FLAG_COUNT = 46;
// ──────────────────────────────────────────────────────
// Test group 1 — Flag definitions registry
// ──────────────────────────────────────────────────────
describe("featureFlagDefinitions", () => {
it("has exactly 45 flag definitions", () => {
it("has exactly 46 flag definitions", () => {
assert.strictEqual(FEATURE_FLAG_DEFINITIONS.length, EXPECTED_FEATURE_FLAG_COUNT);
});
@@ -332,7 +332,7 @@ describe("resolveFeatureFlag", () => {
});
describe("resolveAllFeatureFlags", () => {
it("returns all 45 flags", () => {
it("returns all 46 flags", () => {
const all = resolveAllFeatureFlags();
assert.strictEqual(all.length, EXPECTED_FEATURE_FLAG_COUNT);
});

View File

@@ -46,6 +46,8 @@ test("public login bootstrap route exposes the metadata the login page consumes"
assert.equal(response.status, 200);
assert.deepEqual(body, {
// #9491 added `authenticated` so /login can redirect an active session.
authenticated: false,
requireLogin: true,
hasPassword: false,
setupComplete: true,
@@ -68,6 +70,8 @@ test("public login bootstrap route reports env-provided bootstrap password metad
assert.equal(response.status, 200);
assert.deepEqual(body, {
// #9491 added `authenticated` so /login can redirect an active session.
authenticated: false,
requireLogin: true,
hasPassword: true,
setupComplete: true,
@@ -89,6 +93,8 @@ test("public login bootstrap route reports stored password metadata and disabled
assert.equal(response.status, 200);
assert.deepEqual(body, {
// #9491 added `authenticated` so /login can redirect an active session.
authenticated: false,
requireLogin: false,
hasPassword: true,
setupComplete: true,

View File

@@ -6,7 +6,7 @@ import assert from "node:assert/strict";
// (omniroute_agent_skills_list/get/coverage) are intentionally defined in BOTH
// MCP_TOOLS (open-sse/mcp-server/schemas/tools.ts) and agentSkillTools
// (open-sse/mcp-server/tools/agentSkillTools.ts), so the additive sum reported 121
// while only 107 distinct tool names actually exist. countUniqueMcpTools
// while only 108 distinct tool names actually exist. countUniqueMcpTools
// (open-sse/mcp-server/toolCount.ts) fixes this by unioning tool names from every
// registered collection into a Set, so each user-visible tool is counted once.
@@ -60,7 +60,7 @@ test("#6854: countUniqueMcpTools de-duplicates tools registered in multiple coll
};
const total = countUniqueMcpTools(collections);
assert.equal(total, 107, "the published MCP inventory must match the registered tool set");
assert.equal(total, 108, "the published MCP inventory must match the registered tool set");
// Independently compute the "true" unique count by unioning every collection's
// tool names into a Set — this must equal countUniqueMcpTools's own result AND

View File

@@ -40,6 +40,7 @@ const {
OAUTH_TIMEOUT,
PROVIDERS: OAUTH_PROVIDER_IDS,
QODER_CONFIG,
RAYCAST_CONFIG,
TRAE_CONFIG,
WINDSURF_CONFIG,
XAI_OAUTH_CONFIG,
@@ -63,6 +64,7 @@ const EXPECTED_PROVIDER_KEYS = [
"amazon-q",
"cursor",
"trae",
"raycast",
"kilocode",
"cline",
"clinepass",
@@ -100,6 +102,7 @@ const EXPECTED_CONFIG_BY_PROVIDER = {
clinepass: CLINE_CONFIG, // reuses the Cline WorkOS flow (clinepass: cline in providers/index.ts)
windsurf: WINDSURF_CONFIG,
"devin-cli": WINDSURF_CONFIG,
raycast: RAYCAST_CONFIG,
trae: TRAE_CONFIG,
"grok-cli": GROK_BUILD_OAUTH_CONFIG,
"xai-oauth": XAI_OAUTH_CONFIG,

View File

@@ -1,7 +1,7 @@
// Characterization of the providers.ts catalog split (god-file decomposition): the host became a
// barrel that re-exports 10 data catalogs now living under constants/providers/*, and APIKEY is
// merged from 6 semantic family files (apikey/<family>.ts). Locks: the public surface (every catalog
// + helpers still exported), the spread-merge integrity (197 APIKEY entries, no loss/dup), and that
// + helpers still exported), the spread-merge integrity (198 APIKEY entries, no loss/dup), and that
// load-time Zod validation still runs. Pure-data move → behavior must be identical.
// Count was 171 before obsolete provider removals (PR #6675: glhf/kluster/cablyai/inclusionai etc.,
// 171->167) plus #6126 (ClinePass dual-auth): the API-key-only APIKEY_PROVIDERS_GATEWAYS entry was
@@ -17,7 +17,7 @@
// sarvam+plamo in regional) to 193, then #8170 (inception/typhoon — inception in frontier-labs,
// typhoon in regional) to 195, then Firecrawl dual search+fetch under SEARCH_PROVIDERS.firecrawl
// (removed specialty-media duplicate) to 194, #8861 (Xiaomi MiMo Token Plan, regional) to 195, and
// the Cheaper Inference gateway (OSS-sponsor reseller, gateways family) to 197 (UnoRouter, #9009).
// the Cheaper Inference gateway (OSS-sponsor reseller, gateways family) to 198 (UnoRouter #9009, Raycast Pro #8895).
import { test } from "node:test";
import assert from "node:assert/strict";
@@ -46,12 +46,12 @@ test("barrel still exports every catalog + key helpers", () => {
}
});
test("APIKEY_PROVIDERS merges the 6 family files into 197 entries (no loss / no dup)", async () => {
test("APIKEY_PROVIDERS merges the 6 family files into 198 entries (no loss / no dup)", async () => {
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
assert.equal(keys.length, 197);
assert.equal(new Set(keys).size, 197, "duplicate keys after spread-merge");
assert.equal(keys.length, 198);
assert.equal(new Set(keys).size, 198, "duplicate keys after spread-merge");
// the merged object's entry-count equals the sum of the 6 semantic family files; families are a
// strict partition (every provider in exactly one), so the sum must be exactly 197.
// strict partition (every provider in exactly one), so the sum must be exactly 198.
const families: [string, string][] = [
["gateways", "APIKEY_PROVIDERS_GATEWAYS"],
["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"],
@@ -71,7 +71,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 197 entries (no loss / no
seen.add(k);
}
}
assert.equal(famTotal, 197, "families must partition all 197 providers");
assert.equal(famTotal, 198, "families must partition all 198 providers");
});
test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {