@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
|
@@ -6,16 +6,31 @@
|
|
|
6
6
|
* `isInjectionDebugEnabled()` guard; this module only stores what it is given. */
|
|
7
7
|
|
|
8
8
|
import type { DebugLogEntry } from "./debug-log-buffer";
|
|
9
|
+
import { retainedUtf8Bytes, truncateRetainedUtf8 } from "./admission";
|
|
10
|
+
import { enforceAppOwnedMemoryBudget } from "./app-owned-memory";
|
|
9
11
|
|
|
10
12
|
const MAX_LINES = 2_000;
|
|
13
|
+
const MAX_DEBUG_LINE_BYTES = 16 * 1024;
|
|
11
14
|
const buffer: DebugLogEntry[] = [];
|
|
12
15
|
let nextSeq = 1;
|
|
16
|
+
let bufferBytes = 0;
|
|
17
|
+
|
|
18
|
+
function removeOldestEntry(): number {
|
|
19
|
+
const entry = buffer.shift();
|
|
20
|
+
if (!entry) return 0;
|
|
21
|
+
const bytes = retainedUtf8Bytes(entry.line);
|
|
22
|
+
bufferBytes -= bytes;
|
|
23
|
+
return bytes;
|
|
24
|
+
}
|
|
13
25
|
|
|
14
26
|
/** Append a line to the injection buffer and echo it to the server console. */
|
|
15
27
|
export function injectionDebugLog(line: string): void {
|
|
16
|
-
const
|
|
28
|
+
const retainedLine = truncateRetainedUtf8(line, MAX_DEBUG_LINE_BYTES);
|
|
29
|
+
const entry: DebugLogEntry = { seq: nextSeq++, at: Date.now(), line: retainedLine };
|
|
17
30
|
buffer.push(entry);
|
|
18
|
-
|
|
31
|
+
bufferBytes += retainedUtf8Bytes(retainedLine);
|
|
32
|
+
while (buffer.length > MAX_LINES) removeOldestEntry();
|
|
33
|
+
enforceAppOwnedMemoryBudget();
|
|
19
34
|
console.log(line);
|
|
20
35
|
}
|
|
21
36
|
|
|
@@ -27,8 +42,17 @@ export function getInjectionDebugLogEntries(options?: { after?: number; limit?:
|
|
|
27
42
|
return filtered.slice(-limit);
|
|
28
43
|
}
|
|
29
44
|
|
|
45
|
+
export function injectionBufferMetrics(): { entries: number; bytes: number; oldestAt: number | null } {
|
|
46
|
+
return { entries: buffer.length, bytes: bufferBytes, oldestAt: buffer[0]?.at ?? null };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function evictOldestInjectionEntryForBudget(): number {
|
|
50
|
+
return removeOldestEntry();
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
/** Test isolation. */
|
|
31
54
|
export function resetInjectionDebugLogBufferForTests(): void {
|
|
32
55
|
buffer.length = 0;
|
|
56
|
+
bufferBytes = 0;
|
|
33
57
|
nextSeq = 1;
|
|
34
58
|
}
|
|
@@ -117,6 +117,9 @@ export async function providerOutboundGet(
|
|
|
117
117
|
if (assessment?.kind === "metadata" || assessment?.kind === "link-local" || assessment?.kind === "unspecified") {
|
|
118
118
|
throw new ProviderOutboundPolicyError(`provider URL targets ${assessment.detail}`);
|
|
119
119
|
}
|
|
120
|
+
// Registry defaults count here too, not just the operator flag: a stock Ollama entry is
|
|
121
|
+
// local by definition and previously passed config validation only to be refused at the
|
|
122
|
+
// fetch (#758). Metadata/link-local/unspecified were already rejected above.
|
|
120
123
|
const allowPrivate = providerAllowsPrivateNetwork(name, provider);
|
|
121
124
|
if (!allowPrivate) {
|
|
122
125
|
const destinationError = providerDestinationConfigError(name, {
|
|
@@ -13,7 +13,7 @@ let lastEnterAt = 0;
|
|
|
13
13
|
|
|
14
14
|
export function sidecarEnter(label: string): () => void {
|
|
15
15
|
inFlight++;
|
|
16
|
-
lastLabel = label;
|
|
16
|
+
lastLabel = truncateRetainedUtf8(label, MAX_DIAGNOSTIC_VALUE_BYTES);
|
|
17
17
|
lastEnterAt = Date.now();
|
|
18
18
|
let exited = false;
|
|
19
19
|
return () => {
|
|
@@ -40,10 +40,13 @@ let lastActivity = "";
|
|
|
40
40
|
let lastActivityAt = 0;
|
|
41
41
|
|
|
42
42
|
export function markActivity(note: string): void {
|
|
43
|
-
lastActivity = note;
|
|
43
|
+
lastActivity = truncateRetainedUtf8(note, MAX_DIAGNOSTIC_VALUE_BYTES);
|
|
44
44
|
lastActivityAt = Date.now();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function activityBreadcrumb(): { note: string; sinceMs: number } {
|
|
48
48
|
return { note: lastActivity, sinceMs: lastActivityAt ? Date.now() - lastActivityAt : 0 };
|
|
49
49
|
}
|
|
50
|
+
import { truncateRetainedUtf8 } from "./admission";
|
|
51
|
+
|
|
52
|
+
const MAX_DIAGNOSTIC_VALUE_BYTES = 8 * 1024;
|
package/src/lib/sse-decoder.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TRANSLATOR_MAX_SSE_EVENT_BYTES,
|
|
3
|
+
TranslatorBudgetExceededError,
|
|
4
|
+
type TranslatorBudget,
|
|
5
|
+
} from "./translator-budget";
|
|
6
|
+
|
|
1
7
|
export interface ServerSentEvent {
|
|
2
8
|
event?: string;
|
|
3
9
|
data: string;
|
|
@@ -16,21 +22,78 @@ export type SseRecord =
|
|
|
16
22
|
*/
|
|
17
23
|
export function decodeServerSentEvents(
|
|
18
24
|
source: ReadableStream<Uint8Array>,
|
|
19
|
-
options: { includeComments: true; signal?: AbortSignal },
|
|
25
|
+
options: { includeComments: true; signal?: AbortSignal; translatorBudget: TranslatorBudget },
|
|
20
26
|
): AsyncGenerator<SseRecord>;
|
|
21
27
|
export function decodeServerSentEvents(
|
|
22
28
|
source: ReadableStream<Uint8Array>,
|
|
23
|
-
options
|
|
29
|
+
options: { includeComments?: false; signal?: AbortSignal; translatorBudget: TranslatorBudget },
|
|
24
30
|
): AsyncGenerator<ServerSentEvent>;
|
|
25
31
|
export async function* decodeServerSentEvents(
|
|
26
32
|
source: ReadableStream<Uint8Array>,
|
|
27
|
-
options
|
|
33
|
+
options: { includeComments?: boolean; signal?: AbortSignal; translatorBudget: TranslatorBudget },
|
|
28
34
|
): AsyncGenerator<ServerSentEvent | SseRecord> {
|
|
35
|
+
const translatorBudget = options.translatorBudget;
|
|
29
36
|
const reader = source.getReader();
|
|
30
37
|
const decoder = new TextDecoder();
|
|
31
|
-
let
|
|
38
|
+
let lineBuffer = "";
|
|
39
|
+
let lineRawBytes = 0;
|
|
40
|
+
let lineRetainedBytes = 0;
|
|
32
41
|
let event: string | undefined;
|
|
42
|
+
let eventBytes = 0;
|
|
33
43
|
let dataLines: string[] = [];
|
|
44
|
+
let dataLinesBytes = 0;
|
|
45
|
+
const scope = { kind: "live_transient" as const };
|
|
46
|
+
|
|
47
|
+
const utf8SliceBytes = (value: string, start: number, end: number): number => {
|
|
48
|
+
let bytes = 0;
|
|
49
|
+
for (let index = start; index < end; index++) {
|
|
50
|
+
const codePoint = value.codePointAt(index)!;
|
|
51
|
+
if (codePoint <= 0x7f) bytes += 1;
|
|
52
|
+
else if (codePoint <= 0x7ff) bytes += 2;
|
|
53
|
+
else if (codePoint <= 0xffff) bytes += 3;
|
|
54
|
+
else {
|
|
55
|
+
bytes += 4;
|
|
56
|
+
index += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return bytes;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const linePrefixAfter = (sourceValue: string, start: number, end: number): string => {
|
|
63
|
+
if (lineBuffer.length >= 6) return lineBuffer.slice(0, 6);
|
|
64
|
+
return (lineBuffer + sourceValue.slice(start, Math.min(end, start + 6 - lineBuffer.length))).slice(0, 6);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const accountedLineBytes = (rawBytes: number, prefix: string): number => {
|
|
68
|
+
if (prefix.length < 5 && "data:".startsWith(prefix)) return 0;
|
|
69
|
+
if (!prefix.startsWith("data:")) return rawBytes;
|
|
70
|
+
return Math.max(0, rawBytes - (prefix[5] === " " ? 6 : 5));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const appendLine = (sourceValue: string, start: number, end: number): void => {
|
|
74
|
+
if (start >= end) return;
|
|
75
|
+
const nextRawBytes = lineRawBytes + utf8SliceBytes(sourceValue, start, end);
|
|
76
|
+
const prefix = linePrefixAfter(sourceValue, start, end);
|
|
77
|
+
const nextRetainedBytes = accountedLineBytes(nextRawBytes, prefix);
|
|
78
|
+
if (prefix.startsWith("data:")) {
|
|
79
|
+
const logicalEventBytes = dataLinesBytes + dataLines.length + nextRetainedBytes;
|
|
80
|
+
if (logicalEventBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
|
|
81
|
+
throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const reservation = translatorBudget.reserveTransient(nextRawBytes, scope);
|
|
85
|
+
try {
|
|
86
|
+
const fragment = sourceValue.slice(start, end);
|
|
87
|
+
lineBuffer += fragment;
|
|
88
|
+
reservation.commitRetained();
|
|
89
|
+
translatorBudget.releaseRetained(lineRetainedBytes, scope);
|
|
90
|
+
lineRawBytes = nextRawBytes;
|
|
91
|
+
lineRetainedBytes = nextRawBytes;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
reservation.release();
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
34
97
|
// Prompt cancellation channel: an abort cancels the underlying reader directly, which
|
|
35
98
|
// settles any in-flight read() so a consumer's iterator.return() cannot hang behind an
|
|
36
99
|
// idle upstream (a plain generator return waits for the pending await first).
|
|
@@ -41,61 +104,218 @@ export async function* decodeServerSentEvents(
|
|
|
41
104
|
|
|
42
105
|
const includeComments = options?.includeComments === true;
|
|
43
106
|
|
|
44
|
-
const dispatch = (): ServerSentEvent | SseRecord | undefined => {
|
|
107
|
+
const dispatch = (): { record: ServerSentEvent | SseRecord; bytes: number } | undefined => {
|
|
45
108
|
if (dataLines.length === 0) {
|
|
109
|
+
translatorBudget.releaseRetained(eventBytes, scope);
|
|
46
110
|
event = undefined;
|
|
111
|
+
eventBytes = 0;
|
|
47
112
|
return undefined;
|
|
48
113
|
}
|
|
49
|
-
const
|
|
114
|
+
const payloadBytes = dataLinesBytes + Math.max(0, dataLines.length - 1);
|
|
115
|
+
if (payloadBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
|
|
116
|
+
throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
|
|
117
|
+
}
|
|
118
|
+
let data: string;
|
|
119
|
+
if (dataLines.length === 1) {
|
|
120
|
+
data = dataLines[0]!;
|
|
121
|
+
} else {
|
|
122
|
+
const reservation = translatorBudget.reserveTransient(payloadBytes, scope);
|
|
123
|
+
try {
|
|
124
|
+
data = dataLines.join("\n");
|
|
125
|
+
reservation.commitRetained();
|
|
126
|
+
translatorBudget.releaseRetained(dataLinesBytes, scope);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
reservation.release();
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const record = { ...(event ? { event } : {}), data };
|
|
133
|
+
const retainedRecordBytes = payloadBytes + eventBytes;
|
|
50
134
|
event = undefined;
|
|
135
|
+
eventBytes = 0;
|
|
51
136
|
dataLines = [];
|
|
52
|
-
|
|
137
|
+
dataLinesBytes = 0;
|
|
138
|
+
return {
|
|
139
|
+
record: includeComments ? { kind: "event", ...record } : record,
|
|
140
|
+
bytes: retainedRecordBytes,
|
|
141
|
+
};
|
|
53
142
|
};
|
|
54
143
|
|
|
55
|
-
const acceptLine = (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
144
|
+
const acceptLine = (): { record: ServerSentEvent | SseRecord; bytes: number } | undefined => {
|
|
145
|
+
let line = lineBuffer;
|
|
146
|
+
let retainedLineBytes = lineRetainedBytes;
|
|
147
|
+
let lineOwned = true;
|
|
148
|
+
const releaseLine = (): void => {
|
|
149
|
+
if (!lineOwned) return;
|
|
150
|
+
lineOwned = false;
|
|
151
|
+
translatorBudget.releaseRetained(retainedLineBytes, scope);
|
|
152
|
+
};
|
|
153
|
+
lineBuffer = "";
|
|
154
|
+
lineRawBytes = 0;
|
|
155
|
+
lineRetainedBytes = 0;
|
|
156
|
+
try {
|
|
157
|
+
if (line.endsWith("\r")) {
|
|
158
|
+
const nextLineBytes = retainedLineBytes - 1;
|
|
159
|
+
const reservation = translatorBudget.reserveTransient(nextLineBytes, scope);
|
|
160
|
+
try {
|
|
161
|
+
const nextLine = line.slice(0, -1);
|
|
162
|
+
reservation.commitRetained();
|
|
163
|
+
translatorBudget.releaseRetained(retainedLineBytes, scope);
|
|
164
|
+
line = nextLine;
|
|
165
|
+
retainedLineBytes = nextLineBytes;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
reservation.release();
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (line === "") {
|
|
172
|
+
releaseLine();
|
|
173
|
+
return dispatch();
|
|
174
|
+
}
|
|
175
|
+
if (line.startsWith(":")) {
|
|
176
|
+
if (!includeComments) {
|
|
177
|
+
releaseLine();
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const commentStart = line[1] === " " ? 2 : 1;
|
|
181
|
+
const commentBytes = utf8SliceBytes(line, commentStart, line.length);
|
|
182
|
+
translatorBudget.chargeRetained(commentBytes, scope);
|
|
183
|
+
try {
|
|
184
|
+
const comment = line.slice(commentStart);
|
|
185
|
+
releaseLine();
|
|
186
|
+
return { record: { kind: "comment", comment }, bytes: commentBytes };
|
|
187
|
+
} catch (error) {
|
|
188
|
+
translatorBudget.releaseRetained(commentBytes, scope);
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
64
192
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
193
|
+
const colon = line.indexOf(":");
|
|
194
|
+
const fieldEnd = colon < 0 ? line.length : colon;
|
|
195
|
+
const fieldBytes = utf8SliceBytes(line, 0, fieldEnd);
|
|
196
|
+
const fieldReservation = translatorBudget.reserveTransient(fieldBytes, scope);
|
|
197
|
+
let field = "";
|
|
198
|
+
try {
|
|
199
|
+
field = line.slice(0, fieldEnd);
|
|
200
|
+
fieldReservation.commitRetained();
|
|
201
|
+
} catch (error) {
|
|
202
|
+
fieldReservation.release();
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
let valueStart = colon < 0 ? line.length : colon + 1;
|
|
207
|
+
if (line[valueStart] === " ") valueStart += 1;
|
|
208
|
+
const valueBytes = utf8SliceBytes(line, valueStart, line.length);
|
|
209
|
+
|
|
210
|
+
if (field === "event") {
|
|
211
|
+
const reservation = translatorBudget.reserveTransient(valueBytes, scope);
|
|
212
|
+
try {
|
|
213
|
+
const value = line.slice(valueStart);
|
|
214
|
+
reservation.commitRetained();
|
|
215
|
+
translatorBudget.releaseRetained(eventBytes, scope);
|
|
216
|
+
event = value;
|
|
217
|
+
eventBytes = valueBytes;
|
|
218
|
+
releaseLine();
|
|
219
|
+
} catch (error) {
|
|
220
|
+
reservation.release();
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else if (field === "data") {
|
|
225
|
+
const nextEventBytes = dataLinesBytes + valueBytes + dataLines.length;
|
|
226
|
+
if (nextEventBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
|
|
227
|
+
throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
|
|
228
|
+
}
|
|
229
|
+
// The extracted value is a distinct retained allocation while the source line is still
|
|
230
|
+
// live. Admit that insertion before slicing/pushing, then release the source owner.
|
|
231
|
+
translatorBudget.chargeRetained(valueBytes, scope);
|
|
232
|
+
try {
|
|
233
|
+
const value = line.slice(valueStart);
|
|
234
|
+
dataLines.push(value);
|
|
235
|
+
dataLinesBytes += valueBytes;
|
|
236
|
+
releaseLine();
|
|
237
|
+
} catch (error) {
|
|
238
|
+
translatorBudget.releaseRetained(valueBytes, scope);
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
releaseLine();
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
translatorBudget.releaseRetained(fieldBytes, scope);
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
releaseLine();
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
69
253
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
254
|
+
const consumeDecoded = async function* (
|
|
255
|
+
decoded: string,
|
|
256
|
+
): AsyncGenerator<ServerSentEvent | SseRecord> {
|
|
257
|
+
let offset = 0;
|
|
258
|
+
while (offset < decoded.length) {
|
|
259
|
+
const newline = decoded.indexOf("\n", offset);
|
|
260
|
+
if (newline < 0) {
|
|
261
|
+
appendLine(decoded, offset, decoded.length);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
appendLine(decoded, offset, newline);
|
|
265
|
+
const accepted = acceptLine();
|
|
266
|
+
if (accepted) {
|
|
267
|
+
try {
|
|
268
|
+
yield accepted.record;
|
|
269
|
+
} finally {
|
|
270
|
+
translatorBudget.releaseRetained(accepted.bytes, scope);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
offset = newline + 1;
|
|
274
|
+
}
|
|
73
275
|
};
|
|
74
276
|
|
|
75
277
|
try {
|
|
76
278
|
while (true) {
|
|
77
279
|
const { done, value } = await reader.read();
|
|
78
|
-
if (value)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
280
|
+
if (value) {
|
|
281
|
+
const sourceBytes = value.byteLength;
|
|
282
|
+
const sourceReservation = translatorBudget.reserveTransient(sourceBytes, scope);
|
|
283
|
+
let sourceCommitted = false;
|
|
284
|
+
try {
|
|
285
|
+
const decoded = decoder.decode(value, { stream: !done });
|
|
286
|
+
sourceReservation.commitRetained();
|
|
287
|
+
sourceCommitted = true;
|
|
288
|
+
yield* consumeDecoded(decoded);
|
|
289
|
+
} finally {
|
|
290
|
+
if (sourceCommitted) translatorBudget.releaseRetained(sourceBytes, scope);
|
|
291
|
+
else sourceReservation.release();
|
|
292
|
+
}
|
|
85
293
|
}
|
|
86
|
-
|
|
87
294
|
if (!done) continue;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
295
|
+
const finalDecoded = decoder.decode();
|
|
296
|
+
yield* consumeDecoded(finalDecoded);
|
|
297
|
+
if (lineBuffer.length > 0) {
|
|
298
|
+
const accepted = acceptLine();
|
|
299
|
+
if (accepted) {
|
|
300
|
+
try {
|
|
301
|
+
yield accepted.record;
|
|
302
|
+
} finally {
|
|
303
|
+
translatorBudget.releaseRetained(accepted.bytes, scope);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
93
306
|
}
|
|
94
307
|
const finalRecord = dispatch();
|
|
95
|
-
if (finalRecord)
|
|
308
|
+
if (finalRecord) {
|
|
309
|
+
try {
|
|
310
|
+
yield finalRecord.record;
|
|
311
|
+
} finally {
|
|
312
|
+
translatorBudget.releaseRetained(finalRecord.bytes, scope);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
96
315
|
break;
|
|
97
316
|
}
|
|
98
317
|
} finally {
|
|
318
|
+
translatorBudget.releaseRetained(lineRetainedBytes + dataLinesBytes + eventBytes, scope);
|
|
99
319
|
signal?.removeEventListener("abort", onAbort);
|
|
100
320
|
try { await reader.cancel(); } catch { /* already closed/errored */ }
|
|
101
321
|
try { reader.releaseLock(); } catch { /* already released */ }
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConfigDir,
|
|
3
|
+
reconcileConfigWarningMemos,
|
|
4
|
+
sweepDeadOcxStartProcessCache,
|
|
5
|
+
} from "../config";
|
|
6
|
+
import { reconcileCodexReauthState } from "../codex/account-runtime-state";
|
|
7
|
+
import { reconcileCatalogWarningMemos } from "../codex/catalog/aggregation";
|
|
8
|
+
import { reconcileProviderFetchWarnings } from "../codex/catalog/provider-fetch";
|
|
9
|
+
import { reconcileModelCacheGeneration } from "../codex/model-cache";
|
|
10
|
+
import { reconcilePoolRotationState } from "../codex/pool-rotation";
|
|
11
|
+
import { reconcileCodexQuotaAccounts } from "../codex/quota";
|
|
12
|
+
import {
|
|
13
|
+
listLiveCodexAccountIds,
|
|
14
|
+
reconcileCodexRoutingHealth,
|
|
15
|
+
} from "../codex/routing";
|
|
16
|
+
import { sweepExpiredSubagentModelHealth } from "../codex/subagent-model-fallback";
|
|
17
|
+
import {
|
|
18
|
+
reconcileComboTargetCooldowns,
|
|
19
|
+
sweepExpiredComboTargetCooldowns,
|
|
20
|
+
} from "../combos/failover";
|
|
21
|
+
import { reconcileComboWarningMemos } from "../combos/request";
|
|
22
|
+
import { reconcileComboRotationState } from "../combos/resolve";
|
|
23
|
+
import { listLiveComboTargetKeys } from "../combos/types";
|
|
24
|
+
import {
|
|
25
|
+
listLiveConfigOwnershipRoots,
|
|
26
|
+
reconcileConfigOwnershipRoots,
|
|
27
|
+
} from "./config-ownership";
|
|
28
|
+
import { reconcileGcpAdcTokens, sweepExpiredGcpAdcTokens } from "./gcp-adc";
|
|
29
|
+
import {
|
|
30
|
+
reconcileOAuthFlowState,
|
|
31
|
+
sweepExpiredXaiPermanentFailureVerdicts,
|
|
32
|
+
} from "../oauth";
|
|
33
|
+
import { sweepExpiredAnthropicRoutingHealth } from "../oauth/anthropic-routing";
|
|
34
|
+
import { listLiveOAuthAccountKeys, reconcileOAuthReauthState } from "../oauth/store";
|
|
35
|
+
import { reconcileGuardianBackoff } from "../oauth/token-guardian";
|
|
36
|
+
import { sweepExpiredApiKeyCooldowns } from "../providers/key-failover";
|
|
37
|
+
import { sweepExpiredResponseStates } from "../responses/state";
|
|
38
|
+
import { sweepExpiredAntigravityReplay } from "../adapters/google-antigravity-replay";
|
|
39
|
+
import { reconcileProviderAccountQuotaRows } from "../providers/quota";
|
|
40
|
+
import { reconcileRouterWarningMemos } from "../router";
|
|
41
|
+
import type { OcxConfig } from "../types";
|
|
42
|
+
import {
|
|
43
|
+
type GenerationContext,
|
|
44
|
+
reconcileStateGeneration,
|
|
45
|
+
registerStateStore,
|
|
46
|
+
setGenerationContextBuilder,
|
|
47
|
+
type StateStoreRegistration,
|
|
48
|
+
} from "./state-store-sweeper";
|
|
49
|
+
|
|
50
|
+
let liveServerConfig: OcxConfig | null = null;
|
|
51
|
+
|
|
52
|
+
export function setLiveStateStoreConfig(config: OcxConfig): void {
|
|
53
|
+
liveServerConfig = config;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function reconcileLiveStateStores() {
|
|
57
|
+
if (!liveServerConfig) return { storesVisited: 0, rowsRemoved: 0 };
|
|
58
|
+
return reconcileStateGeneration(buildGenerationContext());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildGenerationContext(): GenerationContext {
|
|
62
|
+
if (!liveServerConfig) throw new Error("live server config is not installed");
|
|
63
|
+
const providerNames = new Set(Object.keys(liveServerConfig.providers));
|
|
64
|
+
return {
|
|
65
|
+
generation: 0,
|
|
66
|
+
providerNames,
|
|
67
|
+
comboIds: new Set(Object.keys(liveServerConfig.combos ?? {})),
|
|
68
|
+
comboTargets: listLiveComboTargetKeys(liveServerConfig),
|
|
69
|
+
codexAccountIds: listLiveCodexAccountIds(liveServerConfig),
|
|
70
|
+
oauthAccountKeys: listLiveOAuthAccountKeys(providerNames),
|
|
71
|
+
configRoots: listLiveConfigOwnershipRoots(getConfigDir()),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const STATE_STORE_REGISTRATIONS = [
|
|
76
|
+
{ name: "subagent-model-health", sweepExpired: sweepExpiredSubagentModelHealth },
|
|
77
|
+
{ name: "api-key-cooldowns", sweepExpired: sweepExpiredApiKeyCooldowns },
|
|
78
|
+
{
|
|
79
|
+
name: "combo-target-cooldowns",
|
|
80
|
+
sweepExpired: sweepExpiredComboTargetCooldowns,
|
|
81
|
+
reconcileGeneration: reconcileComboTargetCooldowns,
|
|
82
|
+
},
|
|
83
|
+
{ name: "anthropic-routing-health", sweepExpired: sweepExpiredAnthropicRoutingHealth },
|
|
84
|
+
{ name: "xai-refresh-verdicts", sweepExpired: sweepExpiredXaiPermanentFailureVerdicts },
|
|
85
|
+
{ name: "responses-continuation", sweepExpired: sweepExpiredResponseStates },
|
|
86
|
+
{ name: "antigravity-replay", sweepExpired: sweepExpiredAntigravityReplay },
|
|
87
|
+
{ name: "config-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileConfigWarningMemos(context.generation) },
|
|
88
|
+
{ name: "catalog-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileCatalogWarningMemos(context.generation) },
|
|
89
|
+
{ name: "provider-fetch-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileProviderFetchWarnings(context.generation) },
|
|
90
|
+
{ name: "combo-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileComboWarningMemos(context.generation) },
|
|
91
|
+
{ name: "router-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileRouterWarningMemos(context.generation) },
|
|
92
|
+
{ name: "codex-quota", reconcileGeneration: reconcileCodexQuotaAccounts },
|
|
93
|
+
{ name: "provider-quota-history", reconcileGeneration: reconcileProviderAccountQuotaRows },
|
|
94
|
+
{ name: "codex-routing-health", reconcileGeneration: reconcileCodexRoutingHealth },
|
|
95
|
+
{ name: "model-cache-history", reconcileGeneration: reconcileModelCacheGeneration },
|
|
96
|
+
{ name: "pool-rotation", reconcileGeneration: reconcilePoolRotationState },
|
|
97
|
+
{ name: "combo-rotation", reconcileGeneration: reconcileComboRotationState },
|
|
98
|
+
{ name: "guardian-backoff", reconcileGeneration: reconcileGuardianBackoff },
|
|
99
|
+
{ name: "codex-reauth", reconcileGeneration: reconcileCodexReauthState },
|
|
100
|
+
{ name: "oauth-reauth", reconcileGeneration: reconcileOAuthReauthState },
|
|
101
|
+
{ name: "gcp-adc", sweepExpired: sweepExpiredGcpAdcTokens, reconcileGeneration: reconcileGcpAdcTokens },
|
|
102
|
+
{ name: "config-ownership", reconcileGeneration: reconcileConfigOwnershipRoots },
|
|
103
|
+
{ name: "oauth-flow-state", reconcileGeneration: reconcileOAuthFlowState },
|
|
104
|
+
{ name: "ocx-start-process-cache", sweepLiveness: sweepDeadOcxStartProcessCache },
|
|
105
|
+
] satisfies readonly StateStoreRegistration[];
|
|
106
|
+
|
|
107
|
+
for (const registration of STATE_STORE_REGISTRATIONS) registerStateStore(registration);
|
|
108
|
+
|
|
109
|
+
setGenerationContextBuilder(buildGenerationContext);
|