@bitkyc08/opencodex 2.65.0-preview.20260925 → 2.66.0-preview.20260925
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/gui/dist/assets/App-BVjA0T6O.js +51 -0
- package/gui/dist/assets/App-gkoJIEOV.css +1 -0
- package/gui/dist/assets/{Tray-DUvc_Wul.js → Tray-BCFtiWwD.js} +1 -1
- package/gui/dist/assets/index-ComVsdSk.css +1 -0
- package/gui/dist/assets/index-vO4dgfiU.js +86 -0
- package/gui/dist/assets/{tray-data-f0lTZ4sF.js → tray-data-C4FiNaxk.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +80 -0
- package/src/adapters/anthropic/passthrough.ts +221 -0
- package/src/adapters/anthropic-image-codec.ts +56 -19
- package/src/adapters/anthropic-image-normalize.ts +20 -10
- package/src/adapters/anthropic.ts +55 -24
- package/src/adapters/coding-agent/protocol.ts +29 -19
- package/src/adapters/cursor/live-transport.ts +18 -3
- package/src/adapters/cursor/native-exec-shell.ts +18 -63
- package/src/adapters/cursor/native-exec.ts +5 -2
- package/src/adapters/cursor/native-foreground-shell.ts +44 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -1
- package/src/adapters/exec-tool-result-normalize.ts +4 -1
- package/src/adapters/google.ts +12 -0
- package/src/adapters/ollama-native.ts +32 -1
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +131 -34
- package/src/adapters/openai-chat.ts +6 -4
- package/src/adapters/openai-responses/passthrough.ts +1 -0
- package/src/adapters/openai-responses/reasoning.ts +45 -1
- package/src/bridge/response-json.ts +10 -2
- package/src/chat/outbound.ts +104 -52
- package/src/claude/claude-code-block.ts +16 -0
- package/src/claude/context-windows.ts +52 -4
- package/src/claude/desktop-first-party.ts +25 -8
- package/src/claude/inbound.ts +22 -0
- package/src/claude/intercept/connect-proxy.ts +37 -4
- package/src/claude/intercept/proxy-auth.ts +166 -0
- package/src/claude/intercept/runtime.ts +21 -0
- package/src/claude/intercept/settings.ts +33 -12
- package/src/claude/outbound.ts +45 -27
- package/src/cli/account-extended.ts +47 -15
- package/src/cli/account-target.ts +115 -0
- package/src/cli/account.ts +20 -28
- package/src/cli/agent.ts +10 -1
- package/src/cli/api-protocols.ts +206 -0
- package/src/cli/capabilities.ts +86 -0
- package/src/cli/combo.ts +2 -2
- package/src/cli/connect.ts +127 -3
- package/src/cli/dispatch.ts +8 -0
- package/src/cli/doctor.ts +33 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/link.ts +288 -0
- package/src/cli/registry.ts +23 -0
- package/src/cli/runtime-api.ts +79 -0
- package/src/cli/system-command.ts +21 -2
- package/src/client/connect.ts +125 -23
- package/src/client/hub-relay.ts +12 -8
- package/src/client/link-join.ts +323 -0
- package/src/client/link-relay.ts +241 -0
- package/src/client/link-state.ts +110 -0
- package/src/client/link-teardown.ts +63 -0
- package/src/client/link-tunnel.ts +465 -0
- package/src/client/machine-listener.ts +14 -5
- package/src/client/runtime.ts +91 -42
- package/src/client/state.ts +4 -0
- package/src/codex/catalog/build-entries.ts +4 -0
- package/src/codex/catalog/derive-entry.ts +5 -0
- package/src/codex/catalog/parsing.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +4 -1
- package/src/codex/desktop-switches.ts +88 -8
- package/src/codex/inject/plan.ts +16 -3
- package/src/codex/inject.ts +3 -0
- package/src/codex/internal/catalog-writer.ts +13 -2
- package/src/combos/failover.ts +3 -2
- package/src/combos/index.ts +12 -0
- package/src/combos/jev.ts +646 -0
- package/src/combos/request.ts +5 -0
- package/src/combos/resolve.ts +17 -15
- package/src/combos/types.ts +47 -3
- package/src/config/atomic-write.ts +3 -0
- package/src/config/live-reconcile.ts +20 -4
- package/src/config/persist-unlocked.ts +20 -7
- package/src/config/schema/config-schema.ts +16 -0
- package/src/config/schema/leaf-validators.ts +38 -1
- package/src/generated/compatibility-version.json +416 -128
- package/src/integrations/omp-yaml-source.ts +109 -4
- package/src/lab/automation/orchestrator.ts +60 -4
- package/src/lab/events/limits.ts +1 -1
- package/src/lib/bounded-body.ts +55 -38
- package/src/lib/config-ownership.ts +11 -22
- package/src/lib/local-desktop-snapshot-capability.ts +56 -0
- package/src/lib/local-management-capability.ts +25 -3
- package/src/lib/optional-shutdown-hooks.ts +17 -0
- package/src/lib/state-store-registrations.ts +2 -2
- package/src/lib/windows-elevation.ts +62 -25
- package/src/lib/windows-secret-acl.ts +114 -23
- package/src/link/admission-wait.ts +73 -0
- package/src/link/compensation.ts +100 -0
- package/src/link/fingerprint.ts +21 -0
- package/src/link/paths.ts +19 -0
- package/src/link/ports.ts +12 -0
- package/src/link/routes.ts +32 -0
- package/src/link/ssh-argv.ts +170 -0
- package/src/link/ssh-config.ts +152 -0
- package/src/link/ssh-runner.ts +161 -0
- package/src/link/status-projection.ts +96 -0
- package/src/link/store.ts +139 -0
- package/src/link/supervisor.ts +404 -0
- package/src/link/tunnel-state.ts +91 -0
- package/src/protocols/baseline.ts +88 -0
- package/src/protocols/codecs/chat.ts +17 -0
- package/src/protocols/codecs/messages.ts +18 -0
- package/src/protocols/codecs/responses.ts +17 -0
- package/src/protocols/contract.ts +185 -0
- package/src/protocols/dto.ts +267 -0
- package/src/protocols/encoders/adapter-events.ts +876 -0
- package/src/protocols/encoders/chat.ts +243 -0
- package/src/protocols/encoders/messages.ts +462 -0
- package/src/protocols/envelope.ts +43 -0
- package/src/protocols/features.ts +295 -0
- package/src/protocols/guard.ts +33 -0
- package/src/protocols/opaque-state.ts +132 -0
- package/src/protocols/path.ts +39 -0
- package/src/protocols/plan-snapshot.ts +226 -0
- package/src/protocols/plan.ts +192 -0
- package/src/protocols/provider-summary.ts +101 -0
- package/src/protocols/settings.ts +118 -0
- package/src/protocols/shadow-plan.ts +36 -0
- package/src/protocols/shadow.ts +62 -0
- package/src/protocols/trace.ts +335 -0
- package/src/providers/model-rename-migration.ts +3 -3
- package/src/providers/registry/entries-core.ts +6 -1
- package/src/providers/registry/entries-extended.ts +15 -0
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/types.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/remote-control/workspace-hub.ts +5 -4
- package/src/responses/apply-patch-envelope.ts +6 -1
- package/src/responses/code-mode-shell-input.ts +4 -2
- package/src/responses/freeform-wrapper-scan.ts +51 -24
- package/src/responses/progressive-freeform-input.ts +14 -20
- package/src/responses/spill-store.ts +187 -33
- package/src/responses/state/snapshot-select.ts +49 -0
- package/src/responses/state/spill-inspect.ts +46 -0
- package/src/responses/state/spill-queue.ts +16 -0
- package/src/responses/state.ts +51 -27
- package/src/server/audio-client.ts +8 -3
- package/src/server/audio-upstream.ts +9 -4
- package/src/server/auth-cors.ts +25 -8
- package/src/server/chat-completions.ts +71 -11
- package/src/server/chat-native-eligibility.ts +74 -0
- package/src/server/chat-native.ts +139 -74
- package/src/server/claude-messages.ts +140 -22
- package/src/server/hub-usage.ts +4 -3
- package/src/server/index/link-listener.ts +195 -0
- package/src/server/index/optional-listeners.ts +115 -0
- package/src/server/index/serve-options.ts +44 -10
- package/src/server/index.ts +10 -9
- package/src/server/inference/attempt.ts +51 -0
- package/src/server/inference/client-encoder-delivery.ts +235 -0
- package/src/server/inference/client-wire-log.ts +49 -0
- package/src/server/inference/client-wire.ts +69 -0
- package/src/server/inference/context.ts +15 -0
- package/src/server/inference/final-log.ts +37 -0
- package/src/server/local-desktop-snapshot-auth.ts +57 -0
- package/src/server/management/agent-settings-routes.ts +15 -13
- package/src/server/management/api-access.ts +11 -1
- package/src/server/management/config-routes.ts +3 -6
- package/src/server/management/context.ts +25 -0
- package/src/server/management/link-routes.ts +535 -0
- package/src/server/management/logs-usage-routes.ts +31 -1
- package/src/server/management/native-integration-routes.ts +6 -11
- package/src/server/management/oauth-account-routes.ts +42 -16
- package/src/server/management/protocol-routes.ts +159 -0
- package/src/server/management/protocol-settings-patch.ts +145 -0
- package/src/server/management/provider-patch-transaction.ts +61 -0
- package/src/server/management/provider-routes.ts +39 -18
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sidebar-routes.ts +8 -3
- package/src/server/management/system-routes.ts +18 -1
- package/src/server/management/usage-aggregate-cache.ts +206 -6
- package/src/server/management-api.ts +26 -1
- package/src/server/management-auth.ts +19 -5
- package/src/server/messages-native-eligibility.ts +161 -0
- package/src/server/messages-native-oauth.ts +149 -0
- package/src/server/messages-native.ts +790 -0
- package/src/server/relay.ts +9 -0
- package/src/server/request-log-filter.ts +92 -0
- package/src/server/request-log.ts +31 -70
- package/src/server/responses/adapter-delivery.ts +43 -16
- package/src/server/responses/agent-task-recovery.ts +90 -22
- package/src/server/responses/codex-ws-exchange.ts +4 -0
- package/src/server/responses/codex-ws-wire.ts +10 -1
- package/src/server/responses/core-combo-native.ts +323 -0
- package/src/server/responses/core-combo.ts +224 -13
- package/src/server/responses/core-options.ts +22 -0
- package/src/server/responses/core.ts +14 -26
- package/src/server/responses/request-sidecar-auth.ts +8 -3
- package/src/server/responses/run-turn-execution.ts +257 -63
- package/src/server/responses/sidecar-execution.ts +7 -4
- package/src/server/system-env.ts +7 -0
- package/src/service/diagnostics.ts +7 -1
- package/src/service/launchd.ts +26 -37
- package/src/service/windows-ops.ts +24 -20
- package/src/types/config.ts +58 -1
- package/src/types.ts +2 -0
- package/src/update/transactional-install.d.mts +0 -1
- package/src/update/transactional-install.mjs +17 -17
- package/src/usage/jev-stats.ts +495 -0
- package/src/usage/log.ts +24 -3
- package/src/web-search/run-turn-loop.ts +568 -0
- package/gui/dist/assets/App-8NMiZxT0.css +0 -1
- package/gui/dist/assets/App-CsYvvpr3.js +0 -50
- package/gui/dist/assets/index--EWgGQvZ.css +0 -1
- package/gui/dist/assets/index-BB0iHG8-.js +0 -86
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import type { OcxComboDefaultEffort } from "../types";
|
|
2
|
+
import type { PersistedUsageEntry } from "./log";
|
|
3
|
+
import { usageDisplayTotalTokens } from "./totals";
|
|
4
|
+
|
|
5
|
+
export const JEV_DECISION_GATES = [
|
|
6
|
+
"apply",
|
|
7
|
+
"missing_key",
|
|
8
|
+
"no_choices",
|
|
9
|
+
"no_state",
|
|
10
|
+
"timeout",
|
|
11
|
+
"network",
|
|
12
|
+
"redirect",
|
|
13
|
+
"http",
|
|
14
|
+
"malformed",
|
|
15
|
+
"invalid",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type JevDecisionGate = (typeof JEV_DECISION_GATES)[number];
|
|
19
|
+
|
|
20
|
+
const JEV_GATE_SET = new Set<string>(JEV_DECISION_GATES);
|
|
21
|
+
const JEV_EFFORTS = new Set<OcxComboDefaultEffort>([
|
|
22
|
+
"low", "medium", "high", "xhigh", "max", "ultra",
|
|
23
|
+
]);
|
|
24
|
+
const MAX_COMBO_ID_CHARS = 128;
|
|
25
|
+
// JEV accepts provider/model candidate fields up to 512 characters. Preserve
|
|
26
|
+
// that same identity on telemetry so a selected target still joins its attempt.
|
|
27
|
+
const MAX_TARGET_IDENTITY_CHARS = 512;
|
|
28
|
+
const CONTROL_CHARS = /[\u0000-\u001f\u007f]/u;
|
|
29
|
+
|
|
30
|
+
export interface PersistedJevDecisionV1 {
|
|
31
|
+
version: 1;
|
|
32
|
+
comboId: string;
|
|
33
|
+
selected: {
|
|
34
|
+
provider: string;
|
|
35
|
+
model: string;
|
|
36
|
+
effort: OcxComboDefaultEffort | null;
|
|
37
|
+
};
|
|
38
|
+
gate: JevDecisionGate;
|
|
39
|
+
latencyMs: number;
|
|
40
|
+
confidence?: number;
|
|
41
|
+
chosenProbability?: number;
|
|
42
|
+
usage?: {
|
|
43
|
+
inputTokens: number;
|
|
44
|
+
outputTokens: number;
|
|
45
|
+
totalTokens: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface JevStatsModelRow {
|
|
50
|
+
provider: string;
|
|
51
|
+
model: string;
|
|
52
|
+
/** Aggregate bucket for identities beyond the retained model-cardinality cap. */
|
|
53
|
+
overflow: boolean;
|
|
54
|
+
picks: number;
|
|
55
|
+
appliedPicks: number;
|
|
56
|
+
failOpenPicks: number;
|
|
57
|
+
attempts: number;
|
|
58
|
+
measuredAttempts: number;
|
|
59
|
+
inputTokens: number;
|
|
60
|
+
outputTokens: number;
|
|
61
|
+
reasoningTokens: number;
|
|
62
|
+
cacheReadTokens: number;
|
|
63
|
+
cacheWriteTokens: number;
|
|
64
|
+
totalTokens: number;
|
|
65
|
+
efforts: Array<{ effort: OcxComboDefaultEffort | null; picks: number }>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const MAX_JEV_STATS_MODEL_ROWS = 256;
|
|
69
|
+
|
|
70
|
+
export interface JevStatsAccumulator {
|
|
71
|
+
readonly estimatedBytes: number;
|
|
72
|
+
add(entry: PersistedUsageEntry): void;
|
|
73
|
+
clone(): JevStatsAccumulator;
|
|
74
|
+
summarize(range: string, generatedAt: number): JevStatsResponse;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface JevStatsResponse {
|
|
78
|
+
range: string;
|
|
79
|
+
comboId: string | null;
|
|
80
|
+
since: number | null;
|
|
81
|
+
until?: number;
|
|
82
|
+
generatedAt: number;
|
|
83
|
+
summary: {
|
|
84
|
+
decisions: number;
|
|
85
|
+
appliedDecisions: number;
|
|
86
|
+
failOpenDecisions: number;
|
|
87
|
+
successfulRequests: number;
|
|
88
|
+
requestsWithModelFallback: number;
|
|
89
|
+
modelAttempts: number;
|
|
90
|
+
measuredModelAttempts: number;
|
|
91
|
+
modelInputTokens: number;
|
|
92
|
+
modelOutputTokens: number;
|
|
93
|
+
modelReasoningTokens: number;
|
|
94
|
+
modelCacheReadTokens: number;
|
|
95
|
+
modelCacheWriteTokens: number;
|
|
96
|
+
modelTotalTokens: number;
|
|
97
|
+
decisionUsageReported: number;
|
|
98
|
+
decisionInputTokens: number;
|
|
99
|
+
decisionOutputTokens: number;
|
|
100
|
+
decisionTotalTokens: number;
|
|
101
|
+
averageLatencyMs: number | null;
|
|
102
|
+
averageConfidence: number | null;
|
|
103
|
+
averageChosenProbability: number | null;
|
|
104
|
+
};
|
|
105
|
+
gates: Array<{ gate: JevDecisionGate; decisions: number }>;
|
|
106
|
+
models: JevStatsModelRow[];
|
|
107
|
+
snapshotWindowStart: number | null;
|
|
108
|
+
snapshotWindowEnd: number | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
112
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function boundedIdentity(value: unknown, maxLength: number): string | undefined {
|
|
116
|
+
if (typeof value !== "string") return undefined;
|
|
117
|
+
const normalized = value.trim();
|
|
118
|
+
if (!normalized || CONTROL_CHARS.test(normalized)) return undefined;
|
|
119
|
+
return normalized.slice(0, maxLength);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function normalizeJevStatsComboId(value: unknown): string | undefined {
|
|
123
|
+
return boundedIdentity(value, MAX_COMBO_ID_CHARS);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function boundedCount(value: unknown): number | undefined {
|
|
127
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return undefined;
|
|
128
|
+
return Math.min(Number.MAX_SAFE_INTEGER, Math.round(value));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function saturatingAdd(total: number, value: number): number {
|
|
132
|
+
if (!Number.isFinite(value) || value <= 0) return total;
|
|
133
|
+
return total >= Number.MAX_SAFE_INTEGER - value
|
|
134
|
+
? Number.MAX_SAFE_INTEGER
|
|
135
|
+
: total + value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function probability(value: unknown): number | undefined {
|
|
139
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1
|
|
140
|
+
? value
|
|
141
|
+
: undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizedDecisionUsage(value: unknown): PersistedJevDecisionV1["usage"] {
|
|
145
|
+
if (!isRecord(value)) return undefined;
|
|
146
|
+
const inputTokens = boundedCount(value.inputTokens ?? value.input_tokens);
|
|
147
|
+
const outputTokens = boundedCount(value.outputTokens ?? value.output_tokens);
|
|
148
|
+
if (inputTokens === undefined || outputTokens === undefined) return undefined;
|
|
149
|
+
return {
|
|
150
|
+
inputTokens,
|
|
151
|
+
outputTokens,
|
|
152
|
+
totalTokens: saturatingAdd(inputTokens, outputTokens),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Re-validates the privacy-bounded decision record at both write and hydration boundaries.
|
|
158
|
+
* Only closed enums, bounded identifiers and finite counters survive; prompts and credentials
|
|
159
|
+
* have no slot in this shape.
|
|
160
|
+
*/
|
|
161
|
+
export function normalizePersistedJevDecision(value: unknown): PersistedJevDecisionV1 | undefined {
|
|
162
|
+
if (!isRecord(value) || value.version !== 1 || !isRecord(value.selected)) return undefined;
|
|
163
|
+
const comboId = boundedIdentity(value.comboId, MAX_COMBO_ID_CHARS);
|
|
164
|
+
const provider = boundedIdentity(value.selected.provider, MAX_TARGET_IDENTITY_CHARS);
|
|
165
|
+
const model = boundedIdentity(value.selected.model, MAX_TARGET_IDENTITY_CHARS);
|
|
166
|
+
const effort = value.selected.effort;
|
|
167
|
+
const gate = value.gate;
|
|
168
|
+
const latencyMs = boundedCount(value.latencyMs);
|
|
169
|
+
if (!comboId || !provider || !model || latencyMs === undefined) return undefined;
|
|
170
|
+
if (effort !== null && (typeof effort !== "string" || !JEV_EFFORTS.has(effort as OcxComboDefaultEffort))) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
if (typeof gate !== "string" || !JEV_GATE_SET.has(gate)) return undefined;
|
|
174
|
+
const confidence = probability(value.confidence);
|
|
175
|
+
const chosenProbability = probability(value.chosenProbability);
|
|
176
|
+
const usage = normalizedDecisionUsage(value.usage);
|
|
177
|
+
return {
|
|
178
|
+
version: 1,
|
|
179
|
+
comboId,
|
|
180
|
+
selected: {
|
|
181
|
+
provider,
|
|
182
|
+
model,
|
|
183
|
+
effort: effort as OcxComboDefaultEffort | null,
|
|
184
|
+
},
|
|
185
|
+
gate: gate as JevDecisionGate,
|
|
186
|
+
latencyMs,
|
|
187
|
+
...(confidence !== undefined ? { confidence } : {}),
|
|
188
|
+
...(chosenProbability !== undefined ? { chosenProbability } : {}),
|
|
189
|
+
...(usage ? { usage } : {}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
interface MutableModelRow extends Omit<JevStatsModelRow, "efforts"> {
|
|
194
|
+
effortCounts: Map<OcxComboDefaultEffort | null, number>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface JevStatsAccumulatorOptions {
|
|
198
|
+
comboId?: string | null;
|
|
199
|
+
since?: number | null;
|
|
200
|
+
until?: number;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cacheReadTokens(usage: NonNullable<NonNullable<PersistedUsageEntry["attempts"]>[number]["usage"]>): number {
|
|
204
|
+
if (typeof usage.cacheReadInputTokens === "number") return usage.cacheReadInputTokens;
|
|
205
|
+
if (typeof usage.cachedInputTokens !== "number") return 0;
|
|
206
|
+
return typeof usage.cacheCreationInputTokens === "number"
|
|
207
|
+
? Math.max(0, usage.cachedInputTokens - usage.cacheCreationInputTokens)
|
|
208
|
+
: usage.cachedInputTokens;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function finiteToken(value: number | undefined): number {
|
|
212
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0) return 0;
|
|
213
|
+
return Math.min(Number.MAX_SAFE_INTEGER, value);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function mean(total: number, count: number): number | null {
|
|
217
|
+
return count > 0 ? total / count : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function nextMean(current: number | null, count: number, value: number): number {
|
|
221
|
+
if (current === null || count === 0) return value;
|
|
222
|
+
if (count >= Number.MAX_SAFE_INTEGER) return current;
|
|
223
|
+
return current + (value - current) / (count + 1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function modelKey(provider: string, model: string): string {
|
|
227
|
+
return `${provider}\0${model}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function createJevStatsAccumulator(options: JevStatsAccumulatorOptions = {}): JevStatsAccumulator {
|
|
231
|
+
return new StreamingJevStatsAccumulator(options);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function blankModelRow(provider: string, model: string, overflow = false): MutableModelRow {
|
|
235
|
+
return {
|
|
236
|
+
provider,
|
|
237
|
+
model,
|
|
238
|
+
overflow,
|
|
239
|
+
picks: 0,
|
|
240
|
+
appliedPicks: 0,
|
|
241
|
+
failOpenPicks: 0,
|
|
242
|
+
attempts: 0,
|
|
243
|
+
measuredAttempts: 0,
|
|
244
|
+
inputTokens: 0,
|
|
245
|
+
outputTokens: 0,
|
|
246
|
+
reasoningTokens: 0,
|
|
247
|
+
cacheReadTokens: 0,
|
|
248
|
+
cacheWriteTokens: 0,
|
|
249
|
+
totalTokens: 0,
|
|
250
|
+
effortCounts: new Map(),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function cloneModelRow(row: MutableModelRow): MutableModelRow {
|
|
255
|
+
return { ...row, effortCounts: new Map(row.effortCounts) };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function publicModelRow({ effortCounts, ...row }: MutableModelRow): JevStatsModelRow {
|
|
259
|
+
return {
|
|
260
|
+
...row,
|
|
261
|
+
efforts: [...effortCounts].map(([effort, picks]) => ({ effort, picks })),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
class StreamingJevStatsAccumulator implements JevStatsAccumulator {
|
|
266
|
+
private readonly comboId: string | null;
|
|
267
|
+
private readonly since: number | null;
|
|
268
|
+
private readonly until: number | undefined;
|
|
269
|
+
private readonly models = new Map<string, MutableModelRow>();
|
|
270
|
+
private overflowModel: MutableModelRow | null = null;
|
|
271
|
+
private readonly gates = new Map<JevDecisionGate, number>();
|
|
272
|
+
private snapshotWindowStart: number | null = null;
|
|
273
|
+
private snapshotWindowEnd: number | null = null;
|
|
274
|
+
private decisions = 0;
|
|
275
|
+
private appliedDecisions = 0;
|
|
276
|
+
private successfulRequests = 0;
|
|
277
|
+
private requestsWithModelFallback = 0;
|
|
278
|
+
private modelAttempts = 0;
|
|
279
|
+
private measuredModelAttempts = 0;
|
|
280
|
+
private modelInputTokens = 0;
|
|
281
|
+
private modelOutputTokens = 0;
|
|
282
|
+
private modelReasoningTokens = 0;
|
|
283
|
+
private modelCacheReadTokens = 0;
|
|
284
|
+
private modelCacheWriteTokens = 0;
|
|
285
|
+
private modelTotalTokens = 0;
|
|
286
|
+
private decisionUsageReported = 0;
|
|
287
|
+
private decisionInputTokens = 0;
|
|
288
|
+
private decisionOutputTokens = 0;
|
|
289
|
+
private decisionTotalTokens = 0;
|
|
290
|
+
private averageLatencyMs: number | null = null;
|
|
291
|
+
private confidenceTotal = 0;
|
|
292
|
+
private confidenceCount = 0;
|
|
293
|
+
private probabilityTotal = 0;
|
|
294
|
+
private probabilityCount = 0;
|
|
295
|
+
|
|
296
|
+
constructor(options: JevStatsAccumulatorOptions = {}) {
|
|
297
|
+
this.comboId = boundedIdentity(options.comboId, MAX_COMBO_ID_CHARS) ?? null;
|
|
298
|
+
this.since = options.since === null || options.since === undefined ? null : options.since;
|
|
299
|
+
this.until = options.until;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
get estimatedBytes(): number {
|
|
303
|
+
let bytes = 512;
|
|
304
|
+
for (const row of this.models.values()) {
|
|
305
|
+
bytes += 256 + (row.provider.length + row.model.length) * 2 + row.effortCounts.size * 32;
|
|
306
|
+
}
|
|
307
|
+
if (this.overflowModel) bytes += 256 + this.overflowModel.effortCounts.size * 32;
|
|
308
|
+
return bytes;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private rowFor(provider: string, model: string): MutableModelRow {
|
|
312
|
+
const key = modelKey(provider, model);
|
|
313
|
+
const existing = this.models.get(key);
|
|
314
|
+
if (existing) return existing;
|
|
315
|
+
// Keep one of the 256 public rows for the aggregate overflow bucket. This
|
|
316
|
+
// bounds retained memory, sort work and response size even if a ledger was
|
|
317
|
+
// populated with attacker-controlled or short-lived model identities.
|
|
318
|
+
if (this.models.size < MAX_JEV_STATS_MODEL_ROWS - 1) {
|
|
319
|
+
const row = blankModelRow(provider, model);
|
|
320
|
+
this.models.set(key, row);
|
|
321
|
+
return row;
|
|
322
|
+
}
|
|
323
|
+
// Empty target identities never survive normalization, so the explicit
|
|
324
|
+
// overflow flag plus this empty tuple cannot collide with a real row.
|
|
325
|
+
this.overflowModel ??= blankModelRow("", "", true);
|
|
326
|
+
return this.overflowModel;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
add(entry: PersistedUsageEntry): void {
|
|
330
|
+
if (Number.isFinite(entry.timestamp)) {
|
|
331
|
+
this.snapshotWindowStart = this.snapshotWindowStart === null
|
|
332
|
+
? entry.timestamp
|
|
333
|
+
: Math.min(this.snapshotWindowStart, entry.timestamp);
|
|
334
|
+
this.snapshotWindowEnd = this.snapshotWindowEnd === null
|
|
335
|
+
? entry.timestamp
|
|
336
|
+
: Math.max(this.snapshotWindowEnd, entry.timestamp);
|
|
337
|
+
}
|
|
338
|
+
if ((this.since !== null && entry.timestamp < this.since)
|
|
339
|
+
|| (this.until !== undefined && entry.timestamp > this.until)) return;
|
|
340
|
+
const decision = normalizePersistedJevDecision(entry.jevDecision);
|
|
341
|
+
if (!decision || (this.comboId !== null && decision.comboId !== this.comboId)) return;
|
|
342
|
+
|
|
343
|
+
this.averageLatencyMs = nextMean(this.averageLatencyMs, this.decisions, decision.latencyMs);
|
|
344
|
+
this.decisions = saturatingAdd(this.decisions, 1);
|
|
345
|
+
if (decision.gate === "apply") this.appliedDecisions = saturatingAdd(this.appliedDecisions, 1);
|
|
346
|
+
if (entry.status >= 200 && entry.status < 400) {
|
|
347
|
+
this.successfulRequests = saturatingAdd(this.successfulRequests, 1);
|
|
348
|
+
}
|
|
349
|
+
this.gates.set(decision.gate, saturatingAdd(this.gates.get(decision.gate) ?? 0, 1));
|
|
350
|
+
if (decision.confidence !== undefined) {
|
|
351
|
+
this.confidenceTotal = saturatingAdd(this.confidenceTotal, decision.confidence);
|
|
352
|
+
this.confidenceCount = saturatingAdd(this.confidenceCount, 1);
|
|
353
|
+
}
|
|
354
|
+
if (decision.chosenProbability !== undefined) {
|
|
355
|
+
this.probabilityTotal = saturatingAdd(this.probabilityTotal, decision.chosenProbability);
|
|
356
|
+
this.probabilityCount = saturatingAdd(this.probabilityCount, 1);
|
|
357
|
+
}
|
|
358
|
+
if (decision.usage) {
|
|
359
|
+
this.decisionUsageReported = saturatingAdd(this.decisionUsageReported, 1);
|
|
360
|
+
this.decisionInputTokens = saturatingAdd(this.decisionInputTokens, decision.usage.inputTokens);
|
|
361
|
+
this.decisionOutputTokens = saturatingAdd(this.decisionOutputTokens, decision.usage.outputTokens);
|
|
362
|
+
this.decisionTotalTokens = saturatingAdd(this.decisionTotalTokens, decision.usage.totalTokens);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const selected = this.rowFor(decision.selected.provider, decision.selected.model);
|
|
366
|
+
selected.picks = saturatingAdd(selected.picks, 1);
|
|
367
|
+
if (decision.gate === "apply") selected.appliedPicks = saturatingAdd(selected.appliedPicks, 1);
|
|
368
|
+
else selected.failOpenPicks = saturatingAdd(selected.failOpenPicks, 1);
|
|
369
|
+
selected.effortCounts.set(
|
|
370
|
+
decision.selected.effort,
|
|
371
|
+
saturatingAdd(selected.effortCounts.get(decision.selected.effort) ?? 0, 1),
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const attempts = (entry.attempts ?? []).flatMap(attempt => {
|
|
375
|
+
const sendCount = boundedCount(attempt.sendCount) ?? 0;
|
|
376
|
+
if (sendCount === 0) return [];
|
|
377
|
+
const provider = boundedIdentity(attempt.provider, MAX_TARGET_IDENTITY_CHARS);
|
|
378
|
+
const model = boundedIdentity(attempt.model, MAX_TARGET_IDENTITY_CHARS);
|
|
379
|
+
return provider && model ? [{ attempt, provider, model, sendCount }] : [];
|
|
380
|
+
});
|
|
381
|
+
if (attempts.some(({ provider, model }) => provider !== decision.selected.provider
|
|
382
|
+
|| model !== decision.selected.model)) {
|
|
383
|
+
this.requestsWithModelFallback = saturatingAdd(this.requestsWithModelFallback, 1);
|
|
384
|
+
}
|
|
385
|
+
for (const { attempt, provider, model: modelId, sendCount } of attempts) {
|
|
386
|
+
const model = this.rowFor(provider, modelId);
|
|
387
|
+
model.attempts = saturatingAdd(model.attempts, sendCount);
|
|
388
|
+
this.modelAttempts = saturatingAdd(this.modelAttempts, sendCount);
|
|
389
|
+
if (!attempt.usage || (attempt.usageStatus !== "reported" && attempt.usageStatus !== "estimated")) continue;
|
|
390
|
+
model.measuredAttempts = saturatingAdd(model.measuredAttempts, 1);
|
|
391
|
+
this.measuredModelAttempts = saturatingAdd(this.measuredModelAttempts, 1);
|
|
392
|
+
const inputTokens = finiteToken(attempt.usage.inputTokens);
|
|
393
|
+
const outputTokens = finiteToken(attempt.usage.outputTokens);
|
|
394
|
+
const reasoningTokens = finiteToken(attempt.usage.reasoningOutputTokens);
|
|
395
|
+
const readTokens = finiteToken(cacheReadTokens(attempt.usage));
|
|
396
|
+
const writeTokens = finiteToken(attempt.usage.cacheCreationInputTokens);
|
|
397
|
+
const totalTokens = finiteToken(usageDisplayTotalTokens(attempt.usage, attempt.totalTokens));
|
|
398
|
+
model.inputTokens = saturatingAdd(model.inputTokens, inputTokens);
|
|
399
|
+
model.outputTokens = saturatingAdd(model.outputTokens, outputTokens);
|
|
400
|
+
model.reasoningTokens = saturatingAdd(model.reasoningTokens, reasoningTokens);
|
|
401
|
+
model.cacheReadTokens = saturatingAdd(model.cacheReadTokens, readTokens);
|
|
402
|
+
model.cacheWriteTokens = saturatingAdd(model.cacheWriteTokens, writeTokens);
|
|
403
|
+
model.totalTokens = saturatingAdd(model.totalTokens, totalTokens);
|
|
404
|
+
this.modelInputTokens = saturatingAdd(this.modelInputTokens, inputTokens);
|
|
405
|
+
this.modelOutputTokens = saturatingAdd(this.modelOutputTokens, outputTokens);
|
|
406
|
+
this.modelReasoningTokens = saturatingAdd(this.modelReasoningTokens, reasoningTokens);
|
|
407
|
+
this.modelCacheReadTokens = saturatingAdd(this.modelCacheReadTokens, readTokens);
|
|
408
|
+
this.modelCacheWriteTokens = saturatingAdd(this.modelCacheWriteTokens, writeTokens);
|
|
409
|
+
this.modelTotalTokens = saturatingAdd(this.modelTotalTokens, totalTokens);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
clone(): JevStatsAccumulator {
|
|
414
|
+
const cloned = new StreamingJevStatsAccumulator({
|
|
415
|
+
comboId: this.comboId,
|
|
416
|
+
since: this.since,
|
|
417
|
+
...(this.until !== undefined ? { until: this.until } : {}),
|
|
418
|
+
});
|
|
419
|
+
for (const [key, row] of this.models) cloned.models.set(key, cloneModelRow(row));
|
|
420
|
+
cloned.overflowModel = this.overflowModel ? cloneModelRow(this.overflowModel) : null;
|
|
421
|
+
for (const [gate, count] of this.gates) cloned.gates.set(gate, count);
|
|
422
|
+
cloned.snapshotWindowStart = this.snapshotWindowStart;
|
|
423
|
+
cloned.snapshotWindowEnd = this.snapshotWindowEnd;
|
|
424
|
+
cloned.decisions = this.decisions;
|
|
425
|
+
cloned.appliedDecisions = this.appliedDecisions;
|
|
426
|
+
cloned.successfulRequests = this.successfulRequests;
|
|
427
|
+
cloned.requestsWithModelFallback = this.requestsWithModelFallback;
|
|
428
|
+
cloned.modelAttempts = this.modelAttempts;
|
|
429
|
+
cloned.measuredModelAttempts = this.measuredModelAttempts;
|
|
430
|
+
cloned.modelInputTokens = this.modelInputTokens;
|
|
431
|
+
cloned.modelOutputTokens = this.modelOutputTokens;
|
|
432
|
+
cloned.modelReasoningTokens = this.modelReasoningTokens;
|
|
433
|
+
cloned.modelCacheReadTokens = this.modelCacheReadTokens;
|
|
434
|
+
cloned.modelCacheWriteTokens = this.modelCacheWriteTokens;
|
|
435
|
+
cloned.modelTotalTokens = this.modelTotalTokens;
|
|
436
|
+
cloned.decisionUsageReported = this.decisionUsageReported;
|
|
437
|
+
cloned.decisionInputTokens = this.decisionInputTokens;
|
|
438
|
+
cloned.decisionOutputTokens = this.decisionOutputTokens;
|
|
439
|
+
cloned.decisionTotalTokens = this.decisionTotalTokens;
|
|
440
|
+
cloned.averageLatencyMs = this.averageLatencyMs;
|
|
441
|
+
cloned.confidenceTotal = this.confidenceTotal;
|
|
442
|
+
cloned.confidenceCount = this.confidenceCount;
|
|
443
|
+
cloned.probabilityTotal = this.probabilityTotal;
|
|
444
|
+
cloned.probabilityCount = this.probabilityCount;
|
|
445
|
+
return cloned;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
summarize(range: string, generatedAt: number): JevStatsResponse {
|
|
449
|
+
const rows = [
|
|
450
|
+
...this.models.values(),
|
|
451
|
+
...(this.overflowModel ? [this.overflowModel] : []),
|
|
452
|
+
]
|
|
453
|
+
.map(publicModelRow)
|
|
454
|
+
.sort((left, right) => right.picks - left.picks
|
|
455
|
+
|| right.attempts - left.attempts
|
|
456
|
+
|| left.provider.localeCompare(right.provider)
|
|
457
|
+
|| left.model.localeCompare(right.model));
|
|
458
|
+
return {
|
|
459
|
+
range,
|
|
460
|
+
comboId: this.comboId,
|
|
461
|
+
since: this.since,
|
|
462
|
+
...(this.until !== undefined ? { until: this.until } : {}),
|
|
463
|
+
generatedAt,
|
|
464
|
+
summary: {
|
|
465
|
+
decisions: this.decisions,
|
|
466
|
+
appliedDecisions: this.appliedDecisions,
|
|
467
|
+
failOpenDecisions: this.decisions - this.appliedDecisions,
|
|
468
|
+
successfulRequests: this.successfulRequests,
|
|
469
|
+
requestsWithModelFallback: this.requestsWithModelFallback,
|
|
470
|
+
modelAttempts: this.modelAttempts,
|
|
471
|
+
measuredModelAttempts: this.measuredModelAttempts,
|
|
472
|
+
modelInputTokens: this.modelInputTokens,
|
|
473
|
+
modelOutputTokens: this.modelOutputTokens,
|
|
474
|
+
modelReasoningTokens: this.modelReasoningTokens,
|
|
475
|
+
modelCacheReadTokens: this.modelCacheReadTokens,
|
|
476
|
+
modelCacheWriteTokens: this.modelCacheWriteTokens,
|
|
477
|
+
modelTotalTokens: this.modelTotalTokens,
|
|
478
|
+
decisionUsageReported: this.decisionUsageReported,
|
|
479
|
+
decisionInputTokens: this.decisionInputTokens,
|
|
480
|
+
decisionOutputTokens: this.decisionOutputTokens,
|
|
481
|
+
decisionTotalTokens: this.decisionTotalTokens,
|
|
482
|
+
averageLatencyMs: this.averageLatencyMs,
|
|
483
|
+
averageConfidence: mean(this.confidenceTotal, this.confidenceCount),
|
|
484
|
+
averageChosenProbability: mean(this.probabilityTotal, this.probabilityCount),
|
|
485
|
+
},
|
|
486
|
+
gates: JEV_DECISION_GATES.flatMap(gate => {
|
|
487
|
+
const count = this.gates.get(gate) ?? 0;
|
|
488
|
+
return count > 0 ? [{ gate, decisions: count }] : [];
|
|
489
|
+
}),
|
|
490
|
+
models: rows,
|
|
491
|
+
snapshotWindowStart: this.snapshotWindowStart,
|
|
492
|
+
snapshotWindowEnd: this.snapshotWindowEnd,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
package/src/usage/log.ts
CHANGED
|
@@ -7,6 +7,10 @@ import { enforceAppOwnedMemoryBudget } from "../lib/app-owned-memory";
|
|
|
7
7
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
8
8
|
import { redactSecretString, sanitizeLogMetadataString } from "../lib/redact";
|
|
9
9
|
import { usageDisplayTotalTokens } from "./totals";
|
|
10
|
+
import {
|
|
11
|
+
normalizePersistedJevDecision,
|
|
12
|
+
type PersistedJevDecisionV1,
|
|
13
|
+
} from "./jev-stats";
|
|
10
14
|
import { normalizeAttemptDeliverySummary } from "./attempt-delivery";
|
|
11
15
|
import {
|
|
12
16
|
isRequestCloseReason,
|
|
@@ -16,6 +20,7 @@ import {
|
|
|
16
20
|
} from "./request-outcome";
|
|
17
21
|
import type { AttemptTierOutcome, OcxUsage } from "../types";
|
|
18
22
|
import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace";
|
|
23
|
+
import { parseProtocolTraceV1, type ProtocolTraceV1 } from "../protocols/dto";
|
|
19
24
|
import { ACCOUNT_LOG_LABEL_RE, CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
|
|
20
25
|
import { claudeCompatibilityReason, normalizeClaudeFeatureCodes, type ClaudeFeatureCode } from "../claude/compatibility";
|
|
21
26
|
import type { CodexWsStageRecord } from "../server/responses/codex-ws-wire";
|
|
@@ -384,8 +389,15 @@ export interface PersistedUsageEntry {
|
|
|
384
389
|
* contains prompts, credentials, or hidden reasoning.
|
|
385
390
|
*/
|
|
386
391
|
routeDecision?: RouteDecisionTraceV1;
|
|
392
|
+
/** Privacy-bounded JEV selection metadata; model usage remains in attempts[]. */
|
|
393
|
+
jevDecision?: PersistedJevDecisionV1;
|
|
387
394
|
/** Closed Claude protocol codes only; absent on older rows. */
|
|
388
395
|
claudeCompatibility?: PersistedClaudeCompatibilityLog;
|
|
396
|
+
/**
|
|
397
|
+
* Observed protocol path (PF-02): fixed vocabulary only. Re-validated on every read; older
|
|
398
|
+
* rows and rows that fail validation carry none, and are never back-filled by guessing.
|
|
399
|
+
*/
|
|
400
|
+
protocolTrace?: ProtocolTraceV1;
|
|
389
401
|
/**
|
|
390
402
|
* How far this request got and why it failed (#2366). Projected from the attempt that ended
|
|
391
403
|
* the request so every surface reads the answer off the same row. Absent on a completed
|
|
@@ -790,6 +802,9 @@ function normalizeCodexWsStageRecord(value: unknown): CodexWsStageRecord | undef
|
|
|
790
802
|
}
|
|
791
803
|
if (!(stage.requestBytes === null || isNonNegativeFiniteNumber(stage.requestBytes))) return undefined;
|
|
792
804
|
if (!(stage.firstFrameMs === null || isNonNegativeFiniteNumber(stage.firstFrameMs))) return undefined;
|
|
805
|
+
// Records written before firstResponseMs existed omit it; read them as unmeasured.
|
|
806
|
+
const firstResponseMs = stage.firstResponseMs === undefined ? null : stage.firstResponseMs;
|
|
807
|
+
if (!(firstResponseMs === null || isNonNegativeFiniteNumber(firstResponseMs))) return undefined;
|
|
793
808
|
if (!(stage.elapsedMs === null || isNonNegativeFiniteNumber(stage.elapsedMs))) return undefined;
|
|
794
809
|
if (!(stage.closeCode === null || (typeof stage.closeCode === "number"
|
|
795
810
|
&& Number.isInteger(stage.closeCode) && stage.closeCode >= 1000 && stage.closeCode <= 4999))) {
|
|
@@ -805,6 +820,7 @@ function normalizeCodexWsStageRecord(value: unknown): CodexWsStageRecord | undef
|
|
|
805
820
|
controlFrames: stage.controlFrames as number,
|
|
806
821
|
relayedEvents: stage.relayedEvents as number,
|
|
807
822
|
firstFrameMs: stage.firstFrameMs as number | null,
|
|
823
|
+
firstResponseMs: firstResponseMs as number | null,
|
|
808
824
|
elapsedMs: stage.elapsedMs as number | null,
|
|
809
825
|
pings: stage.pings as number,
|
|
810
826
|
pongs: stage.pongs as number,
|
|
@@ -942,7 +958,9 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
942
958
|
const routeDecision = entry.routeDecision
|
|
943
959
|
? normalizeRouteDecisionTrace(entry.routeDecision)
|
|
944
960
|
: undefined;
|
|
961
|
+
const jevDecision = normalizePersistedJevDecision(entry.jevDecision);
|
|
945
962
|
const spend = normalizeRequestSpend(entry.spend);
|
|
963
|
+
const protocolTrace = parseProtocolTraceV1(entry.protocolTrace);
|
|
946
964
|
return {
|
|
947
965
|
requestId: entry.requestId,
|
|
948
966
|
...(isLogicalRequestId(entry.logicalRequestId) ? { logicalRequestId: entry.logicalRequestId } : {}),
|
|
@@ -1030,7 +1048,9 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
1030
1048
|
...(isRequestCloseReason(entry.closeReason) ? { closeReason: entry.closeReason } : {}),
|
|
1031
1049
|
...(entry.upstreamError ? { upstreamError: entry.upstreamError } : {}),
|
|
1032
1050
|
...(routeDecision ? { routeDecision } : {}),
|
|
1051
|
+
...(jevDecision ? { jevDecision } : {}),
|
|
1033
1052
|
...(claudeCompatibility ? { claudeCompatibility } : {}),
|
|
1053
|
+
...(protocolTrace ? { protocolTrace } : {}),
|
|
1034
1054
|
...normalizeRequestFailureAttribution(entry),
|
|
1035
1055
|
};
|
|
1036
1056
|
}
|
|
@@ -1639,9 +1659,10 @@ async function readUsageEntriesIncrementally(
|
|
|
1639
1659
|
// would make a byte-truncated read claim rows were dropped when none were.
|
|
1640
1660
|
entriesTruncated: entriesDropped > 0,
|
|
1641
1661
|
entriesDropped,
|
|
1642
|
-
//
|
|
1643
|
-
//
|
|
1644
|
-
prefixDigest:
|
|
1662
|
+
// Reuse this read's verified digest only for identical bounds. Growth or trimming
|
|
1663
|
+
// needs a new digest of the returned region; metadata alone never proves reuse.
|
|
1664
|
+
prefixDigest: rowsBeginAtBytes === retained.rowsBeginAtBytes && size === retained.coveredThroughBytes
|
|
1665
|
+
? covered : usageRegionDigest(fd, rowsBeginAtBytes, size) ?? "",
|
|
1645
1666
|
entryLengths: lengths,
|
|
1646
1667
|
trailingSkippedBytes: appendedTrailingSkipped,
|
|
1647
1668
|
rowsBeginAtBytes,
|