Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions docs-site/src/content/docs/reference/configuration/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,17 @@ CLI: `ocx route policy list [--json]`, `ocx route policy show <id> [--json]`, an
Dry-run evaluates candidates without sending any upstream request.

Quota evidence (`optimize.quota`, `require.minQuotaHeadroom`, `unknownEvidence.quota`) comes from
the local Codex pool and Anthropic account quota caches, which are keyed by account. In **Pool** mode
the canonical `openai` provider preserves its existing account selection, then reads quota for the
selected account; **Direct** mode reads quota only from the current (caller/main) account. For other
providers (e.g. Anthropic), runtime candidates use the provider's active account. Quota evidence
never changes account selection, session affinity, cooldowns, or switching behavior — it only feeds
policy scoring. To see quota-aware behavior in a dry-run, supply account refs through the dry-run/API
candidate evidence: `candidates[].codexAccountId` (Codex pool, provider `openai`) or
`candidates[].accountRef` (Anthropic) derives the matching cached account quota; an explicit
`candidates[].quota` object is echoed as given.
account-keyed Codex and Anthropic quota caches. A runtime candidate receives cached quota only when
the evidence already identifies the account. Unbound canonical `openai` and Anthropic candidates
remain unknown during policy evaluation because Pool selection, Direct caller identity, provider
rotation, and thread affinity are resolved after the policy chooses a provider/model; a process-active
account is not used as a substitute.
Quota evidence never changes account selection, session affinity, cooldowns, or switching behavior —
it only feeds policy scoring. To see quota-aware behavior in an API dry-run, supply account refs in
the candidate evidence sent to `POST /api/routing-profiles/dry-run`:
`candidates[].codexAccountId` (Codex pool, provider `openai`) or `candidates[].accountRef`
(Anthropic) derives the matching cached account quota; an explicit `candidates[].quota` object is
echoed as given. The CLI dry-run cannot supply these per-candidate account fields.
Comment on lines +156 to +166

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List routing documentation pages, including localized variants.
fd -t f -g 'routing.md' docs-site/src/content/docs | sort

# Inspect quota and account-routing statements in all routing pages.
rg -n -C 3 --glob 'routing.md' \
  'quota|accountRef|codexAccountId|active account|Pool|Anthropic|affinity|switching' \
  docs-site/src/content/docs

Repository: lidge-jun/opencodex

Length of output: 19417


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the quota/account-routing paragraphs across all routing docs, with line numbers.
for file in docs-site/src/content/docs/reference/configuration/routing.md \
  docs-site/src/content/docs/ja/reference/configuration/routing.md \
  docs-site/src/content/docs/ko/reference/configuration/routing.md \
  docs-site/src/content/docs/ru/reference/configuration/routing.md \
  docs-site/src/content/docs/zh-cn/reference/configuration/routing.md; do
  echo "===== $file ====="
  rg -n -C 4 --glob 'routing.md' \
    'quota|accountRef|codexAccountId|active account|active Pool account|Pool selection|Anthropic|affinity|switching|dry-run|Candidates' \
    "$file" || true
done

# Extract only routing docs into a small JSON report for deterministic comparison by file and quoted key terms.
python3 - <<'PY'
from pathlib import Path
terms = [
    "quota", "accountRef", "codexAccountId", "active account", "active Pool account",
    "Pool selection", "Anthropic", "affinity", "switching", "dry-run", "active"
]
for locale in ["", "ja", "ko", "ru", "zh-cn"]:
    path = Path("docs-site/src/content/docs") / f"{locale}reference/configuration/routing.md".replace("//", "/")
    if not path.exists():
        print(locale or "en", "missing")
        continue
    text = path.read_text()
    print(f"locale={locale or 'en'} lines={len(text.splitlines())}")
    for term in terms:
        hits = [i+1 for i,line in enumerate(text.splitlines()) if term in line]
        if hits:
            print(f"  {term}: {hits[:10]}{'...' if len(hits)>10 else ''}")
PY

Repository: lidge-jun/opencodex

