@bitkyc08/opencodex 2.48.0 → 2.50.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 (141) hide show
  1. package/AGENTS_INSTALL.md +9 -1
  2. package/README.md +11 -5
  3. package/SPONSORS.md +1 -1
  4. package/assets/sponsors/orcarouter.png +0 -0
  5. package/assets/sponsors/packycode.png +0 -0
  6. package/gui/dist/assets/index-BoBRSehJ.css +1 -0
  7. package/gui/dist/assets/index-C39tnjXO.js +115 -0
  8. package/gui/dist/index.html +2 -2
  9. package/gui/dist/provider-icons/packycode.svg +19 -0
  10. package/gui/dist/provider-icons/qoder.svg +5 -0
  11. package/package.json +5 -3
  12. package/src/adapters/anthropic.ts +31 -16
  13. package/src/adapters/codebuddy/adapter.ts +85 -0
  14. package/src/adapters/codebuddy/profiles.ts +52 -0
  15. package/src/adapters/coding-agent/profile.ts +100 -0
  16. package/src/adapters/coding-agent/protocol.ts +463 -0
  17. package/src/adapters/coding-agent/turn.ts +353 -0
  18. package/src/adapters/google.ts +15 -11
  19. package/src/adapters/mimo-free.ts +3 -0
  20. package/src/adapters/openai-chat.ts +2 -2
  21. package/src/adapters/openai-responses.ts +18 -11
  22. package/src/adapters/qoder/adapter.ts +70 -0
  23. package/src/adapters/qoder/live-models.ts +89 -0
  24. package/src/adapters/qoder/profiles.ts +36 -0
  25. package/src/adapters/registry.ts +12 -0
  26. package/src/adapters/responses-tool-schema.ts +113 -8
  27. package/src/claude/inbound.ts +17 -5
  28. package/src/cli/account-api.ts +18 -3
  29. package/src/cli/account-auth.ts +8 -1
  30. package/src/cli/account-extended.ts +2 -1
  31. package/src/cli/account.ts +1 -0
  32. package/src/cli/capabilities.ts +15 -1
  33. package/src/cli/dispatch.ts +2 -0
  34. package/src/cli/doctor.ts +40 -0
  35. package/src/cli/effort.ts +24 -8
  36. package/src/cli/help.ts +2 -0
  37. package/src/cli/index.ts +29 -2
  38. package/src/cli/models-runtime.ts +8 -3
  39. package/src/cli/observe.ts +13 -3
  40. package/src/cli/provider-runtime.ts +2 -1
  41. package/src/cli/registry.ts +2 -2
  42. package/src/cli/system-command.ts +10 -3
  43. package/src/cli/usage-report.ts +9 -5
  44. package/src/clients/config-export/zcode.ts +24 -0
  45. package/src/codex/account-lifecycle.ts +35 -2
  46. package/src/codex/account-runtime-state.ts +6 -1
  47. package/src/codex/account-store.ts +72 -9
  48. package/src/codex/account-usability.ts +3 -2
  49. package/src/codex/auth-api.ts +113 -26
  50. package/src/codex/auth-collision.ts +12 -2
  51. package/src/codex/auth-context.ts +96 -7
  52. package/src/codex/catalog/parsing.ts +23 -0
  53. package/src/codex/catalog/provider-fetch.ts +144 -11
  54. package/src/codex/catalog/sync.ts +14 -0
  55. package/src/codex/inject.ts +128 -30
  56. package/src/codex/internal/catalog-writer.ts +3 -0
  57. package/src/codex/journal.ts +61 -12
  58. package/src/codex/model-cache.ts +11 -4
  59. package/src/codex/native-profile-startup.ts +72 -5
  60. package/src/codex/native-profile-store.ts +2 -2
  61. package/src/codex/ocx-compaction-history.ts +226 -0
  62. package/src/codex/project-config-warnings.ts +3 -1
  63. package/src/codex/quota-auto-refresh.ts +6 -1
  64. package/src/codex/quota.ts +71 -15
  65. package/src/codex/reserve-availability.ts +21 -5
  66. package/src/codex/runtime.ts +45 -1
  67. package/src/codex/sync.ts +5 -0
  68. package/src/combos/index.ts +2 -0
  69. package/src/combos/resolve.ts +52 -0
  70. package/src/config.ts +59 -0
  71. package/src/generated/compatibility-version.json +178 -114
  72. package/src/images/loop.ts +1 -0
  73. package/src/images/xai-video-client.ts +2 -0
  74. package/src/integrations/registry.ts +1 -0
  75. package/src/lib/errors.ts +8 -0
  76. package/src/lib/privacy.ts +25 -0
  77. package/src/lib/process-control.ts +52 -8
  78. package/src/lib/upstream-retry.ts +1 -0
  79. package/src/oauth/chatgpt.ts +83 -0
  80. package/src/oauth/health.ts +47 -12
  81. package/src/oauth/index.ts +46 -8
  82. package/src/oauth/token-guardian.ts +32 -6
  83. package/src/oauth/xai.ts +151 -8
  84. package/src/providers/api-key-selection-capture.ts +10 -0
  85. package/src/providers/api-key-selection.ts +2 -7
  86. package/src/providers/caller-authorization.ts +36 -0
  87. package/src/providers/codebuddy-models.ts +184 -0
  88. package/src/providers/derive.ts +5 -0
  89. package/src/providers/free-directory.ts +26 -2
  90. package/src/providers/google-ai-studio-model-discovery.ts +74 -0
  91. package/src/providers/openai-sidecar.ts +35 -11
  92. package/src/providers/opencode-zen-rate-limit.ts +75 -0
  93. package/src/providers/qoder-models.ts +25 -0
  94. package/src/providers/quota.ts +15 -0
  95. package/src/providers/registry.ts +140 -1
  96. package/src/responses/compaction.ts +4 -0
  97. package/src/responses/task-input.ts +21 -1
  98. package/src/router.ts +1 -1
  99. package/src/server/auth-cors.ts +6 -0
  100. package/src/server/chat-completions.ts +30 -13
  101. package/src/server/chat-native.ts +10 -1
  102. package/src/server/claude-messages.ts +17 -7
  103. package/src/server/images.ts +3 -2
  104. package/src/server/index.ts +25 -2
  105. package/src/server/management/account-selection-stream.ts +13 -4
  106. package/src/server/management/config-routes.ts +24 -5
  107. package/src/server/management/logs-usage-routes.ts +5 -1
  108. package/src/server/management/model-rows.ts +16 -1
  109. package/src/server/management/native-integration-routes.ts +2 -1
  110. package/src/server/management/oauth-account-routes.ts +6 -2
  111. package/src/server/management/provider-routes.ts +33 -2
  112. package/src/server/management/request-history-routes.ts +4 -2
  113. package/src/server/management/route-registry.ts +5 -4
  114. package/src/server/management/shared.ts +66 -3
  115. package/src/server/management-api.ts +15 -1
  116. package/src/server/port-reclaim.ts +11 -26
  117. package/src/server/request-decompress.ts +91 -3
  118. package/src/server/request-log.ts +16 -0
  119. package/src/server/responses/codex-ws-wire.ts +1 -1
  120. package/src/server/responses/collaboration.ts +4 -9
  121. package/src/server/responses/compact.ts +8 -2
  122. package/src/server/responses/context-overflow.ts +11 -0
  123. package/src/server/responses/core.ts +285 -57
  124. package/src/server/responses/fetch-helpers.ts +18 -7
  125. package/src/server/responses/policy-fallback.ts +18 -2
  126. package/src/server/search.ts +2 -2
  127. package/src/service.ts +128 -9
  128. package/src/storage/cleanup.ts +77 -45
  129. package/src/types/accounts.ts +18 -0
  130. package/src/types/config.ts +43 -1
  131. package/src/types/provider.ts +56 -0
  132. package/src/types.ts +4 -0
  133. package/src/usage/log.ts +24 -0
  134. package/src/vision/anthropic-describe.ts +1 -0
  135. package/src/web-search/anthropic-executor.ts +1 -0
  136. package/src/web-search/loop.ts +1 -0
  137. package/src/web-search/ollama-executor.ts +127 -0
  138. package/src/web-search/passthrough-bridge.ts +761 -0
  139. package/src/web-search/progress-stream.ts +4 -0
  140. package/gui/dist/assets/index-B5r7LNHN.js +0 -115
  141. package/gui/dist/assets/index-D5SiRo8X.css +0 -1
