-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(logs): surface the account decision in the route explanation (#4546) #4606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ import { createHash, type Hash } from "node:crypto"; | |
| import { chmodSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, appendFileSync } from "node:fs"; | ||
| import { join } from "node:path"; | ||
| import { getConfigDir } from "../config"; | ||
| import type { CodexAffinityMove, CodexAffinityReason } from "../codex/routing"; | ||
| import { enforceAppOwnedMemoryBudget } from "../lib/app-owned-memory"; | ||
| import { recordOwnedConfigPath } from "../lib/config-ownership"; | ||
| import { sanitizeLogMetadataString } from "../lib/redact"; | ||
|
|
@@ -187,6 +188,13 @@ export interface PersistedUsageEntry { | |
| transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse"; | ||
| /** Whether the terminal came from upstream or a proxy-generated tail. */ | ||
| terminalSource?: "upstream" | "synthetic"; | ||
| /** | ||
| * What happened to this request's Codex pool binding, and why (#4546). A move discards the | ||
| * prompt-cache prefix warmed on the previous account, so it is recorded as an event rather | ||
| * than left to be inferred from account labels across rows. Additive; older rows omit it. | ||
| */ | ||
| affinity?: CodexAffinityMove; | ||
| affinityReason?: CodexAffinityReason; | ||
|
Comment on lines
+196
to
+197
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For every request carrying an affinity decision, Useful? React with 👍 / 👎.
coderabbitai[bot] marked this conversation as resolved.
|
||
| /** | ||
| * Bounded route-decision trace (RI-01): why this provider/model/account was | ||
| * selected. Additive field; old rows without it parse unchanged. Never | ||
|
|
@@ -248,6 +256,28 @@ export function isKnownTerminalSource(value: unknown): value is NonNullable<Pers | |
| return typeof value === "string" && KNOWN_TERMINAL_SOURCES.has(value as NonNullable<PersistedUsageEntry["terminalSource"]>); | ||
| } | ||
|
|
||
| /** | ||
| * The persisted entry is built by an explicit whitelist, so a field the writer sets but this | ||
| * normalizer does not name is dropped without a word. #4592 added the affinity record at the | ||
| * call site and it never reached disk for exactly that reason. | ||
| */ | ||
| const KNOWN_AFFINITY_MOVES = new Set<NonNullable<PersistedUsageEntry["affinity"]>>([ | ||
| "reused", "held", "detour", "rebound", "new_bind", "cleared", | ||
| ]); | ||
| const KNOWN_AFFINITY_REASONS = new Set<NonNullable<PersistedUsageEntry["affinityReason"]>>([ | ||
| "healthy", "quota_headroom", "quota_refusal", "transient", "transient_hold_expired", | ||
| "unusable", "paused", "plan_excluded", "cooldown", "quota_avoided", "generation", | ||
| "expired", "model_lane", | ||
| ]); | ||
|
|
||
| export function isKnownAffinityMove(value: unknown): value is NonNullable<PersistedUsageEntry["affinity"]> { | ||
| return typeof value === "string" && KNOWN_AFFINITY_MOVES.has(value as NonNullable<PersistedUsageEntry["affinity"]>); | ||
| } | ||
|
|
||
| export function isKnownAffinityReason(value: unknown): value is NonNullable<PersistedUsageEntry["affinityReason"]> { | ||
| return typeof value === "string" && KNOWN_AFFINITY_REASONS.has(value as NonNullable<PersistedUsageEntry["affinityReason"]>); | ||
| } | ||
|
|
||
| export function usageLogPath(configDir?: string): string { | ||
| return join(configDir ?? getConfigDir(), "usage.jsonl"); | ||
| } | ||
|
|
@@ -590,6 +620,11 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry { | |
| const claudeCompatibility = normalizeClaudeCompatibilityUsageLog(entry.claudeCompatibility); | ||
| const transportPhase = isKnownTransportPhase(entry.transportPhase) ? entry.transportPhase : undefined; | ||
| const terminalSource = isKnownTerminalSource(entry.terminalSource) ? entry.terminalSource : undefined; | ||
| const affinity = isKnownAffinityMove(entry.affinity) ? entry.affinity : undefined; | ||
| // A reason without a move describes nothing, so it is only kept alongside one. | ||
| const affinityReason = affinity !== undefined && isKnownAffinityReason(entry.affinityReason) | ||
| ? entry.affinityReason | ||
| : undefined; | ||
| const routeDecision = entry.routeDecision | ||
| ? normalizeRouteDecisionTrace(entry.routeDecision) | ||
| : undefined; | ||
|
|
@@ -660,6 +695,8 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry { | |
| ...(Array.isArray(entry.attempts) ? { attempts } : {}), | ||
| ...(transportPhase ? { transportPhase } : {}), | ||
| ...(terminalSource ? { terminalSource } : {}), | ||
| ...(affinity ? { affinity } : {}), | ||
| ...(affinityReason ? { affinityReason } : {}), | ||
| ...(entry.errorCode ? { errorCode: entry.errorCode } : {}), | ||
| ...(entry.terminalStatus ? { terminalStatus: entry.terminalStatus } : {}), | ||
| ...(entry.closeReason ? { closeReason: entry.closeReason } : {}), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes the user-visible JSON returned by both the management endpoint and
ocx logs explain, but the operator references still describe the explanation as only trace, attempts, and outcome, while the documented CLI JSON shape omitsaffinity. Update the English reference, its translated counterparts, and the CLI JSON-shape reference so operators can interpretmove,reason, and the legacynullcase.AGENTS.md reference: AGENTS.md:L380-L381
Useful? React with 👍 / 👎.