Length of output: 18122


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Compare the exact routing profile evidence/quota sections across locales against the English source.
for locale in ja ko ru zh-cn; do
  en="docs-site/src/content/docs/reference/configuration/routing.md"
  loc="docs-site/src/content/docs/${locale}/reference/configuration/routing.md"
  lo=$(wc -l < "$loc")
  echo "===== $locale: $loc $lo lines ====="
  sed -n '94,170p' "$loc"
  echo
done

# Find any active/account quota phrasing that could contradict the English contract.
rg -n -i --glob 'routing.md' \
  'quota.*(account|active|process)|active.*(quota|accountRef|codexAccountId)|account.*(quota|cache)|can.*quota|supply.*quota|active Pool account|process-active' \
  docs-site/src/content/docs | sed -n '1,200p'

Repository: lidge-jun/opencodex

Length of output: 10717


Add the localized quota-evidence paragraphs to the routing docs.

The localized routing pages end before the English optimize.quota behavior, so docs-site/src/content/docs/{ja,ko,ru,zh-cn}/reference/configuration/routing.md can now contradict the source by omission. Add the matching account-bound quote and dry-run evidence text; do not present an active Codex or Anthropic account as a substitute for candidates[].codexAccountId / candidates[].accountRef.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs-site/src/content/docs/reference/configuration/routing.md` around lines
156 - 166, The localized routing documentation pages for ja, ko, ru, and zh-cn
need the same quota-evidence behavior described in the English routing page. Add
localized text covering account-bound Codex and Anthropic quota caches,
including the dry-run fields candidates[].codexAccountId,
candidates[].accountRef, and candidates[].quota, while explicitly stating that
an active account is not a substitute and quota evidence does not affect account
selection or switching behavior.

Sources: Path instructions, Learnings


### Combos vs policy profiles

Expand Down
20 changes: 0 additions & 20 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import {
import { decodeRoutedModelId, encodeRoutedModelId } from "./providers/slug-codec";
import { getStaleCached } from "./codex/model-cache";
import { codexAccountNamespaceEntries } from "./codex/account-namespaces";
import { getEffectiveActiveCodexAccountId } from "./codex/routing";
import { getAccountSet } from "./oauth/store";
import {
buildRouteDecisionTrace,
type RouteDecisionKind,
Expand Down Expand Up @@ -513,24 +511,6 @@ function routeModelInternal(
quota: quotaEvidenceForCandidate({
provider: candidate.provider,
model: candidate.model,
...(candidate.provider === OPENAI_CODEX_PROVIDER_ID
&& providerCodexAccountMode(
OPENAI_CODEX_PROVIDER_ID,
config.providers[OPENAI_CODEX_PROVIDER_ID],
) === "pool"
? (() => {
const codexAccountId = getEffectiveActiveCodexAccountId(config);
return {
codexAccountId,
codexAccountPlan: codexAccountId
? config.codexAccounts?.find(account => account.id === codexAccountId)?.plan
: undefined,
};
})()
: {}),
accountRef: candidate.provider === "anthropic"
? getAccountSet("anthropic")?.activeAccountId
: undefined,
}),
Comment on lines 511 to 514

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve provider-level quota evidence for Codex pools

For any live Pool-mode profile, this call now receives neither codexAccountId nor codexAccountPlan, and routing-profile candidates cannot configure those fields, so an openai candidate always gets { known: false } even when the reconciled pool has fresh quota data. The deleted path previously used the plan as a sentinel for quotaEvidenceForCandidate to aggregate every cached pool account via codexPoolQuotaEvidence—it did not attribute only account A's quota—so configurations using unknownEvidence.quota: "exclude" can now reject a healthy pool, while quota weighting and minQuotaHeadroom cease working on live Pool routes. Remove the active-account attribution but preserve provider-level aggregate evidence for the unbound pool, as already specified by tests/routing-policy-pool-quota.test.ts.

Useful? React with 👍 / 👎.

cost: costEvidenceForCandidate({
provider: candidate.provider,
Expand Down
22 changes: 0 additions & 22 deletions src/server/management/routing-profile-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ import { candidateCapabilityEvidence } from "../../routing/capability";
import { policyCandidateHealthEvidence } from "../../routing/health";
import { quotaEvidenceForCandidate } from "../../routing/quota";
import { costEvidenceForCandidate } from "../../routing/cost";
import { providerCodexAccountMode } from "../../providers/registry";
import { getEffectiveActiveCodexAccountId } from "../../codex/routing";
import { getAccountSet } from "../../oauth/store";
import { OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
import { saveConfigPreservingClaudeCode } from "../../config";
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
import { isPlainRecord } from "./shared";
Expand Down Expand Up @@ -115,24 +111,6 @@ function assembleCandidateEvidence(
quota: quotaEvidenceForCandidate({
provider: candidate.provider,
model: candidate.model,
...(candidate.provider === OPENAI_CODEX_PROVIDER_ID
&& providerCodexAccountMode(
OPENAI_CODEX_PROVIDER_ID,
config.providers[OPENAI_CODEX_PROVIDER_ID],
) === "pool"
? (() => {
const codexAccountId = getEffectiveActiveCodexAccountId(config);
return {
codexAccountId,
codexAccountPlan: codexAccountId
? config.codexAccounts?.find(account => account.id === codexAccountId)?.plan
: undefined,
};
})()
: {}),
accountRef: candidate.provider === "anthropic"
? getAccountSet("anthropic")?.activeAccountId
: undefined,
}),
cost: costEvidenceForCandidate({
provider: candidate.provider,
Expand Down
40 changes: 37 additions & 3 deletions tests/quota-scoring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { setCachedProviderAccountQuotaForTests, clearAccountQuotaCache } from ".
import { quotaEvidenceForCandidate, quotaScore } from "../src/routing/quota";
import { evaluatePolicyProfile, QUOTA_UNKNOWN_PENALTY_SCORE } from "../src/routing/evaluator";
import { routeModel } from "../src/router";
import { getAccountSet, saveCredential } from "../src/oauth/store";
import { closeRequestHistoryIndex } from "../src/routing/history/indexer";
import type { OcxConfig } from "../src/types";

Expand Down Expand Up @@ -195,7 +196,7 @@ describe("quota-aware scoring (RI-07)", () => {
expect(penalized.candidates[0]!.score!.components.quota).toBe(QUOTA_UNKNOWN_PENALTY_SCORE);
});

test("execution path passes the active codex account into quota evidence", async () => {
test("execution path does not invent Codex quota evidence from the active pool account", async () => {
updateAccountQuota("pool-a", 30, 1_800_000_000_000, 20, 1_900_000_000_000);
const cfg = config({
codexAccounts: [{ id: "pool-a", email: "pool-a@example.test", isMain: false }],
Expand All @@ -205,8 +206,41 @@ describe("quota-aware scoring (RI-07)", () => {
},
});
const route = routeModel(cfg, "policy/quotaRoute");
expect(route.routeDecision!.candidates[0]!.quota?.known).toBe(true);
expect(route.routeDecision!.candidates[0]!.quota?.headroom).toBeCloseTo(0.7, 2);
expect(route.routeDecision!.candidates[0]!.accountRef).toBeUndefined();
expect(route.routeDecision!.candidates[0]!.quota?.known).toBe(false);
expect(route.routeDecision!.candidates[0]!.quota?.headroom).toBeUndefined();
});

test("execution path does not invent Anthropic quota evidence from the active account", async () => {
await saveCredential("anthropic", {
access: "access-a",
refresh: "refresh-a",
expires: Date.now() + 3_600_000,
accountId: "uuid-a",
email: "a@example.test",
});
const activeId = getAccountSet("anthropic")!.activeAccountId;
setCachedProviderAccountQuotaForTests("anthropic", activeId, {
fiveHourPercent: 40,
updatedAt: Date.now(),
});
const cfg = config({
providers: {
anthropic: {
adapter: "anthropic",
baseUrl: "https://api.anthropic.com",
authMode: "oauth",
models: ["claude-sonnet-5"],
},
},
routingProfiles: {
quotaRoute: { candidates: [{ provider: "anthropic", model: "claude-sonnet-5" }] },
},
});
const route = routeModel(cfg, "policy/quotaRoute");
expect(route.routeDecision!.candidates[0]!.accountRef).toBeUndefined();
expect(route.routeDecision!.candidates[0]!.quota?.known).toBe(false);
expect(route.routeDecision!.candidates[0]!.quota?.headroom).toBeUndefined();
});

test("exact account selectors and pool strategies remain authoritative", () => {
Expand Down
75 changes: 75 additions & 0 deletions tests/routing-profile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,4 +476,79 @@ describe("routing profiles (RI-04)", () => {
expect(body.candidates?.[0]?.quota?.known).toBe(true);
expect(body.candidates?.[0]?.quota?.headroom).toBeCloseTo(0.7, 2);
});

test("API dry-run leaves an unbound Codex candidate quota unknown despite an active pool account", async () => {
updateAccountQuota("pool-a", 30, 1_800_000_000_000, 20, 1_900_000_000_000);
const config = baseConfig({
providers: {
openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api/codex" },
},
codexAccounts: [{ id: "pool-a", email: "pool-a@example.test", isMain: false }],
activeCodexAccountId: "pool-a",
routingProfiles: {
only: { candidates: [{ provider: "openai", model: "gpt-5.6" }] },
},
});
const req = new ManagementRequest("http://localhost/api/routing-profiles/dry-run", {
method: "POST",
headers: { "content-type": "application/json" },
// No candidates[] override: the candidate is unbound, so the dry-run must
// not reach for the process-global active pool account. Policy evaluation
// runs before Pool/Direct identity and thread affinity resolve, so an
// account attached here can differ from the one that executes.
body: JSON.stringify({ profile: "only", evidence: {} }),
});
const response = await handleManagementAPI(req, new URL(req.url), config, { refreshCodexCatalog: async () => {} });
expect(response).not.toBeNull();
expect(response!.status).toBe(200);
const body = await response!.json() as {
candidates?: Array<{ accountRef?: string; quota?: { known?: boolean; headroom?: number } }>;
};
expect(body.candidates?.[0]?.accountRef).toBeUndefined();
expect(body.candidates?.[0]?.quota?.known).toBe(false);
expect(body.candidates?.[0]?.quota?.headroom).toBeUndefined();
});

test("API dry-run leaves an unbound Anthropic candidate quota unknown despite an active account", async () => {
const { saveCredential, getAccountSet } = await import("../src/oauth/store");
const { setCachedProviderAccountQuotaForTests } = await import("../src/providers/quota");
await saveCredential("anthropic", {
access: "access-a",
refresh: "refresh-a",
expires: Date.now() + 3_600_000,
accountId: "uuid-a",
email: "a@example.test",
});
const activeId = getAccountSet("anthropic")!.activeAccountId;
setCachedProviderAccountQuotaForTests("anthropic", activeId, {
fiveHourPercent: 40,
updatedAt: Date.now(),
});
const config = baseConfig({
providers: {
anthropic: {
adapter: "anthropic",
baseUrl: "https://api.anthropic.com",
authMode: "oauth",
models: ["claude-sonnet-5"],
},
},
routingProfiles: {
only: { candidates: [{ provider: "anthropic", model: "claude-sonnet-5" }] },
},
});
const req = new ManagementRequest("http://localhost/api/routing-profiles/dry-run", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ profile: "only", evidence: {} }),
});
const response = await handleManagementAPI(req, new URL(req.url), config, { refreshCodexCatalog: async () => {} });
expect(response).not.toBeNull();
expect(response!.status).toBe(200);
const body = await response!.json() as {
candidates?: Array<{ accountRef?: string; quota?: { known?: boolean; headroom?: number } }>;
};
expect(body.candidates?.[0]?.accountRef).toBeUndefined();
expect(body.candidates?.[0]?.quota?.known).toBe(false);
});
});
Loading