@yansigit/opencodex 2.36.1 → 2.36.2-dev.20260829.53

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 (83) hide show
  1. package/README.md +9 -4
  2. package/gui/dist/assets/{ApiKeys-CsmNyf4I.js → ApiKeys-BhwqQvO8.js} +1 -1
  3. package/gui/dist/assets/{Claude-B4sHsGTD.js → Claude-DYNY8SHC.js} +1 -1
  4. package/gui/dist/assets/{CodexSet-BuAQ4YQn.js → CodexSet-tSN1aWmM.js} +1 -1
  5. package/gui/dist/assets/{FileIntegrationPage-BC65sEaS.js → FileIntegrationPage-B68tn2JT.js} +1 -1
  6. package/gui/dist/assets/{Grok-DNJWlISf.js → Grok-Dr6FN8SD.js} +1 -1
  7. package/gui/dist/assets/{Integrations-vGJshFZK.js → Integrations-jvqYofMm.js} +2 -2
  8. package/gui/dist/assets/{IntegrationsOverview-DYMkDFHE.js → IntegrationsOverview-Bjc7JSgw.js} +1 -1
  9. package/gui/dist/assets/{Logs-Coc7_RPP.js → Logs-C23CdgmP.js} +1 -1
  10. package/gui/dist/assets/{Models-CKVT8Meb.js → Models-Ci4wK3HO.js} +1 -1
  11. package/gui/dist/assets/{NumberStepper-Dug4wR8n.js → NumberStepper-nggECFok.js} +1 -1
  12. package/gui/dist/assets/Providers-DasIL4ng.js +1 -0
  13. package/gui/dist/assets/{RestoreDialog-BpuvVU2w.js → RestoreDialog-CW7LbS-w.js} +1 -1
  14. package/gui/dist/assets/{Startup-DAUQqNEN.js → Startup-Cx3dZre0.js} +1 -1
  15. package/gui/dist/assets/{Storage-DfP95SlJ.js → Storage-D0iUYQED.js} +1 -1
  16. package/gui/dist/assets/{Subagents-CMNsZLfk.js → Subagents-phLFffyl.js} +1 -1
  17. package/gui/dist/assets/{Usage-ESFRfBMO.js → Usage-NvYxG04o.js} +1 -1
  18. package/gui/dist/assets/{codex-stale-banner-W4FhikN7.js → codex-stale-banner-B5VQPO3t.js} +1 -1
  19. package/gui/dist/assets/{data-surface-B8PX8rjm.js → data-surface-B0APtUxm.js} +1 -1
  20. package/gui/dist/assets/{data-surface-CETFXqiA.js → data-surface-OiQUZok8.js} +1 -1
  21. package/gui/dist/assets/index-C8flH881.css +1 -0
  22. package/gui/dist/assets/{index-DHQHBRhb.js → index-CZ1jyQCF.js} +3 -3
  23. package/gui/dist/assets/{model-display-OBTJhQwT.js → model-display-Dlrzica2.js} +1 -1
  24. package/gui/dist/assets/{provider-payload-CJRJUn7w.js → provider-payload-Ca3Ff8oP.js} +1 -1
  25. package/gui/dist/assets/{section-tabs-B8sLMiz6.js → section-tabs-Bpm5lFcZ.js} +1 -1
  26. package/gui/dist/assets/shared-BHpPx-nZ.js +69 -0
  27. package/gui/dist/index.html +3 -3
  28. package/package.json +2 -1
  29. package/src/adapters/base.ts +2 -0
  30. package/src/adapters/command-code.ts +2 -1
  31. package/src/adapters/google-http.ts +34 -14
  32. package/src/adapters/google.ts +4 -1
  33. package/src/cli/dispatch.ts +2 -2
  34. package/src/cli/help.ts +2 -2
  35. package/src/cli/index.ts +9 -1
  36. package/src/cli/init.ts +52 -3
  37. package/src/cli/registry.ts +2 -2
  38. package/src/codex/account-lifecycle.ts +4 -8
  39. package/src/codex/inject.ts +14 -9
  40. package/src/config/process-state.ts +15 -0
  41. package/src/config.ts +17 -0
  42. package/src/generated/compatibility-version.json +67 -47
  43. package/src/lib/bun-stream-caps.ts +2 -1
  44. package/src/lib/pinned-http.ts +7 -1
  45. package/src/lib/process-control.ts +3 -2
  46. package/src/lib/provider-outbound.ts +77 -27
  47. package/src/lib/provider-runtime-fetch.ts +23 -0
  48. package/src/lib/provider-tls-profile.ts +97 -11
  49. package/src/lib/server-tls.ts +47 -0
  50. package/src/lib/test-provider-fetch.ts +6 -0
  51. package/src/lib/test-server-start.ts +5 -0
  52. package/src/lib/upstream-retry.ts +39 -25
  53. package/src/oauth/anthropic.ts +4 -3
  54. package/src/oauth/chatgpt.ts +7 -3
  55. package/src/oauth/command-code.ts +3 -2
  56. package/src/oauth/cursor.ts +3 -2
  57. package/src/oauth/github-copilot.ts +7 -5
  58. package/src/oauth/google-antigravity.ts +5 -4
  59. package/src/oauth/key-providers.ts +11 -13
  60. package/src/oauth/kimi.ts +7 -6
  61. package/src/oauth/kiro.ts +3 -2
  62. package/src/oauth/nous.ts +5 -5
  63. package/src/oauth/transport.ts +77 -0
  64. package/src/oauth/xai.ts +5 -4
  65. package/src/providers/replit/probe.ts +8 -3
  66. package/src/providers/xai-transport.ts +16 -2
  67. package/src/server/auth-cors.ts +15 -1
  68. package/src/server/chat-native.ts +9 -3
  69. package/src/server/index.ts +63 -23
  70. package/src/server/management/config-routes.ts +117 -15
  71. package/src/server/management/context.ts +4 -0
  72. package/src/server/management/provider-routes.ts +1 -0
  73. package/src/server/responses/compact.ts +5 -2
  74. package/src/server/responses/core.ts +16 -19
  75. package/src/server/responses/fetch-helpers.ts +15 -7
  76. package/src/server/ws-bridge.ts +33 -4
  77. package/src/service.ts +10 -1
  78. package/src/types/config.ts +9 -0
  79. package/src/types/provider.ts +2 -0
  80. package/src/types.ts +1 -0
  81. package/gui/dist/assets/Providers-DyaCHRWL.js +0 -1
  82. package/gui/dist/assets/index-pLho_LBF.css +0 -1
  83. package/gui/dist/assets/shared-DI18-uF7.js +0 -69
