@bitkyc08/opencodex 2.8.0 → 2.9.0
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 +24 -0
- package/bin/ocx.mjs +32 -4
- package/gui/dist/assets/index-CHwf3tTD.css +1 -0
- package/gui/dist/assets/index-u5eFOv2y.js +67 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-normalize.ts +114 -20
- package/src/adapters/anthropic.ts +126 -10
- package/src/adapters/azure.ts +3 -3
- package/src/adapters/base.ts +7 -3
- package/src/adapters/cursor/discovery.ts +14 -4
- package/src/adapters/cursor/effort-map.ts +18 -6
- package/src/adapters/cursor/framing.ts +102 -27
- package/src/adapters/cursor/kv-store.ts +30 -3
- package/src/adapters/cursor/live-models.ts +22 -2
- package/src/adapters/cursor/live-transport.ts +245 -49
- package/src/adapters/cursor/mcp-manager.ts +105 -8
- package/src/adapters/cursor/native-exec-mcp.ts +5 -3
- package/src/adapters/cursor/native-exec-shell.ts +296 -14
- package/src/adapters/cursor/native-exec.ts +381 -33
- package/src/adapters/cursor/protobuf-events.ts +28 -1
- package/src/adapters/cursor/protobuf-request.ts +71 -39
- package/src/adapters/cursor/request-builder.ts +2 -2
- package/src/adapters/cursor/transport.ts +2 -0
- package/src/adapters/cursor.ts +13 -2
- package/src/adapters/google-antigravity-replay.ts +184 -17
- package/src/adapters/google.ts +58 -8
- package/src/adapters/kiro-thinking.ts +23 -9
- package/src/adapters/kiro-tools.ts +49 -18
- package/src/adapters/kiro.ts +377 -133
- package/src/adapters/mimo-free.ts +36 -4
- package/src/adapters/openai-chat.ts +143 -17
- package/src/adapters/openai-responses.ts +130 -14
- package/src/adapters/run-turn-queue.ts +7 -1
- package/src/bridge.ts +466 -69
- package/src/chat/outbound.ts +144 -38
- package/src/claude/inbound-debug.ts +53 -8
- package/src/claude/outbound.ts +224 -38
- package/src/cli/agent-driven.ts +34 -1
- package/src/cli/catalog-prewarm.ts +5 -2
- package/src/cli/claude-desktop.ts +2 -2
- package/src/cli/doctor.ts +12 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +11 -0
- package/src/cli/index.ts +13 -3
- package/src/cli/init.ts +129 -102
- package/src/cli/opencode.ts +36 -151
- package/src/cli/star-prompt.ts +13 -4
- package/src/cli/status-oauth.ts +12 -2
- package/src/clients/config-export.ts +377 -0
- package/src/codex/account-runtime-state.ts +19 -1
- package/src/codex/account-store.ts +162 -82
- package/src/codex/auth-api.ts +467 -159
- package/src/codex/auth-context.ts +15 -2
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/effort.ts +16 -6
- package/src/codex/catalog/metadata.ts +6 -0
- package/src/codex/catalog/parsing.ts +3 -1
- package/src/codex/catalog/provider-fetch.ts +29 -0
- package/src/codex/catalog/sync.ts +64 -7
- package/src/codex/catalog.ts +2 -2
- package/src/codex/inject.ts +5 -5
- package/src/codex/main-account-cache.ts +8 -1
- package/src/codex/model-cache.ts +81 -2
- package/src/codex/pool-rotation.ts +39 -0
- package/src/codex/project-config-warnings.ts +12 -1
- package/src/codex/quota.ts +35 -3
- package/src/codex/routing.ts +46 -1
- package/src/codex/shim.ts +10 -4
- package/src/codex/subagent-model-fallback.ts +12 -0
- package/src/codex/websocket-registry.ts +27 -0
- package/src/combos/failover.ts +31 -1
- package/src/combos/request.ts +9 -0
- package/src/combos/resolve.ts +60 -4
- package/src/combos/types.ts +12 -0
- package/src/config.ts +510 -55
- package/src/github/star-state.ts +13 -1
- package/src/images/fulfill.ts +39 -1
- package/src/images/loop.ts +52 -12
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bun-stream-caps.ts +31 -7
- package/src/lib/config-ownership.ts +33 -0
- package/src/lib/crash-guard.ts +65 -5
- package/src/lib/debug-log-buffer.ts +47 -6
- package/src/lib/destination-policy.ts +12 -1
- package/src/lib/errors.ts +3 -0
- package/src/lib/gcp-adc.ts +40 -2
- package/src/lib/injection-debug-log.ts +26 -2
- package/src/lib/provider-outbound.ts +3 -0
- package/src/lib/sidecar-tracker.ts +5 -2
- package/src/lib/sse-decoder.ts +257 -37
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/windows-secret-acl.ts +33 -12
- package/src/lib/winsw.ts +14 -1
- package/src/oauth/anthropic-routing.ts +31 -7
- package/src/oauth/google-antigravity.ts +2 -1
- package/src/oauth/health.ts +30 -12
- package/src/oauth/index.ts +127 -23
- package/src/oauth/kiro-credentials.ts +72 -1
- package/src/oauth/kiro.ts +23 -4
- package/src/oauth/store.ts +165 -18
- package/src/oauth/token-guardian.ts +43 -4
- package/src/oauth/types.ts +2 -1
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/derive.ts +12 -0
- package/src/providers/free-directory.ts +4 -1
- package/src/providers/key-failover.ts +12 -0
- package/src/providers/openai-sidecar.ts +4 -1
- package/src/providers/quota.ts +68 -7
- package/src/providers/registry.ts +279 -3
- package/src/responses/parser.ts +5 -1
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +520 -102
- package/src/router.ts +18 -1
- package/src/server/adapter-resolve.ts +20 -3
- package/src/server/auth-cors.ts +121 -28
- package/src/server/chat-completions.ts +57 -12
- package/src/server/claude-messages.ts +85 -13
- package/src/server/index.ts +242 -100
- package/src/server/lifecycle.ts +155 -25
- package/src/server/management/agent-settings-routes.ts +79 -36
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +5 -1
- package/src/server/management/config-routes.ts +42 -12
- package/src/server/management/logs-usage-routes.ts +41 -21
- package/src/server/management/model-routes.ts +188 -54
- package/src/server/management/oauth-account-routes.ts +115 -26
- package/src/server/management/provider-routes.ts +56 -6
- package/src/server/management/shared.ts +16 -3
- package/src/server/management/sidebar-routes.ts +50 -1
- package/src/server/management/system-restart.ts +13 -6
- package/src/server/management/system-routes.ts +15 -3
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +39 -5
- package/src/server/management-auth.ts +65 -14
- package/src/server/port-reclaim.ts +58 -12
- package/src/server/ports.ts +2 -0
- package/src/server/proxy-liveness.ts +60 -14
- package/src/server/relay-eager.ts +20 -4
- package/src/server/relay.ts +548 -154
- package/src/server/request-decompress.ts +51 -4
- package/src/server/request-log.ts +134 -15
- package/src/server/responses/collaboration.ts +15 -4
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +241 -66
- package/src/server/responses-image-gen-repair.ts +19 -5
- package/src/server/responses-item-id-repair.ts +23 -5
- package/src/server/sse-payload-rewrite.ts +71 -12
- package/src/server/startup-health-cache.ts +14 -1
- package/src/server/system-env.ts +8 -1
- package/src/server/windows-tcp-drop.ts +15 -5
- package/src/server/ws-bridge.ts +25 -0
- package/src/service.ts +179 -13
- package/src/storage/policy-job.ts +93 -23
- package/src/storage/policy-worker.ts +6 -0
- package/src/storage/restore-job.ts +62 -16
- package/src/storage/restore-worker.ts +6 -0
- package/src/storage/storage-mutation-coordinator.ts +36 -6
- package/src/storage/worker-lifecycle.ts +181 -47
- package/src/tray/windows.ts +97 -25
- package/src/types.ts +39 -6
- package/src/update/index.ts +24 -5
- package/src/update/job.ts +598 -73
- package/src/usage/log.ts +115 -17
- package/src/usage/summary.ts +67 -2
- package/src/vision/index.ts +112 -22
- package/src/web-search/loop.ts +38 -6
- package/src/web-search/progress-stream.ts +14 -3
- package/gui/dist/assets/index-BDjpkcRN.js +0 -67
- package/gui/dist/assets/index-BHsKRFh9.css +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { gunzipSync, inflateRawSync, inflateSync, zstdDecompressSync } from "node:zlib";
|
|
2
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Request-body decompression for the /v1/responses data plane.
|
|
@@ -37,6 +38,13 @@ function assertBodySizeWithinLimit(body: Uint8Array, maxBytes: number): Uint8Arr
|
|
|
37
38
|
return body;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
function declaredBodyLength(req: Request): number | null {
|
|
42
|
+
const raw = req.headers.get("content-length");
|
|
43
|
+
if (raw === null || raw.trim() === "") return null;
|
|
44
|
+
const length = Number(raw);
|
|
45
|
+
return Number.isFinite(length) && length >= 0 ? length : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
function inflateDeflateBody(compressed: Uint8Array<ArrayBuffer>, opts: { maxOutputLength: number }): Uint8Array {
|
|
41
49
|
// HTTP "deflate" appears both zlib-wrapped and raw in the wild (Bun.deflateSync emits raw,
|
|
42
50
|
// which the previous Bun.inflateSync accepted). Try zlib-wrapped first, fall back to raw —
|
|
@@ -77,9 +85,48 @@ export function decodeRequestBody(
|
|
|
77
85
|
return assertBodySizeWithinLimit(decoded, maxBytes);
|
|
78
86
|
}
|
|
79
87
|
|
|
80
|
-
/** Parse a JSON request body, transparently decoding compressed payloads. */
|
|
81
|
-
export async function
|
|
88
|
+
/** Parse a bounded JSON request body, transparently decoding compressed payloads. */
|
|
89
|
+
export async function readBoundedJsonRequestBody(
|
|
90
|
+
req: Request,
|
|
91
|
+
maxBytes: number,
|
|
92
|
+
budget?: TranslatorBudget,
|
|
93
|
+
): Promise<unknown> {
|
|
82
94
|
const encoding = req.headers.get("content-encoding");
|
|
83
|
-
const
|
|
84
|
-
|
|
95
|
+
const declaredLength = declaredBodyLength(req);
|
|
96
|
+
// Reject an honest oversized declaration before req.arrayBuffer() can allocate it.
|
|
97
|
+
// Missing, malformed, or dishonest declarations remain covered by decodeRequestBody's
|
|
98
|
+
// post-read cap below.
|
|
99
|
+
if (declaredLength !== null && declaredLength > maxBytes) {
|
|
100
|
+
throw new DecompressedBodyTooLargeError(declaredLength, maxBytes);
|
|
101
|
+
}
|
|
102
|
+
const releaseReservation = budget && declaredLength !== null && declaredLength > 0
|
|
103
|
+
? budget.observeAcceptedRequestCopy(declaredLength)
|
|
104
|
+
: undefined;
|
|
105
|
+
let raw: Uint8Array;
|
|
106
|
+
try {
|
|
107
|
+
raw = new Uint8Array(await req.arrayBuffer());
|
|
108
|
+
} finally {
|
|
109
|
+
releaseReservation?.();
|
|
110
|
+
}
|
|
111
|
+
const releaseRaw = budget?.observeAcceptedRequestCopy(raw.byteLength);
|
|
112
|
+
let releaseDecoded: (() => void) | undefined;
|
|
113
|
+
let releaseText: (() => void) | undefined;
|
|
114
|
+
try {
|
|
115
|
+
const decoded = decodeRequestBody(raw, encoding, maxBytes);
|
|
116
|
+
releaseDecoded = decoded === raw ? undefined : budget?.observeAcceptedRequestCopy(decoded.byteLength);
|
|
117
|
+
const text = new TextDecoder().decode(decoded);
|
|
118
|
+
releaseText = budget?.observeAcceptedRequestCopy(new TextEncoder().encode(text).byteLength);
|
|
119
|
+
const parsed = JSON.parse(text);
|
|
120
|
+
budget?.observeAcceptedRequestCopy(new TextEncoder().encode(JSON.stringify(parsed)).byteLength);
|
|
121
|
+
return parsed;
|
|
122
|
+
} finally {
|
|
123
|
+
releaseText?.();
|
|
124
|
+
releaseDecoded?.();
|
|
125
|
+
releaseRaw?.();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Parse a JSON data-plane body using the shared 256 MiB admission cap. */
|
|
130
|
+
export function readJsonRequestBody(req: Request, budget?: TranslatorBudget): Promise<unknown> {
|
|
131
|
+
return readBoundedJsonRequestBody(req, MAX_DECOMPRESSED_BODY_BYTES, budget);
|
|
85
132
|
}
|
|
@@ -12,6 +12,8 @@ import type { AdapterRequest } from "../adapters/base";
|
|
|
12
12
|
import { redactSecretString } from "../lib/redact";
|
|
13
13
|
import {
|
|
14
14
|
appendUsageEntry,
|
|
15
|
+
isKnownAdmissionKind,
|
|
16
|
+
isKnownInboundProtocol,
|
|
15
17
|
isKnownUsageSurface,
|
|
16
18
|
readRecentUsageEntries,
|
|
17
19
|
usageForFinalLog,
|
|
@@ -30,6 +32,7 @@ import {
|
|
|
30
32
|
type UsageDebugBodyKind,
|
|
31
33
|
} from "../usage/debug";
|
|
32
34
|
import { matchesLogConversationId } from "./request-log-conversation";
|
|
35
|
+
import { enforceAppOwnedMemoryBudget, type RetainedStoreSnapshot } from "../lib/app-owned-memory";
|
|
33
36
|
|
|
34
37
|
export interface RequestLogContext {
|
|
35
38
|
model: string;
|
|
@@ -39,6 +42,16 @@ export interface RequestLogContext {
|
|
|
39
42
|
/** Best-effort chat/session correlation for Logs grouping (#330). Opaque; omit when unknown. */
|
|
40
43
|
conversationId?: string;
|
|
41
44
|
surface?: "claude" | "claude-desktop" | "grok";
|
|
45
|
+
/** The matched configured key's id. Set ONLY for admissionKind "configured" —
|
|
46
|
+
* never a sentinel, so a hand-edited entry whose id happens to be "loopback"
|
|
47
|
+
* cannot absorb unrelated traffic. */
|
|
48
|
+
apiKeyId?: string;
|
|
49
|
+
/** Which kind of admission opened this request. Carries no secret. */
|
|
50
|
+
admissionKind?: "configured" | "environment" | "loopback";
|
|
51
|
+
/** Which inbound wire was used. Orthogonal to `surface`, which names the client
|
|
52
|
+
* product: widening that enum would merge Responses and Chat Completions,
|
|
53
|
+
* since both leave it undefined. */
|
|
54
|
+
inboundProtocol?: "responses" | "chat" | "messages";
|
|
42
55
|
requestedModel?: string;
|
|
43
56
|
/** Internal structural combo identity; omitted from RequestLogEntry/JSONL. */
|
|
44
57
|
comboId?: string;
|
|
@@ -92,6 +105,16 @@ export interface RequestLogEntry {
|
|
|
92
105
|
/** TTFT: ms from request start to the first non-empty model output delta; unset for non-streaming/tool-only. */
|
|
93
106
|
firstOutputMs?: number;
|
|
94
107
|
surface?: "claude" | "claude-desktop" | "grok";
|
|
108
|
+
/** The matched configured key's id. Set ONLY for admissionKind "configured" —
|
|
109
|
+
* never a sentinel, so a hand-edited entry whose id happens to be "loopback"
|
|
110
|
+
* cannot absorb unrelated traffic. */
|
|
111
|
+
apiKeyId?: string;
|
|
112
|
+
/** Which kind of admission opened this request. Carries no secret. */
|
|
113
|
+
admissionKind?: "configured" | "environment" | "loopback";
|
|
114
|
+
/** Which inbound wire was used. Orthogonal to `surface`, which names the client
|
|
115
|
+
* product: widening that enum would merge Responses and Chat Completions,
|
|
116
|
+
* since both leave it undefined. */
|
|
117
|
+
inboundProtocol?: "responses" | "chat" | "messages";
|
|
95
118
|
/** Best-effort chat/session correlation for Logs grouping (#330). */
|
|
96
119
|
conversationId?: string;
|
|
97
120
|
requestedModel?: string;
|
|
@@ -126,11 +149,49 @@ export interface RequestLogEntry {
|
|
|
126
149
|
}
|
|
127
150
|
|
|
128
151
|
const requestLog: RequestLogEntry[] = [];
|
|
129
|
-
const MAX_LOG_SIZE =
|
|
152
|
+
const MAX_LOG_SIZE = 2000;
|
|
153
|
+
const requestLogEntryBytes = new WeakMap<RequestLogEntry, number>();
|
|
154
|
+
let requestLogBytes = 0;
|
|
130
155
|
let requestLogSeq = 0;
|
|
131
156
|
/** True after hydrateRequestLogsFromDisk ran once in this process. */
|
|
132
157
|
let requestLogsHydratedFromDisk = false;
|
|
133
158
|
|
|
159
|
+
function retainedRequestLogBytes(entry: RequestLogEntry): number {
|
|
160
|
+
return Buffer.byteLength(JSON.stringify(entry), "utf8");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function removeOldestRequestLogEntry(): number {
|
|
164
|
+
const entry = requestLog.shift();
|
|
165
|
+
if (!entry) return 0;
|
|
166
|
+
const bytes = requestLogEntryBytes.get(entry) ?? retainedRequestLogBytes(entry);
|
|
167
|
+
requestLogEntryBytes.delete(entry);
|
|
168
|
+
requestLogBytes = Math.max(0, requestLogBytes - bytes);
|
|
169
|
+
return bytes;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function retainRequestLogEntry(entry: RequestLogEntry): void {
|
|
173
|
+
const bytes = retainedRequestLogBytes(entry);
|
|
174
|
+
requestLog.push(entry);
|
|
175
|
+
requestLogEntryBytes.set(entry, bytes);
|
|
176
|
+
requestLogBytes += bytes;
|
|
177
|
+
while (requestLog.length > MAX_LOG_SIZE) removeOldestRequestLogEntry();
|
|
178
|
+
enforceAppOwnedMemoryBudget();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function requestLogRetainedStoreSnapshot(): RetainedStoreSnapshot {
|
|
182
|
+
return {
|
|
183
|
+
count: requestLog.length,
|
|
184
|
+
bytes: requestLogBytes,
|
|
185
|
+
evictableBytes: requestLogBytes,
|
|
186
|
+
pinnedBytes: 0,
|
|
187
|
+
oldestAt: requestLog[0]?.timestamp ?? null,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function evictOldestRequestLogForBudget(): number {
|
|
192
|
+
return removeOldestRequestLogEntry();
|
|
193
|
+
}
|
|
194
|
+
|
|
134
195
|
function asTerminalStatus(value: string | undefined): ResponsesTerminalStatus | undefined {
|
|
135
196
|
if (value === "completed" || value === "failed" || value === "incomplete") return value;
|
|
136
197
|
return undefined;
|
|
@@ -208,7 +269,7 @@ export function hydrateRequestLogsFromDisk(
|
|
|
208
269
|
const slice = persisted.length > MAX_LOG_SIZE
|
|
209
270
|
? persisted.slice(persisted.length - MAX_LOG_SIZE)
|
|
210
271
|
: persisted;
|
|
211
|
-
for (const entry of slice)
|
|
272
|
+
for (const entry of slice) retainRequestLogEntry(requestLogEntryFromPersistedUsage(entry));
|
|
212
273
|
return slice.length;
|
|
213
274
|
} catch (err) {
|
|
214
275
|
requestLogsHydratedFromDisk = true;
|
|
@@ -220,8 +281,7 @@ export function hydrateRequestLogsFromDisk(
|
|
|
220
281
|
}
|
|
221
282
|
|
|
222
283
|
export function addRequestLog(entry: RequestLogEntry) {
|
|
223
|
-
|
|
224
|
-
if (requestLog.length > MAX_LOG_SIZE) requestLog.shift();
|
|
284
|
+
retainRequestLogEntry(entry);
|
|
225
285
|
try {
|
|
226
286
|
// Failure diagnostics survive the 200-entry ring buffer by riding the persisted
|
|
227
287
|
// usage entry (devlog/_plan/260716_claudecode_hardening/030). Success rows stay
|
|
@@ -240,6 +300,12 @@ export function addRequestLog(entry: RequestLogEntry) {
|
|
|
240
300
|
provider: entry.provider,
|
|
241
301
|
model: entry.model,
|
|
242
302
|
...(isKnownUsageSurface(entry.surface) ? { surface: entry.surface } : {}),
|
|
303
|
+
// This function REBUILDS the persisted row field by field rather than
|
|
304
|
+
// spreading it, so a field missing here reaches /api/logs and never
|
|
305
|
+
// reaches usage.jsonl — which is where the per-key rollup reads from.
|
|
306
|
+
...(entry.apiKeyId ? { apiKeyId: entry.apiKeyId } : {}),
|
|
307
|
+
...(isKnownAdmissionKind(entry.admissionKind) ? { admissionKind: entry.admissionKind } : {}),
|
|
308
|
+
...(isKnownInboundProtocol(entry.inboundProtocol) ? { inboundProtocol: entry.inboundProtocol } : {}),
|
|
243
309
|
...(entry.conversationId ? { conversationId: entry.conversationId } : {}),
|
|
244
310
|
...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}),
|
|
245
311
|
...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
|
|
@@ -506,14 +572,26 @@ export function inspectResponseLogJson(logCtx: RequestLogContext, text: string):
|
|
|
506
572
|
|
|
507
573
|
export function inspectResponseLogSsePayload(logCtx: RequestLogContext, payload: string | null): void {
|
|
508
574
|
if (!payload || payload.trim() === "[DONE]") return;
|
|
509
|
-
|
|
510
|
-
const sseAlreadyMarked = logCtx.usageDebugBodyKind === "sse";
|
|
575
|
+
let parsed: unknown | undefined;
|
|
511
576
|
try {
|
|
512
|
-
|
|
577
|
+
parsed = JSON.parse(payload);
|
|
513
578
|
} catch {
|
|
514
579
|
/* SSE block payload may not be JSON; metadata inspection is best-effort */
|
|
515
580
|
}
|
|
516
|
-
|
|
581
|
+
inspectResponseLogSsePayloadParsed(logCtx, payload, parsed);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Inspect an SSE payload using the caller's single best-effort JSON parse. */
|
|
585
|
+
export function inspectResponseLogSsePayloadParsed(
|
|
586
|
+
logCtx: RequestLogContext,
|
|
587
|
+
payload: string | null,
|
|
588
|
+
parsed: unknown | undefined,
|
|
589
|
+
): void {
|
|
590
|
+
if (!payload || payload.trim() === "[DONE]") return;
|
|
591
|
+
const debugEnabled = isUsageDebugEnabled();
|
|
592
|
+
const sseAlreadyMarked = logCtx.usageDebugBodyKind === "sse";
|
|
593
|
+
if (parsed !== undefined) applyResponseLogMetadata(logCtx, parsed);
|
|
594
|
+
captureUpstreamErrorParsed(logCtx, payload, parsed);
|
|
517
595
|
if (debugEnabled) {
|
|
518
596
|
if (!sseAlreadyMarked) {
|
|
519
597
|
logCtx.usageDebugBodyKind = "sse";
|
|
@@ -535,8 +613,22 @@ export function inspectResponseLogSsePayload(logCtx: RequestLogContext, payload:
|
|
|
535
613
|
*/
|
|
536
614
|
function captureUpstreamError(logCtx: RequestLogContext, text: string | null): void {
|
|
537
615
|
if (!text) return;
|
|
616
|
+
let parsed: unknown | undefined;
|
|
538
617
|
try {
|
|
539
|
-
|
|
618
|
+
parsed = JSON.parse(text);
|
|
619
|
+
} catch {
|
|
620
|
+
/* retain the raw malformed payload for the bounded fallback below */
|
|
621
|
+
}
|
|
622
|
+
captureUpstreamErrorParsed(logCtx, text, parsed);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function captureUpstreamErrorParsed(
|
|
626
|
+
logCtx: RequestLogContext,
|
|
627
|
+
text: string,
|
|
628
|
+
parsed: unknown | undefined,
|
|
629
|
+
): void {
|
|
630
|
+
if (parsed !== undefined && parsed !== null) {
|
|
631
|
+
const json = parsed as {
|
|
540
632
|
type?: unknown;
|
|
541
633
|
error?: { message?: unknown };
|
|
542
634
|
last_error?: { message?: unknown };
|
|
@@ -568,12 +660,12 @@ function captureUpstreamError(logCtx: RequestLogContext, text: string | null): v
|
|
|
568
660
|
if (typeof reason === "string" && reason.trim()) {
|
|
569
661
|
logCtx.upstreamError = redactSecretString(incompleteReasonLabel(reason.trim())).slice(0, 500);
|
|
570
662
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (logCtx.upstreamError) return;
|
|
666
|
+
const trimmed = text.trim();
|
|
667
|
+
if (trimmed) {
|
|
668
|
+
logCtx.upstreamError = redactSecretString(trimmed).slice(0, 500);
|
|
577
669
|
}
|
|
578
670
|
}
|
|
579
671
|
|
|
@@ -695,6 +787,9 @@ export function addFinalRequestLog(
|
|
|
695
787
|
model: isCombo ? logCtx.requestedModel! : logCtx.model,
|
|
696
788
|
provider: isCombo ? "combo" : logCtx.provider,
|
|
697
789
|
...(logCtx.surface ? { surface: logCtx.surface } : {}),
|
|
790
|
+
...(logCtx.apiKeyId ? { apiKeyId: logCtx.apiKeyId } : {}),
|
|
791
|
+
...(logCtx.admissionKind ? { admissionKind: logCtx.admissionKind } : {}),
|
|
792
|
+
...(logCtx.inboundProtocol ? { inboundProtocol: logCtx.inboundProtocol } : {}),
|
|
698
793
|
...(logCtx.conversationId ? { conversationId: logCtx.conversationId } : {}),
|
|
699
794
|
...(logCtx.requestedModel ? { requestedModel: logCtx.requestedModel } : {}),
|
|
700
795
|
...(logCtx.requestedEffort ? { requestedEffort: logCtx.requestedEffort } : {}),
|
|
@@ -760,9 +855,32 @@ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchPara
|
|
|
760
855
|
const tail = Number.parseInt(tailRaw, 10);
|
|
761
856
|
if (Number.isFinite(tail) && tail > 0) filtered = filtered.slice(-Math.min(tail, MAX_LOG_SIZE));
|
|
762
857
|
}
|
|
858
|
+
const offsetRaw = params.get("offset")?.trim();
|
|
859
|
+
const limitRaw = params.get("limit")?.trim();
|
|
860
|
+
if (limitRaw) {
|
|
861
|
+
const limit = Number.parseInt(limitRaw, 10);
|
|
862
|
+
const offset = offsetRaw ? Number.parseInt(offsetRaw, 10) : 0;
|
|
863
|
+
if (Number.isFinite(limit) && limit > 0) {
|
|
864
|
+
const capped = Math.min(limit, MAX_LOG_SIZE);
|
|
865
|
+
const startOffset = Number.isFinite(offset) && offset > 0 ? offset : 0;
|
|
866
|
+
const end = filtered.length - startOffset;
|
|
867
|
+
if (end <= 0) filtered = [];
|
|
868
|
+
else {
|
|
869
|
+
const begin = Math.max(0, end - capped);
|
|
870
|
+
filtered = filtered.slice(begin, end);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
763
874
|
return filtered;
|
|
764
875
|
}
|
|
765
876
|
|
|
877
|
+
export function filteredRequestLogCount(logs: RequestLogEntry[], params: URLSearchParams): number {
|
|
878
|
+
const withoutPagination = new URLSearchParams(params);
|
|
879
|
+
withoutPagination.delete("limit");
|
|
880
|
+
withoutPagination.delete("offset");
|
|
881
|
+
return filterRequestLogs(logs, withoutPagination).length;
|
|
882
|
+
}
|
|
883
|
+
|
|
766
884
|
interface FinalizedUsageResult {
|
|
767
885
|
usage?: OcxUsage;
|
|
768
886
|
status: UsageStatus;
|
|
@@ -922,6 +1040,7 @@ export function getRequestLogEntries(): RequestLogEntry[] { return requestLog; }
|
|
|
922
1040
|
/** Test-only process-state reset for isolated integration harnesses. */
|
|
923
1041
|
export function clearRequestLogsForTests(): void {
|
|
924
1042
|
requestLog.length = 0;
|
|
1043
|
+
requestLogBytes = 0;
|
|
925
1044
|
requestLogSeq = 0;
|
|
926
1045
|
requestLogsHydratedFromDisk = false;
|
|
927
1046
|
}
|
|
@@ -97,9 +97,10 @@ import {
|
|
|
97
97
|
} from "../relay";
|
|
98
98
|
import { hasResponsesItemIdRepair, relaySseWithResponsesItemIdRepair } from "../responses-item-id-repair";
|
|
99
99
|
import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/catalog";
|
|
100
|
+
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
100
101
|
|
|
101
102
|
|
|
102
|
-
export function buildToolBridgeMaps(parsed: OcxParsedRequest): {
|
|
103
|
+
export function buildToolBridgeMaps(parsed: OcxParsedRequest, budget?: TranslatorBudget): {
|
|
103
104
|
toolNsMap: Map<string, { namespace: string; name: string }>;
|
|
104
105
|
freeformToolNames: Set<string>;
|
|
105
106
|
toolSearchToolNames: Set<string>;
|
|
@@ -108,9 +109,19 @@ export function buildToolBridgeMaps(parsed: OcxParsedRequest): {
|
|
|
108
109
|
const freeformToolNames = new Set<string>();
|
|
109
110
|
const toolSearchToolNames = new Set<string>();
|
|
110
111
|
for (const t of parsed.context.tools ?? []) {
|
|
111
|
-
if (t.namespace)
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
if (t.namespace) {
|
|
113
|
+
const wireName = namespacedToolName(t.namespace, t.name);
|
|
114
|
+
budget?.chargeRetained(new TextEncoder().encode(JSON.stringify([wireName, t.namespace, t.name])).byteLength, { kind: "retained_collectors" });
|
|
115
|
+
toolNsMap.set(wireName, { namespace: t.namespace, name: t.name });
|
|
116
|
+
}
|
|
117
|
+
if (t.freeform) {
|
|
118
|
+
budget?.chargeRetained(new TextEncoder().encode(t.name).byteLength, { kind: "retained_collectors" });
|
|
119
|
+
freeformToolNames.add(t.name);
|
|
120
|
+
}
|
|
121
|
+
if (t.toolSearch) {
|
|
122
|
+
budget?.chargeRetained(new TextEncoder().encode(t.name).byteLength, { kind: "retained_collectors" });
|
|
123
|
+
toolSearchToolNames.add(t.name);
|
|
124
|
+
}
|
|
114
125
|
}
|
|
115
126
|
return { toolNsMap, freeformToolNames, toolSearchToolNames };
|
|
116
127
|
}
|
|
@@ -267,6 +267,9 @@ export async function handleResponsesCompact(
|
|
|
267
267
|
modelId: selectedModelId,
|
|
268
268
|
probeLeaseId: codexProbeLeaseId(authCtx),
|
|
269
269
|
probeQuotaScope: codexProbeQuotaScope(authCtx),
|
|
270
|
+
writerGeneration: authCtx.kind === "pool" || authCtx.kind === "main-pool"
|
|
271
|
+
? authCtx.writerGeneration
|
|
272
|
+
: undefined,
|
|
270
273
|
});
|
|
271
274
|
};
|
|
272
275
|
let upstream: Response;
|