@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.
Files changed (175) hide show
  1. package/README.md +24 -0
  2. package/bin/ocx.mjs +32 -4
  3. package/gui/dist/assets/index-CHwf3tTD.css +1 -0
  4. package/gui/dist/assets/index-u5eFOv2y.js +67 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +1 -1
  7. package/src/adapters/anthropic-image-normalize.ts +114 -20
  8. package/src/adapters/anthropic.ts +126 -10
  9. package/src/adapters/azure.ts +3 -3
  10. package/src/adapters/base.ts +7 -3
  11. package/src/adapters/cursor/discovery.ts +14 -4
  12. package/src/adapters/cursor/effort-map.ts +18 -6
  13. package/src/adapters/cursor/framing.ts +102 -27
  14. package/src/adapters/cursor/kv-store.ts +30 -3
  15. package/src/adapters/cursor/live-models.ts +22 -2
  16. package/src/adapters/cursor/live-transport.ts +245 -49
  17. package/src/adapters/cursor/mcp-manager.ts +105 -8
  18. package/src/adapters/cursor/native-exec-mcp.ts +5 -3
  19. package/src/adapters/cursor/native-exec-shell.ts +296 -14
  20. package/src/adapters/cursor/native-exec.ts +381 -33
  21. package/src/adapters/cursor/protobuf-events.ts +28 -1
  22. package/src/adapters/cursor/protobuf-request.ts +71 -39
  23. package/src/adapters/cursor/request-builder.ts +2 -2
  24. package/src/adapters/cursor/transport.ts +2 -0
  25. package/src/adapters/cursor.ts +13 -2
  26. package/src/adapters/google-antigravity-replay.ts +184 -17
  27. package/src/adapters/google.ts +58 -8
  28. package/src/adapters/kiro-thinking.ts +23 -9
  29. package/src/adapters/kiro-tools.ts +49 -18
  30. package/src/adapters/kiro.ts +377 -133
  31. package/src/adapters/mimo-free.ts +36 -4
  32. package/src/adapters/openai-chat.ts +143 -17
  33. package/src/adapters/openai-responses.ts +130 -14
  34. package/src/adapters/run-turn-queue.ts +7 -1
  35. package/src/bridge.ts +466 -69
  36. package/src/chat/outbound.ts +144 -38
  37. package/src/claude/inbound-debug.ts +53 -8
  38. package/src/claude/outbound.ts +224 -38
  39. package/src/cli/agent-driven.ts +34 -1
  40. package/src/cli/catalog-prewarm.ts +5 -2
  41. package/src/cli/claude-desktop.ts +2 -2
  42. package/src/cli/doctor.ts +12 -0
  43. package/src/cli/export-command.ts +187 -0
  44. package/src/cli/help.ts +11 -0
  45. package/src/cli/index.ts +13 -3
  46. package/src/cli/init.ts +129 -102
  47. package/src/cli/opencode.ts +36 -151
  48. package/src/cli/star-prompt.ts +13 -4
  49. package/src/cli/status-oauth.ts +12 -2
  50. package/src/clients/config-export.ts +377 -0
  51. package/src/codex/account-runtime-state.ts +19 -1
  52. package/src/codex/account-store.ts +162 -82
  53. package/src/codex/auth-api.ts +467 -159
  54. package/src/codex/auth-context.ts +15 -2
  55. package/src/codex/catalog/aggregation.ts +15 -0
  56. package/src/codex/catalog/effort.ts +16 -6
  57. package/src/codex/catalog/metadata.ts +6 -0
  58. package/src/codex/catalog/parsing.ts +3 -1
  59. package/src/codex/catalog/provider-fetch.ts +29 -0
  60. package/src/codex/catalog/sync.ts +64 -7
  61. package/src/codex/catalog.ts +2 -2
  62. package/src/codex/inject.ts +5 -5
  63. package/src/codex/main-account-cache.ts +8 -1
  64. package/src/codex/model-cache.ts +81 -2
  65. package/src/codex/pool-rotation.ts +39 -0
  66. package/src/codex/project-config-warnings.ts +12 -1
  67. package/src/codex/quota.ts +35 -3
  68. package/src/codex/routing.ts +46 -1
  69. package/src/codex/shim.ts +10 -4
  70. package/src/codex/subagent-model-fallback.ts +12 -0
  71. package/src/codex/websocket-registry.ts +27 -0
  72. package/src/combos/failover.ts +31 -1
  73. package/src/combos/request.ts +9 -0
  74. package/src/combos/resolve.ts +60 -4
  75. package/src/combos/types.ts +12 -0
  76. package/src/config.ts +510 -55
  77. package/src/github/star-state.ts +13 -1
  78. package/src/images/fulfill.ts +39 -1
  79. package/src/images/loop.ts +52 -12
  80. package/src/lib/admission.ts +83 -0
  81. package/src/lib/app-owned-memory-stores.ts +173 -0
  82. package/src/lib/app-owned-memory.ts +265 -0
  83. package/src/lib/bun-stream-caps.ts +31 -7
  84. package/src/lib/config-ownership.ts +33 -0
  85. package/src/lib/crash-guard.ts +65 -5
  86. package/src/lib/debug-log-buffer.ts +47 -6
  87. package/src/lib/destination-policy.ts +12 -1
  88. package/src/lib/errors.ts +3 -0
  89. package/src/lib/gcp-adc.ts +40 -2
  90. package/src/lib/injection-debug-log.ts +26 -2
  91. package/src/lib/provider-outbound.ts +3 -0
  92. package/src/lib/sidecar-tracker.ts +5 -2
  93. package/src/lib/sse-decoder.ts +257 -37
  94. package/src/lib/state-store-registrations.ts +109 -0
  95. package/src/lib/state-store-sweeper.ts +184 -0
  96. package/src/lib/translator-budget.ts +356 -0
  97. package/src/lib/windows-secret-acl.ts +33 -12
  98. package/src/lib/winsw.ts +14 -1
  99. package/src/oauth/anthropic-routing.ts +31 -7
  100. package/src/oauth/google-antigravity.ts +2 -1
  101. package/src/oauth/health.ts +30 -12
  102. package/src/oauth/index.ts +127 -23
  103. package/src/oauth/kiro-credentials.ts +72 -1
  104. package/src/oauth/kiro.ts +23 -4
  105. package/src/oauth/store.ts +165 -18
  106. package/src/oauth/token-guardian.ts +43 -4
  107. package/src/oauth/types.ts +2 -1
  108. package/src/providers/base-url-choices.ts +10 -0
  109. package/src/providers/derive.ts +12 -0
  110. package/src/providers/free-directory.ts +4 -1
  111. package/src/providers/key-failover.ts +12 -0
  112. package/src/providers/openai-sidecar.ts +4 -1
  113. package/src/providers/quota.ts +68 -7
  114. package/src/providers/registry.ts +279 -3
  115. package/src/responses/parser.ts +5 -1
  116. package/src/responses/spill-store.ts +394 -0
  117. package/src/responses/state.ts +520 -102
  118. package/src/router.ts +18 -1
  119. package/src/server/adapter-resolve.ts +20 -3
  120. package/src/server/auth-cors.ts +121 -28
  121. package/src/server/chat-completions.ts +57 -12
  122. package/src/server/claude-messages.ts +85 -13
  123. package/src/server/index.ts +242 -100
  124. package/src/server/lifecycle.ts +155 -25
  125. package/src/server/management/agent-settings-routes.ts +79 -36
  126. package/src/server/management/api-key-usage.ts +167 -0
  127. package/src/server/management/body.ts +35 -0
  128. package/src/server/management/combo-routes.ts +5 -1
  129. package/src/server/management/config-routes.ts +42 -12
  130. package/src/server/management/logs-usage-routes.ts +41 -21
  131. package/src/server/management/model-routes.ts +188 -54
  132. package/src/server/management/oauth-account-routes.ts +115 -26
  133. package/src/server/management/provider-routes.ts +56 -6
  134. package/src/server/management/shared.ts +16 -3
  135. package/src/server/management/sidebar-routes.ts +50 -1
  136. package/src/server/management/system-restart.ts +13 -6
  137. package/src/server/management/system-routes.ts +15 -3
  138. package/src/server/management/usage-summary-cache.ts +86 -0
  139. package/src/server/management-api.ts +39 -5
  140. package/src/server/management-auth.ts +65 -14
  141. package/src/server/port-reclaim.ts +58 -12
  142. package/src/server/ports.ts +2 -0
  143. package/src/server/proxy-liveness.ts +60 -14
  144. package/src/server/relay-eager.ts +20 -4
  145. package/src/server/relay.ts +548 -154
  146. package/src/server/request-decompress.ts +51 -4
  147. package/src/server/request-log.ts +134 -15
  148. package/src/server/responses/collaboration.ts +15 -4
  149. package/src/server/responses/compact.ts +3 -0
  150. package/src/server/responses/core.ts +241 -66
  151. package/src/server/responses-image-gen-repair.ts +19 -5
  152. package/src/server/responses-item-id-repair.ts +23 -5
  153. package/src/server/sse-payload-rewrite.ts +71 -12
  154. package/src/server/startup-health-cache.ts +14 -1
  155. package/src/server/system-env.ts +8 -1
  156. package/src/server/windows-tcp-drop.ts +15 -5
  157. package/src/server/ws-bridge.ts +25 -0
  158. package/src/service.ts +179 -13
  159. package/src/storage/policy-job.ts +93 -23
  160. package/src/storage/policy-worker.ts +6 -0
  161. package/src/storage/restore-job.ts +62 -16
  162. package/src/storage/restore-worker.ts +6 -0
  163. package/src/storage/storage-mutation-coordinator.ts +36 -6
  164. package/src/storage/worker-lifecycle.ts +181 -47
  165. package/src/tray/windows.ts +97 -25
  166. package/src/types.ts +39 -6
  167. package/src/update/index.ts +24 -5
  168. package/src/update/job.ts +598 -73
  169. package/src/usage/log.ts +115 -17
  170. package/src/usage/summary.ts +67 -2
  171. package/src/vision/index.ts +112 -22
  172. package/src/web-search/loop.ts +38 -6
  173. package/src/web-search/progress-stream.ts +14 -3
  174. package/gui/dist/assets/index-BDjpkcRN.js +0 -67
  175. 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 entry: DebugLogEntry = { seq: nextSeq++, at: Date.now(), line };
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
- if (buffer.length > MAX_LINES) buffer.splice(0, buffer.length - MAX_LINES);
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;
@@ -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?: { includeComments?: false; signal?: AbortSignal },
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?: { includeComments?: boolean; signal?: AbortSignal },
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 buffer = "";
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 record = { ...(event ? { event } : {}), data: dataLines.join("\n") };
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
- return includeComments ? { kind: "event", ...record } : record;
137
+ dataLinesBytes = 0;
138
+ return {
139
+ record: includeComments ? { kind: "event", ...record } : record,
140
+ bytes: retainedRecordBytes,
141
+ };
53
142
  };
54
143
 
55
- const acceptLine = (rawLine: string): ServerSentEvent | SseRecord | undefined => {
56
- const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
57
- if (line === "") return dispatch();
58
- if (line.startsWith(":")) {
59
- if (!includeComments) return undefined;
60
- let comment = line.slice(1);
61
- if (comment.startsWith(" ")) comment = comment.slice(1);
62
- return { kind: "comment", comment };
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
- const colon = line.indexOf(":");
66
- const field = colon < 0 ? line : line.slice(0, colon);
67
- let value = colon < 0 ? "" : line.slice(colon + 1);
68
- if (value.startsWith(" ")) value = value.slice(1);
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
- if (field === "event") event = value;
71
- else if (field === "data") dataLines.push(value);
72
- return undefined;
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) buffer += decoder.decode(value, { stream: !done });
79
-
80
- let newline: number;
81
- while ((newline = buffer.indexOf("\n")) >= 0) {
82
- const record = acceptLine(buffer.slice(0, newline));
83
- buffer = buffer.slice(newline + 1);
84
- if (record) yield record;
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
- buffer += decoder.decode();
89
- if (buffer.length > 0) {
90
- const record = acceptLine(buffer);
91
- buffer = "";
92
- if (record) yield record;
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) yield 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);