@@ -11,6 +11,8 @@ import type { WsData } from "../ws-bridge";
11
11
  import { waitForProviderRequestSlot } from "../../providers/request-pacing";
12
12
  import { withUpstreamHttpVersion } from "../../lib/upstream-http-version";
13
13
  import { providerTlsFetch } from "../../lib/provider-tls-profile";
14
+ import { testProviderFetch } from "../../lib/test-provider-fetch";
15
+ import { runtimeProviderFetch } from "../../lib/provider-runtime-fetch";
14
16
 
15
17
  export { withUpstreamHttpVersion };
16
18
 
@@ -58,6 +60,8 @@ export interface ProviderFetchOptions {
58
60
  modelId?: string;
59
61
  /** One pacing slot was acquired immediately before this fetch wrapper was created. */
60
62
  pacingSlotAcquired?: boolean;
63
+ /** Explicit test/integration executor; never read from serialized provider config. */
64
+ fetch?: typeof globalThis.fetch;
61
65
  }
62
66
 
63
67
  export function providerFetch(
@@ -65,7 +69,7 @@ export function providerFetch(
65
69
  runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
66
70
  options: ProviderFetchOptions = {},
67
71
  ): ProviderFetch {
68
- const base = (provider as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch ?? globalThis.fetch;
72
+ const base = options.fetch ?? testProviderFetch(provider) ?? runtimeProviderFetch(provider, options.providerName) ?? globalThis.fetch;
69
73
  const preconnect = (...args: Parameters<typeof globalThis.fetch.preconnect>): void => {
70
74
  base.preconnect?.(...args);
71
75
  };
@@ -126,7 +130,7 @@ export async function fetchWithHeaderTimeout(
126
130
  timeoutMs: number,
127
131
  preferIdentityEncoding = false,
128
132
  executor: typeof globalThis.fetch = globalThis.fetch,
129
- manualRedirect = false,
133
+ _manualRedirect = true,
130
134
  ): Promise<Response> {
131
135
  const pacing = executor as ProviderFetch;
132
136
  await pacing.waitForPacing?.(abortSignal);
@@ -142,16 +146,20 @@ export async function fetchWithHeaderTimeout(
142
146
  headers.set("accept-encoding", "identity");
143
147
  }
144
148
  try {
145
- return await fetchExecutor(url, {
149
+ const response = await fetchExecutor(url, {
146
150
  ...init,
147
151
  headers,
148
- // Credential-bearing sends opt into manual redirects so a 3xx is relayed
149
- // as a Response instead of being followed into a rejection that is
150
- // indistinguishable from a pre-connection failure (#914).
151
- ...(manualRedirect ? { redirect: "manual" as const } : {}),
152
+ // Upstream URLs are configuration, not navigation. Refuse every redirect
153
+ // so POST bodies and provider headers are never replayed to another hop.
154
+ redirect: "manual",
152
155
  signal: AbortSignal.any([abortSignal, timeout.signal]),
153
156
  timeout: 0,
154
157
  });
158
+ if (response.status >= 300 && response.status < 400) {
159
+ try { await response.body?.cancel(); } catch { /* ignore cancellation failures */ }
160
+ throw new Error(`upstream returned ${response.status} redirect; configure the final upstream URL directly`);
161
+ }
162
+ return response;
155
163
  } finally {
156
164
  clearTimeout(timer);
157
165
  }
@@ -54,6 +54,8 @@ export interface WsData {
54
54
  /** Turn/account ownership retained for the complete sideband socket lifetime. */
55
55
  liveTurnAdmissionLease?: AdmissionLease;
56
56
  admissionLease?: AdmissionReservation<ServerWebSocket<WsData>>;
57
+ wsDrain?: () => void;
58
+ wsDrainWaiter?: { resolve: () => void };
57
59
  }
58
60
 
59
61
  /**
@@ -155,6 +157,18 @@ export function sendTextFrame(ws: ServerWebSocket<WsData>, payload: string): voi
155
157
  // Bun returns -1 when queued with backpressure. That is accepted; a later 0 is the hard failure.
156
158
  }
157
159
 
160
+ async function sendTextFrameWithDrain(ws: ServerWebSocket<WsData>, payload: string): Promise<void> {
161
+ if (ws.readyState !== OPEN) throw new WsSendDroppedError();
162
+ const result = ws.send(payload);
163
+ if (result === 0) throw new WsSendDroppedError();
164
+ if (result === -1) {
165
+ await new Promise<void>(resolve => {
166
+ ws.data.wsDrainWaiter = { resolve };
167
+ });
168
+ if (ws.readyState !== OPEN) throw new WsSendDroppedError();
169
+ }
170
+ }
171
+
158
172
  export function sendJsonFrame(ws: ServerWebSocket<WsData>, payload: Record<string, unknown>): void {
159
173
  sendTextFrame(ws, JSON.stringify(payload));
160
174
  }
@@ -222,6 +236,7 @@ export async function pumpResponsesSseToWebSocket(
222
236
  sseStream: ReadableStream<Uint8Array>,
223
237
  options: {
224
238
  isCurrent?: () => boolean;
239
+ abortSignal?: AbortSignal;
225
240
  onTerminal?: ResponsesTerminalReporter;
226
241
  onSsePayload?: ResponsesPayloadObserver;
227
242
  } = {},
@@ -237,15 +252,22 @@ export async function pumpResponsesSseToWebSocket(
237
252
  };
238
253
  const cancel = () => {
239
254
  clientCancelled = true;
255
+ ws.data.wsDrain?.();
240
256
  void reader.cancel().catch(() => {});
241
257
  };
258
+ const drain = () => {
259
+ ws.data.wsDrainWaiter?.resolve();
260
+ ws.data.wsDrainWaiter = undefined;
261
+ };
262
+ ws.data.wsDrain = drain;
242
263
  ws.data.cancel = cancel;
264
+ options.abortSignal?.addEventListener("abort", cancel, { once: true });
243
265
 
244
266
  const decoder = new TextDecoder();
245
267
  const framer = new BoundedSseFrameBuffer();
246
268
  let terminalSeen = false;
247
269
 
248
- const handlePayload = (payload: string): boolean => {
270
+ const handlePayload = async (payload: string): Promise<boolean> => {
249
271
  if (!isCurrent()) return true;
250
272
  if (payload === "[DONE]") return false;
251
273
  try {
@@ -262,7 +284,7 @@ export async function pumpResponsesSseToWebSocket(
262
284
  return true;
263
285
  }
264
286
  if (terminalSeen) return true;
265
- sendTextFrame(ws, payload);
287
+ await sendTextFrameWithDrain(ws, payload);
266
288
  const terminalStatus = terminalStatusFromType(type);
267
289
  if (terminalStatus) {
268
290
  reportTerminal(terminalStatus);
@@ -279,13 +301,13 @@ export async function pumpResponsesSseToWebSocket(
279
301
  if (done) break;
280
302
  for (const frame of framer.feed(value)) {
281
303
  const payload = parseSseBlock(decoder.decode(frame.block));
282
- if (payload && handlePayload(payload)) break;
304
+ if (payload && await handlePayload(payload)) break;
283
305
  }
284
306
  }
285
307
  const tail = framer.finish();
286
308
  if (!terminalSeen && tail.byteLength > 0) {
287
309
  const payload = parseSseBlock(decoder.decode(tail));
288
- if (payload) handlePayload(payload);
310
+ if (payload) await handlePayload(payload);
289
311
  }
290
312
  if (!terminalSeen && isCurrent() && !clientCancelled) {
291
313
  reportTerminal("incomplete");
@@ -314,6 +336,10 @@ export async function pumpResponsesSseToWebSocket(
314
336
  // release the reader, even when terminal/send paths already cancelled it.
315
337
  void reader.cancel().catch(() => {});
316
338
  if (ws.data.cancel === cancel) ws.data.cancel = undefined;
339
+ if (ws.data.wsDrain === drain) ws.data.wsDrain = undefined;
340
+ options.abortSignal?.removeEventListener("abort", cancel);
341
+ ws.data.wsDrainWaiter?.resolve();
342
+ ws.data.wsDrainWaiter = undefined;
317
343
  }
318
344
  }
319
345
 
@@ -363,6 +389,7 @@ export async function sendResponseToWebSocket(
363
389
  options: {
364
390
  onTerminal?: ResponsesTerminalReporter;
365
391
  onSsePayload?: ResponsesPayloadObserver;
392
+ abortSignal?: AbortSignal;
366
393
  } = {},
367
394
  ): Promise<void> {
368
395
  if (!isCurrent()) {
@@ -391,6 +418,7 @@ export async function sendResponseToWebSocket(
391
418
  if (contentType.includes("text/event-stream")) {
392
419
  await pumpResponsesSseToWebSocket(ws, response.body, {
393
420
  isCurrent,
421
+ abortSignal: options.abortSignal,
394
422
  onTerminal: options.onTerminal,
395
423
  onSsePayload: options.onSsePayload,
396
424
  });
@@ -413,6 +441,7 @@ export async function sendResponseToWebSocket(
413
441
  if (looksLikeSse(prefix)) {
414
442
  await pumpResponsesSseToWebSocket(ws, stream, {
415
443
  isCurrent,
444
+ abortSignal: options.abortSignal,
416
445
  onTerminal: options.onTerminal,
417
446
  onSsePayload: options.onSsePayload,
418
447
  });
package/src/service.ts CHANGED
@@ -3400,6 +3400,9 @@ export type ServiceCommandPlan =
3400
3400
  | { ok: true; parsed: ParsedServiceArgs; command: string }
3401
3401
  | { ok: false; message: string };
3402
3402
 
3403
+ const SERVICE_SUBCOMMANDS = new Set(["install", "repair", "start", "stop", "status", "uninstall", "remove"]);
3404
+ const SERVICE_USAGE = "Usage: ocx service [install|repair|restart|start|stop|status|uninstall|remove] [--native|--scheduler]";
3405
+
3403
3406
  export function planServiceCommand(
3404
3407
  args: string[],
3405
3408
  options: { platform?: NodeJS.Platform; probeInstallation?: () => ServiceInstallationProbe } = {},
@@ -3408,6 +3411,12 @@ export function planServiceCommand(
3408
3411
  if (parsed.invalid.length > 0) {
3409
3412
  return { ok: false, message: `Unknown service option: ${parsed.invalid.join(" ")}` };
3410
3413
  }
3414
+ // Validate the requested action before touching platform probes. In a Linux
3415
+ // test container (and on a host without systemd), invalid CLI input must
3416
+ // report usage rather than an unrelated service-manager capability error.
3417
+ if (!SERVICE_SUBCOMMANDS.has(parsed.sub)) {
3418
+ return { ok: false, message: SERVICE_USAGE };
3419
+ }
3411
3420
  if (parsed.backend && parsed.sub !== "install") {
3412
3421
  return { ok: false, message: "--native/--scheduler apply to `ocx service install` only; other subcommands use the installed backend." };
3413
3422
  }
@@ -3603,7 +3612,7 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
3603
3612
  console.log("✅ service uninstalled.");
3604
3613
  break;
3605
3614
  default:
3606
- console.error("Usage: ocx service [install|repair|restart|start|stop|status|uninstall|remove] [--native|--scheduler]");
3615
+ console.error(SERVICE_USAGE);
3607
3616
  console.error(" With no subcommand, installs when absent or repairs/restarts an existing service.");
3608
3617
  console.error(" repair: refresh assets and restart an already-installed service (no admin re-prompt).");
3609
3618
  console.error(" restart: alias of repair.");
@@ -260,6 +260,13 @@ export interface OcxConfigRebaseProvenance {
260
260
  deletedTopLevelKeys: string[];
261
261
  }
262
262
 
263
+ export interface OcxServerTlsConfig {
264
+ certFile: string;
265
+ keyFile: string;
266
+ /** Exact externally reachable HTTPS origin; paths, query strings, fragments, and credentials are rejected. */
267
+ publicOrigin: string;
268
+ }
269
+
263
270
  export interface OcxConfig {
264
271
  port: number;
265
272
  autonomousRemediation?: {
@@ -512,6 +519,8 @@ export interface OcxConfig {
512
519
  contextCapValue?: number;
513
520
  /** Bind hostname. Default "127.0.0.1" (loopback only). Set "0.0.0.0" to expose on all interfaces. */
514
521
  hostname?: string;
522
+ /** Native Bun TLS for the public listener. Required for non-loopback binds. */
523
+ tls?: OcxServerTlsConfig;
515
524
  /**
516
525
  * Optional second listener bound to 127.0.0.1 that admits data-plane requests without a
517
526
  * credential (issue #1102).
@@ -568,6 +568,8 @@ export interface OcxProviderConfig {
568
568
  * before any response bytes are relayed, so the replay is lossless.
569
569
  */
570
570
  retryOn429?: RateLimitRetryPolicy;
571
+ /** Opt in to replaying transient upstream 5xx responses (at most three total sends). */
572
+ replayTransientFailures?: boolean;
571
573
  /**
572
574
  * Model ids whose OpenAI-compatible chat endpoint accepts `reasoning_split: true` and returns
573
575
  * thinking separately in `reasoning_content` / `reasoning_details` instead of visible content.
package/src/types.ts CHANGED
@@ -67,6 +67,7 @@ export type {
67
67
  OcxClientIntegrationsConfig,
68
68
  OcxSubagentRole,
69
69
  OcxConfigRebaseProvenance,
70
+ OcxServerTlsConfig,
70
71
  OcxConfig,
71
72
  OcxAccountPoolRotationStrategy,
72
73
  OcxComboStrategy,