@@ -2,11 +2,9 @@
2
2
  * Reclaim a listen port after stop/update so restart can stay on the configured
3
3
  * port instead of hopping to an ephemeral one (Windows CLOSE_WAIT / leftover ocx).
4
4
  *
5
- * Killing is never the default. A process may be killed only when the caller
6
- * sets `killOcxHolders` and either supplies a non-empty `onlyKillPids` allowlist
7
- * (trusted teardown PIDs, including allowlisted holders that fail ocx revalidate)
8
- * or enables `killAllOcxOnPort` for revalidated ocx listeners. Unknown foreign
9
- * (non-ocx, non-allowlisted) processes are never killed.
5
+ * Killing is never the default. It requires `killOcxHolders`, an allowed PID or
6
+ * `killAllOcxOnPort`, and successful ocx verification. A historical PID allowlist
7
+ * never overrides a rejected verifier result; rejected live holders stay protected.
10
8
  */
11
9
  import { execFileSync } from "node:child_process";
12
10
  import { verifyPidIdentity } from "../config/process-state";
@@ -29,6 +27,9 @@ export type ReclaimListenPortOptions = WaitForPortOptions & {
29
27
  /**
30
28
  * Explicit PIDs the caller just stopped / hard-killed. An omitted or empty
31
29
  * list means no process may be killed — unless {@link killAllOcxOnPort} is set.
30
+ * The allowlist only narrows kill candidates: every candidate, allowlisted or
31
+ * not, still requires verifier acceptance (`verifyOcxFn(pid) === pid`) on each
32
+ * scan, and a rejected live holder is never killed or TCP-row dropped.
32
33
  */
33
34
  onlyKillPids?: number[];
34
35
  /**
@@ -36,8 +37,7 @@ export type ReclaimListenPortOptions = WaitForPortOptions & {
36
37
  * killed (re-checked each scan). Used by post-update restart so a Windows
37
38
  * service wrapper that respawns a *new* bun PID mid-reclaim cannot stay
38
39
  * protected just because it was absent from the pre-wait allowlist snapshot.
39
- * Never kills foreign (non-ocx) processes — only allowlisted teardown PIDs
40
- * and revalidated ocx listeners.
40
+ * Every candidate still requires ocx verifier acceptance before termination.
41
41
  */
42
42
  killAllOcxOnPort?: boolean;
43
43
  /**
@@ -174,8 +174,8 @@ export function listListenPids(port: number): number[] {
174
174
  * Never kills a process unless `killOcxHolders === true` and either
175
175
  * `onlyKillPids` is a non-empty allowlist or `killAllOcxOnPort` is set — then
176
176
  * revalidates immediately before each kill.
177
- * Never kills foreign processes. Never drops TCP rows while a live foreign or
178
- * protected ocx listener owns the port, or when the listener scan failed.
177
+ * Never overrides a rejected ocx verifier result. Never drops TCP rows while a
178
+ * rejected live or protected ocx listener owns the port, or when the scan failed.
179
179
  */
180
180
  export async function reclaimListenPort(
181
181
  port: number,
@@ -231,24 +231,9 @@ export async function reclaimListenPort(
231
231
  }
232
232
  const isOcx = verifyOcxFn(pid) === pid;
233
233
  const allowlisted = allowedKillPids.has(pid);
234
- // Pre-update PIDs can fail verify while still LISTENing (dead owner still
235
- // listed, or cmdline probe raced). Allowlisted teardown PIDs may be killed;
236
- // unknown foreign claimants must remain fail-closed.
237
234
  if (!isOcx) {
238
- if (mayKill && allowlisted) {
239
- if (!killed.has(pid)) {
240
- try {
241
- killFn(pid);
242
- killed.add(pid);
243
- } catch {
244
- // Kill failed: never SetTcpEntry while the process may still own the port.
245
- protectedOcxListener = true;
246
- }
247
- }
248
- if (!isAliveFn(pid)) killed.delete(pid);
249
- else protectedOcxListener = true;
250
- continue;
251
- }
235
+ // A saved PID narrows eligible candidates; it cannot override verifier rejection.
236
+ // Dead ghost owners have already been skipped by the liveness check above.
252
237
  foreignLive = true;
253
238
  continue;
254
239
  }
@@ -21,6 +21,58 @@ import type { TranslatorBudget } from "../lib/translator-budget";
21
21
  */
22
22
  export const MAX_DECOMPRESSED_BODY_BYTES = 256 * 1024 * 1024;
23
23
 
24
+ /**
25
+ * Hard ceiling on the opt-in `maxInboundBodyBytes` (#3573).
26
+ *
27
+ * The opt-in exists because a 922k-token session serializes past the 256 MiB default, and the
28
+ * request that crosses it is the compaction request itself — so the session can no longer
29
+ * shrink and is stuck. An UNBOUNDED inbound cap is not an acceptable answer: this admission
30
+ * limit is the only thing standing between one request and the process heap, and
31
+ * `readBoundedJsonRequestBody` materializes the body several times over (retained wire bytes,
32
+ * decoded bytes, the decoded string, the re-encoded measurement copies, and the parsed object
33
+ * graph), so peak RSS is a MULTIPLE of whatever is admitted here. 512 MiB is the largest value
34
+ * that keeps that multiple survivable on an ordinary machine, and it is what #3573 asked for.
35
+ */
36
+ export const MAX_CONFIGURABLE_INBOUND_BODY_BYTES = 512 * 1024 * 1024;
37
+
38
+ /** Floor for the opt-in. Below this an ordinary multi-image turn cannot be admitted at all. */
39
+ export const MIN_CONFIGURABLE_INBOUND_BODY_BYTES = 1024 * 1024;
40
+
41
+ /**
42
+ * Resolve the configured inbound admission limit, clamped to the supported range.
43
+ *
44
+ * Pure and total on purpose: the schema in `src/config.ts` degrades an invalid hand edit to
45
+ * `undefined` rather than failing the parse, so the schema cannot be the place the ceiling is
46
+ * enforced. Every caller resolves through here, which makes this the single auditable bound
47
+ * regardless of how the config object was produced.
48
+ *
49
+ * Omitted, zero, or non-finite = the 256 MiB default, so an unconfigured proxy admits exactly
50
+ * what it admits today.
51
+ */
52
+ export function resolveInboundBodyLimitBytes(configured: number | undefined): number {
53
+ if (configured === undefined || !Number.isFinite(configured) || configured <= 0) {
54
+ return MAX_DECOMPRESSED_BODY_BYTES;
55
+ }
56
+ return Math.min(
57
+ Math.max(Math.floor(configured), MIN_CONFIGURABLE_INBOUND_BODY_BYTES),
58
+ MAX_CONFIGURABLE_INBOUND_BODY_BYTES,
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Render a byte count, or nothing at all. `DecompressedBodyTooLargeError` accepts non-finite
64
+ * and untyped values from legacy callers and deliberately keeps them out of its own message;
65
+ * the client-facing message inherits that rule rather than printing `NaN MB`.
66
+ */
67
+ function megabytes(bytes: number): string | null {
68
+ return Number.isFinite(bytes) && bytes >= 0 && bytes <= Number.MAX_SAFE_INTEGER
69
+ ? (bytes / (1024 * 1024)).toFixed(1)
70
+ : null;
71
+ }
72
+
73
+ const INBOUND_CEILING_MB = (MAX_CONFIGURABLE_INBOUND_BODY_BYTES / (1024 * 1024)).toFixed(1);
74
+
75
+
24
76
  export class UnsupportedContentEncodingError extends Error {
25
77
  constructor(readonly encoding: string) {
26
78
  super(`Unsupported content-encoding: ${encoding}`);
@@ -54,6 +106,33 @@ export class DecompressedBodyTooLargeError extends Error {
54
106
  }
55
107
  }
56
108
 
109
+ /**
110
+ * Name OpenCodex as the refuser, and name the lever.
111
+ *
112
+ * #4112 gave the UPSTREAM context refusal on `/v1/responses` its own HTTP 413 with
113
+ * `context_length_exceeded`. That makes the two 413s on this surface look alike to a client
114
+ * while having opposite remedies: the upstream one means the provider will not take the turn,
115
+ * this one means the proxy never read it and a config key would have let it through. The
116
+ * wording deliberately avoids "context window"/"context length", which `classifyError` treats
117
+ * as evidence of an upstream context verdict.
118
+ */
119
+ export function describeInboundBodyRefusal(error: DecompressedBodyTooLargeError): string {
120
+ // A lower-bound measurement stopped counting at the cap; reporting it as exact would be a lie.
121
+ const approximate = error.measurement === "declared_wire" || error.measurement === "decoded_exact"
122
+ ? "" : "at least ";
123
+ const observed = megabytes(error.bytes);
124
+ const limit = megabytes(error.limit);
125
+ const sizes = limit === null
126
+ ? "the body is above the inbound admission limit"
127
+ : observed === null
128
+ ? `the body is above the ${limit} MB inbound admission limit`
129
+ : `the body is ${approximate}${observed} MB, above the ${limit} MB inbound admission limit`;
130
+ return `OpenCodex refused this request before reading it: ${sizes}. `
131
+ + "This is a local proxy limit, not a provider refusal. Raise \"maxInboundBodyBytes\" in "
132
+ + `config.json (ceiling ${INBOUND_CEILING_MB} MB) and restart the proxy, or compact the `
133
+ + "conversation earlier.";
134
+ }
135
+
57
136
  function assertBodySizeWithinLimit(
58
137
  body: Uint8Array,
59
138
  maxBytes: number,
@@ -259,7 +338,16 @@ export async function readBoundedJsonRequestBody(
259
338
  }
260
339
  }
261
340
 
262
- /** Parse a JSON data-plane body using the shared 256 MiB admission cap. */
263
- export function readJsonRequestBody(req: Request, budget?: TranslatorBudget): Promise<unknown> {
264
- return readBoundedJsonRequestBody(req, MAX_DECOMPRESSED_BODY_BYTES, budget);
341
+ /**
342
+ * Parse a JSON data-plane body using the shared admission cap.
343
+ *
344
+ * `maxBytes` is the resolved per-deployment limit from `resolveInboundBodyLimitBytes()`;
345
+ * omitting it keeps the 256 MiB default for callers with no config in scope.
346
+ */
347
+ export function readJsonRequestBody(
348
+ req: Request,
349
+ budget?: TranslatorBudget,
350
+ maxBytes: number = MAX_DECOMPRESSED_BODY_BYTES,
351
+ ): Promise<unknown> {
352
+ return readBoundedJsonRequestBody(req, maxBytes, budget);
265
353
  }
@@ -22,6 +22,8 @@ import {
22
22
  appendUsageEntry,
23
23
  isKnownAdmissionKind,
24
24
  isKnownInboundProtocol,
25
+ isKnownTerminalSource,
26
+ isKnownTransportPhase,
25
27
  isKnownUsageSurface,
26
28
  isCodexUsageAccountLogLabel,
27
29
  isValidReasoningWireValue,
@@ -320,6 +322,8 @@ export function requestLogEntryFromPersistedUsage(entry: PersistedUsageEntry): R
320
322
  ...(entry.usage ? { usage: entry.usage } : {}),
321
323
  ...(entry.totalTokens !== undefined ? { totalTokens: entry.totalTokens } : {}),
322
324
  ...(entry.attempts !== undefined ? { attempts: entry.attempts } : {}),
325
+ ...(isKnownTransportPhase(entry.transportPhase) ? { transportPhase: entry.transportPhase } : {}),
326
+ ...(isKnownTerminalSource(entry.terminalSource) ? { terminalSource: entry.terminalSource } : {}),
323
327
  ...(routeDecision ? { routeDecision } : {}),
324
328
  ...(claudeCompatibility ? { claudeCompatibility } : {}),
325
329
  };
@@ -441,6 +445,8 @@ export function addRequestLog(entry: RequestLogEntry) {
441
445
  ...(entry.usage ? { usage: entry.usage } : {}),
442
446
  ...(entry.totalTokens !== undefined ? { totalTokens: entry.totalTokens } : {}),
443
447
  ...(entry.attempts !== undefined ? { attempts: entry.attempts } : {}),
448
+ ...(isKnownTransportPhase(entry.transportPhase) ? { transportPhase: entry.transportPhase } : {}),
449
+ ...(isKnownTerminalSource(entry.terminalSource) ? { terminalSource: entry.terminalSource } : {}),
444
450
  ...failureDiagnostics,
445
451
  ...(entry.routeDecision ? { routeDecision: entry.routeDecision } : {}),
446
452
  ...(entry.claudeCompatibility ? { claudeCompatibility: entry.claudeCompatibility } : {}),
@@ -1113,6 +1119,16 @@ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchPara
1113
1119
  filtered = filtered.filter(entry => entry.model === model
1114
1120
  || entry.attempts?.some(attempt => attempt.model === model));
1115
1121
  }
1122
+ // #4057: "which account served this request" is the first question asked when one provider
1123
+ // holds several accounts, and until now the only way to answer it was to grep usage.jsonl by
1124
+ // hand. Attempts are matched for the same reason `provider` and `model` match them: when a
1125
+ // request failed over between pool accounts, a search for the account that finally served it
1126
+ // has to find that request, not only the account that first refused it.
1127
+ const account = params.get("account")?.trim();
1128
+ if (account) {
1129
+ filtered = filtered.filter(entry => entry.accountLogLabel === account
1130
+ || entry.attempts?.some(attempt => attempt.accountLogLabel === account));
1131
+ }
1116
1132
  const status = params.get("status")?.trim().toLowerCase();
1117
1133
  if (status) {
1118
1134
  filtered = /^[1-5]xx$/.test(status)
@@ -2,7 +2,7 @@ import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
2
2
  // If the 101 never arrives (network black hole), give SSE a chance well before
3
3
  // the caller's connect timeout (default 200s) would fire.
4
4
  export const UPGRADE_DEADLINE_MS = 10_000;
5
- export const CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS = 30_000;
5
+ export const CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS = 90_000;
6
6
  // Keep the push-based WS transport inside the same memory envelope as the
7
7
  // bounded SSE relays that consume this response. Unlike fetch response bodies,
8
8
  // a WebSocket cannot be paused when a ReadableStream applies backpressure, so
@@ -8,6 +8,7 @@ import {
8
8
  } from "../../config";
9
9
  import { parseRequest } from "../../responses/parser";
10
10
  import { externalTaskInputContent } from "../../responses/task-input";
11
+ import { MULTI_AGENT_MODE_HINT_RECOMMENDATION } from "../../codex/multi-agent-mode-policy";
11
12
  import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
12
13
  import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
13
14
  import { expandPreviousResponseInput, previousResponseProviderState, rememberResponseState } from "../../responses/state";
@@ -233,13 +234,7 @@ export function buildToolBridgeMaps(parsed: OcxParsedRequest, budget?: Translato
233
234
 
234
235
 
235
236
 
236
- export const PROACTIVE_MULTI_AGENT_MODE_TEXT = [
237
- "Proactive multi-agent delegation is active.",
238
- "Any earlier instruction requiring an explicit user request before spawning sub-agents no longer applies.",
239
- "Delegate independent sub-tasks to sub-agents whenever parallel work would materially improve speed or quality — do not serialize work that can run concurrently.",
240
- "Each sub-agent runs in its own context and can use all available tools; prefer spawning specialists over doing everything yourself.",
241
- "This mode remains active until a later multi-agent mode developer message changes it.",
242
- ].join(" ");
237
+ export const PROACTIVE_MULTI_AGENT_MODE_TEXT = MULTI_AGENT_MODE_HINT_RECOMMENDATION.text;
243
238
 
244
239
  const OPENCODEX_SUBAGENT_GUIDANCE_OPEN_TAG = "<opencodex_subagent_guidance>";
245
240
  const OPENCODEX_SUBAGENT_GUIDANCE_CLOSE_TAG = "</opencodex_subagent_guidance>";
@@ -491,8 +486,8 @@ export async function multiAgentGuidanceText(
491
486
  }
492
487
 
493
488
  const effort = parsed.options.reasoning;
494
- // v1 keeps only the upstream-parity behavior: Proactive text at the top tier
495
- // (ultra arrives as max on the wire). No designation/roster payload here.
489
+ // v1 changes only the delegation trigger at the top tier; other rules still apply.
490
+ // Ultra arrives as max on the wire. No designation/roster payload here.
496
491
  if (effort !== "max" && effort !== "ultra") return null;
497
492
  return `<multi_agent_mode>${PROACTIVE_MULTI_AGENT_MODE_TEXT}</multi_agent_mode>`;
498
493
  }
@@ -104,7 +104,12 @@ import { fastPolicyForModel } from "../../providers/service-tier";
104
104
  import { parseFastOnlyRowId } from "../fast-row";
105
105
  import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../../providers/openai-virtual-models";
106
106
  import { isUsageDebugEnabled } from "../../usage/debug";
107
- import { readJsonRequestBody, DecompressedBodyTooLargeError, UnsupportedContentEncodingError } from "../request-decompress";
107
+ import {
108
+ readJsonRequestBody,
109
+ resolveInboundBodyLimitBytes,
110
+ DecompressedBodyTooLargeError,
111
+ UnsupportedContentEncodingError,
112
+ } from "../request-decompress";
108
113
  import { resolveAdapter, resolveWireProtocolOverride } from "../adapter-resolve";
109
114
  import { hasKeyPoolFailover, rotateProviderTransportOn429 } from "../../providers/key-failover";
110
115
  import { shouldAttemptImageTierRetry } from "../image-retry";
@@ -522,7 +527,7 @@ export async function handleResponsesCompact(
522
527
  ): Promise<Response> {
523
528
  let body: unknown;
524
529
  try {
525
- body = await readJsonRequestBody(req);
530
+ body = await readJsonRequestBody(req, undefined, resolveInboundBodyLimitBytes(config.maxInboundBodyBytes));
526
531
  } catch (err) {
527
532
  return decodeRequestErrorResponse(err, "responses-compact");
528
533
  }
@@ -1015,6 +1020,7 @@ export async function handleResponsesCompact(
1015
1020
  upstream.headers,
1016
1021
  authCtx.writerGeneration,
1017
1022
  authCtx.kind === "main-pool" ? authCtx.mainQuotaWriter : undefined,
1023
+ { modelId: route.modelId },
1018
1024
  );
1019
1025
  }
1020
1026
  recordCompactPoolOutcome(authCtx, upstream.status, {
@@ -5,6 +5,17 @@ import type { AdapterEvent } from "../../types";
5
5
  export const PROVIDER_INPUT_TOO_LARGE_MESSAGE =
6
6
  "The provider rejected this turn because its input exceeds the provider size or context limit. Reduce the current input or compact the conversation before retrying.";
7
7
 
8
+ /** Preserve non-streaming HTTP failure semantics without exposing an upstream body. */
9
+ export function jsonContextOverflowResponse(): Response {
10
+ return Response.json({
11
+ error: {
12
+ message: PROVIDER_INPUT_TOO_LARGE_MESSAGE,
13
+ type: "invalid_request_error",
14
+ code: "context_length_exceeded",
15
+ },
16
+ }, { status: 413, headers: { "Cache-Control": "no-store" } });
17
+ }
18
+
8
19
  async function* contextOverflowEvents(): AsyncGenerator<AdapterEvent> {
9
20
  yield {
10
21
  type: "error",