@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
@@ -26,6 +26,18 @@ export interface DecodedConnectFrames {
26
26
  remainder: Uint8Array;
27
27
  }
28
28
 
29
+ interface CopyReservation {
30
+ commitRetained(): void;
31
+ release(): void;
32
+ }
33
+
34
+ interface InspectedConnectFrame {
35
+ flags: number;
36
+ length: number;
37
+ payloadStart: number;
38
+ readBytes: number;
39
+ }
40
+
29
41
  export class ConnectFrameError extends Error {
30
42
  constructor(
31
43
  public readonly code: ConnectFrameErrorCode,
@@ -65,27 +77,33 @@ export function encodeConnectFrame(
65
77
  return frame;
66
78
  }
67
79
 
68
- export function tryDecodeConnectFrame(input: Uint8Array, offset = 0): DecodedConnectFrame | null {
80
+ export function tryDecodeConnectFrame(
81
+ input: Uint8Array,
82
+ offset = 0,
83
+ maxPayloadBytes = MAX_CONNECT_FRAME_PAYLOAD_BYTES,
84
+ reservePayloadCopy?: (bytes: number) => CopyReservation | undefined,
85
+ ): DecodedConnectFrame | null {
69
86
  assertOffset(input, offset);
70
- if (input.length - offset < CONNECT_FRAME_HEADER_BYTES) return null;
71
-
72
- const view = new DataView(input.buffer, input.byteOffset + offset, input.byteLength - offset);
73
- const flags = view.getUint8(0);
74
- const length = view.getUint32(1, false);
75
- const readBytes = CONNECT_FRAME_HEADER_BYTES + length;
76
- if (input.length - offset < readBytes) return null;
77
-
78
- const payloadStart = offset + CONNECT_FRAME_HEADER_BYTES;
79
- const payloadEnd = payloadStart + length;
80
- const payload = input.slice(payloadStart, payloadEnd);
87
+ const inspected = inspectConnectFrame(input, offset, maxPayloadBytes);
88
+ if (!inspected) return null;
89
+ const payloadEnd = inspected.payloadStart + inspected.length;
90
+ const reservation = reservePayloadCopy?.(inspected.length);
91
+ let payload: Uint8Array;
92
+ try {
93
+ payload = input.slice(inspected.payloadStart, payloadEnd);
94
+ reservation?.commitRetained();
95
+ } catch (error) {
96
+ reservation?.release();
97
+ throw error;
98
+ }
81
99
  return {
82
100
  frame: {
83
- flags,
101
+ flags: inspected.flags,
84
102
  payload,
85
- compressed: isConnectFrameCompressed(flags),
86
- endStream: isConnectFrameEndStream(flags),
103
+ compressed: isConnectFrameCompressed(inspected.flags),
104
+ endStream: isConnectFrameEndStream(inspected.flags),
87
105
  },
88
- readBytes,
106
+ readBytes: inspected.readBytes,
89
107
  };
90
108
  }
91
109
 
@@ -108,19 +126,76 @@ export function decodeConnectFrames(input: Uint8Array): ConnectFrame[] {
108
126
  return frames;
109
127
  }
110
128
 
111
- export function decodeAvailableConnectFrames(input: Uint8Array): DecodedConnectFrames {
112
- const frames: ConnectFrame[] = [];
129
+ export function decodeAvailableConnectFrames(
130
+ input: Uint8Array,
131
+ maxPayloadBytes = MAX_CONNECT_FRAME_PAYLOAD_BYTES,
132
+ availableFrameSlots = Number.POSITIVE_INFINITY,
133
+ reservePayloadCopy?: (bytes: number) => CopyReservation | undefined,
134
+ ): DecodedConnectFrames {
135
+ const planned: Array<InspectedConnectFrame & { reservation?: CopyReservation }> = [];
113
136
  let offset = 0;
114
- while (offset < input.length) {
115
- const decoded = tryDecodeConnectFrame(input, offset);
116
- if (!decoded) break;
117
- frames.push(decoded.frame);
118
- offset += decoded.readBytes;
137
+ let remainderReservation: CopyReservation | undefined;
138
+ try {
139
+ while (offset < input.length && planned.length < availableFrameSlots) {
140
+ const inspected = inspectConnectFrame(input, offset, maxPayloadBytes);
141
+ if (!inspected) break;
142
+ const reservation = reservePayloadCopy?.(inspected.length);
143
+ planned.push({ ...inspected, reservation });
144
+ offset += inspected.readBytes;
145
+ }
146
+ const remainderBytes = offset === input.length ? 0 : bufferedPayloadBytes(input, offset);
147
+ remainderReservation = reservePayloadCopy?.(remainderBytes);
148
+
149
+ // Keep every reservation transient until every batch allocation succeeds. A later admission
150
+ // failure can then roll the entire batch back without needing ownership of unreturned frames.
151
+ const frames = planned.map(({ flags, length, payloadStart }) => {
152
+ const payload = input.slice(payloadStart, payloadStart + length);
153
+ return {
154
+ flags,
155
+ payload,
156
+ compressed: isConnectFrameCompressed(flags),
157
+ endStream: isConnectFrameEndStream(flags),
158
+ };
159
+ });
160
+ const remainder = offset === input.length ? new Uint8Array() : input.slice(offset);
161
+ for (const entry of planned) entry.reservation?.commitRetained();
162
+ remainderReservation?.commitRetained();
163
+ return { frames, remainder };
164
+ } catch (error) {
165
+ for (const entry of planned) entry.reservation?.release();
166
+ remainderReservation?.release();
167
+ throw error;
119
168
  }
120
- return {
121
- frames,
122
- remainder: offset === input.length ? new Uint8Array() : input.slice(offset),
123
- };
169
+ }
170
+
171
+ function inspectConnectFrame(
172
+ input: Uint8Array,
173
+ offset: number,
174
+ maxPayloadBytes: number,
175
+ ): InspectedConnectFrame | null {
176
+ if (input.length - offset < CONNECT_FRAME_HEADER_BYTES) return null;
177
+ const view = new DataView(input.buffer, input.byteOffset + offset, input.byteLength - offset);
178
+ const flags = view.getUint8(0);
179
+ const length = view.getUint32(1, false);
180
+ if (length > maxPayloadBytes) {
181
+ throw new ConnectFrameError("payload_too_large", `Connect frame payload too large: ${length}`);
182
+ }
183
+ const readBytes = CONNECT_FRAME_HEADER_BYTES + length;
184
+ if (input.length - offset < readBytes) return null;
185
+ return { flags, length, payloadStart: offset + CONNECT_FRAME_HEADER_BYTES, readBytes };
186
+ }
187
+
188
+ function bufferedPayloadBytes(input: Uint8Array, start: number): number {
189
+ let offset = start;
190
+ let payloadBytes = 0;
191
+ while (input.byteLength - offset >= CONNECT_FRAME_HEADER_BYTES) {
192
+ const length = new DataView(input.buffer, input.byteOffset + offset, input.byteLength - offset).getUint32(1, false);
193
+ const available = Math.min(length, input.byteLength - offset - CONNECT_FRAME_HEADER_BYTES);
194
+ payloadBytes += available;
195
+ if (available < length) break;
196
+ offset += CONNECT_FRAME_HEADER_BYTES + length;
197
+ }
198
+ return payloadBytes;
124
199
  }
125
200
 
126
201
  function assertOffset(input: Uint8Array, offset: number): void {
@@ -1,3 +1,5 @@
1
+ import type { TranslatorBudget } from "../../lib/translator-budget";
2
+
1
3
  export interface CursorKvStore {
2
4
  get(key: string): Uint8Array | undefined;
3
5
  set(key: string, value: Uint8Array): void;
@@ -7,19 +9,44 @@ function cloneBytes(value: Uint8Array): Uint8Array {
7
9
  return new Uint8Array(value);
8
10
  }
9
11
 
10
- export function createCursorKvStore(seed: Record<string, Uint8Array> = {}): CursorKvStore {
12
+ export function createCursorKvStore(
13
+ seed: Record<string, Uint8Array>,
14
+ budget: TranslatorBudget,
15
+ ): CursorKvStore {
11
16
  const values = new Map<string, Uint8Array>();
17
+ const encoder = new TextEncoder();
18
+ const sizes = new Map<string, number>();
12
19
  for (const [key, value] of Object.entries(seed)) {
20
+ const bytes = encoder.encode(key).byteLength + value.byteLength;
21
+ budget.chargeRetained(bytes, { kind: "cursor_kv" });
13
22
  values.set(key, cloneBytes(value));
23
+ sizes.set(key, bytes);
14
24
  }
15
25
 
16
26
  return {
17
27
  get(key) {
18
28
  const value = values.get(key);
19
- return value ? cloneBytes(value) : undefined;
29
+ if (!value) return undefined;
30
+ budget.chargeRetained(value.byteLength, { kind: "cursor_kv" });
31
+ const cloned = cloneBytes(value);
32
+ queueMicrotask(() => budget.releaseRetained(value.byteLength, { kind: "cursor_kv" }));
33
+ return cloned;
20
34
  },
21
35
  set(key, value) {
22
- values.set(key, cloneBytes(value));
36
+ const nextBytes = encoder.encode(key).byteLength + value.byteLength;
37
+ const previousBytes = sizes.get(key) ?? 0;
38
+ const reservation = budget.reserveTransient(nextBytes, { kind: "cursor_kv" });
39
+ let cloned: Uint8Array;
40
+ try {
41
+ cloned = cloneBytes(value);
42
+ } catch (error) {
43
+ reservation.release();
44
+ throw error;
45
+ }
46
+ values.set(key, cloned);
47
+ sizes.set(key, nextBytes);
48
+ reservation.commitRetained();
49
+ budget.releaseRetained(previousBytes, { kind: "cursor_kv" });
23
50
  },
24
51
  };
25
52
  }
@@ -18,6 +18,7 @@ import { GetUsableModelsResponseSchema } from "./gen/agent_pb";
18
18
 
19
19
  const CURSOR_GET_USABLE_MODELS_PATH = "/agent.v1.AgentService/GetUsableModels";
20
20
  const CURSOR_DISCOVERY_CLIENT_VERSION = "cli-2026.02.13-41ac335";
21
+ const CURSOR_MODEL_DISCOVERY_MAX_BYTES = 4 * 1024 * 1024;
21
22
 
22
23
  export interface CursorUsableModelsOptions {
23
24
  apiKey: string;
@@ -28,7 +29,7 @@ export interface CursorUsableModelsOptions {
28
29
 
29
30
  export type CursorUsableModelsResult =
30
31
  | { ok: true; models: string[] }
31
- | { ok: false; error: "auth" | "http" | "transport" | "timeout" | "decode" | "empty"; detail?: string };
32
+ | { ok: false; error: "auth" | "http" | "transport" | "timeout" | "decode" | "empty" | "too_large"; detail?: string };
32
33
 
33
34
  const RETRYABLE_DISCOVERY_ERRORS = new Set(["timeout", "transport"]);
34
35
  const DISCOVERY_RETRY_TIMEOUT_MS = 3_000;
@@ -97,12 +98,31 @@ async function fetchCursorUsableModelsOnce(opts: CursorUsableModelsOptions): Pro
97
98
 
98
99
  let status = 0;
99
100
  const chunks: Buffer[] = [];
101
+ let receivedBytes = 0;
102
+ let bodyRejected = false;
100
103
  req.on("response", headers => {
101
104
  status = Number(headers[":status"] ?? 0);
105
+ const contentLength = Number(headers["content-length"] ?? 0);
106
+ if (Number.isFinite(contentLength) && contentLength > CURSOR_MODEL_DISCOVERY_MAX_BYTES) {
107
+ bodyRejected = true;
108
+ req.close(http2.constants.NGHTTP2_CANCEL);
109
+ close({ ok: false, error: "too_large", detail: "GetUsableModels response exceeds 4 MiB" });
110
+ }
111
+ });
112
+ req.on("data", (chunk: Buffer) => {
113
+ if (bodyRejected) return;
114
+ receivedBytes += chunk.byteLength;
115
+ if (receivedBytes > CURSOR_MODEL_DISCOVERY_MAX_BYTES) {
116
+ bodyRejected = true;
117
+ req.close(http2.constants.NGHTTP2_CANCEL);
118
+ close({ ok: false, error: "too_large", detail: "GetUsableModels response exceeds 4 MiB" });
119
+ return;
120
+ }
121
+ chunks.push(chunk);
102
122
  });
103
- req.on("data", (chunk: Buffer) => chunks.push(chunk));
104
123
  req.on("error", () => close({ ok: false, error: "transport", detail: "HTTP/2 request failed" }));
105
124
  req.on("end", () => {
125
+ if (bodyRejected) return;
106
126
  if (status === 401 || status === 403) {
107
127
  return close({ ok: false, error: "auth", detail: `HTTP ${status}` });
108
128
  }