@bitkyc08/opencodex 2.17.1-preview.20260814 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/{index-DUCH59lJ.css → index-CQ7bIKee.css} +1 -1
- package/gui/dist/assets/{index-ta3-_hgj.js → index-D_JUZLEC.js} +16 -16
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/client-fingerprint.ts +14 -10
- package/src/adapters/cursor/live-transport.ts +17 -5
- package/src/adapters/cursor/protobuf-events.ts +662 -20
- package/src/adapters/cursor/tool-definitions.ts +12 -6
- package/src/adapters/google-antigravity-wire.ts +4 -3
- package/src/adapters/google.ts +22 -3
- package/src/bridge.ts +12 -2
- package/src/chat/inbound.ts +24 -1
- package/src/cli/index.ts +11 -0
- package/src/codex/app-server-processes.ts +3 -3
- package/src/codex/shim.ts +100 -5
- package/src/codex/user-identity.ts +36 -6
- package/src/config.ts +0 -2
- package/src/generated/compatibility-version.json +52 -44
- package/src/lib/errors.ts +27 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/windows-elevation.ts +37 -0
- package/src/lib/windows-secret-acl.ts +7 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +0 -2
- package/src/oauth/index.ts +1 -1
- package/src/oauth/store.ts +32 -18
- package/src/providers/antigravity-models.ts +25 -5
- package/src/providers/free-directory.ts +1 -1
- package/src/providers/registry.ts +6 -3
- package/src/responses/spill-store.ts +20 -1
- package/src/responses/state.ts +159 -3
- package/src/server/chat-completions.ts +4 -2
- package/src/server/effort-policy.ts +18 -0
- package/src/server/index.ts +5 -1
- package/src/server/management/logs-usage-routes.ts +7 -22
- package/src/server/request-log.ts +48 -3
- package/src/server/responses/core.ts +59 -15
- package/src/server/responses/encrypted-payload.ts +58 -38
- package/src/server/responses/fetch-helpers.ts +12 -4
- package/src/server/responses/input-admission.ts +169 -0
- package/src/server/responses/policy-fallback.ts +13 -2
- package/src/server/responses/ws-upstream.ts +115 -6
- package/src/service-manager-probe.ts +23 -37
- package/src/service.ts +233 -25
- package/src/tray/windows.ts +0 -2
- package/src/types.ts +10 -2
- package/src/update/job.ts +2 -2
- package/src/usage/summary.ts +21 -4
- package/src/vision/index.ts +21 -4
- package/src/web-search/index.ts +2 -1
package/src/oauth/store.ts
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* Exceptions:
|
|
11
11
|
* - `chatgpt` stays single-slot (always replaced): codex-auth-api uses it as a scratch slot
|
|
12
12
|
* for Codex pool logins, which have their own ledger (codex-accounts.json).
|
|
13
|
-
* - Credentials without identity (no accountId/email) replace the active slot
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* extracts JWT `sub`
|
|
13
|
+
* - Credentials without identity (no accountId/email) replace the active slot on a normal
|
|
14
|
+
* login: their refresh tokens rotate, so a derived id would duplicate the same human on every
|
|
15
|
+
* re-login. An explicit add-account login instead preserves the prior slot and appends a
|
|
16
|
+
* distinct one. Kimi extracts JWT `user_id`/`sub` as accountId; Cursor extracts JWT `sub` —
|
|
17
|
+
* both append distinct identified accounts under multiauth.
|
|
17
18
|
*/
|
|
18
19
|
import { createHash, randomUUID } from "node:crypto";
|
|
19
20
|
import { chmodSync, closeSync, copyFileSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -300,6 +301,17 @@ function newAccountId(cred: OAuthCredentials): string {
|
|
|
300
301
|
return createHash("sha256").update(identity).digest("hex").slice(0, 32);
|
|
301
302
|
}
|
|
302
303
|
|
|
304
|
+
/** Allocate a persisted slot id without reusing any existing account's ownership key. */
|
|
305
|
+
function distinctAccountId(cred: OAuthCredentials, accounts: readonly ProviderAccount[]): string {
|
|
306
|
+
const base = newAccountId(cred);
|
|
307
|
+
const occupied = new Set(accounts.map(account => account.id));
|
|
308
|
+
if (!occupied.has(base)) return base;
|
|
309
|
+
for (let suffix = 1; ; suffix += 1) {
|
|
310
|
+
const candidate = `${base}-${suffix}`;
|
|
311
|
+
if (!occupied.has(candidate)) return candidate;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
303
315
|
function normalizeAccount(value: unknown): ProviderAccount | null {
|
|
304
316
|
if (!value || typeof value !== "object") return null;
|
|
305
317
|
const candidate = value as Partial<ProviderAccount>;
|
|
@@ -473,7 +485,8 @@ export function getCredential(provider: string): OAuthCredentials | null {
|
|
|
473
485
|
* Persist a credential as the ACTIVE account. Identity-matching (accountId ?? email) upserts
|
|
474
486
|
* the same human's slot; a new identity appends a new account. Credentials without identity
|
|
475
487
|
* (rotating refresh tokens would fabricate duplicates) and single-slot providers replace the
|
|
476
|
-
* active slot / whole set instead.
|
|
488
|
+
* active slot / whole set instead. An explicit add-account login can preserve the legacy slot;
|
|
489
|
+
* an identity-less credential then gets its deterministic refresh-derived account id.
|
|
477
490
|
*/
|
|
478
491
|
export async function saveCredential(
|
|
479
492
|
provider: string,
|
|
@@ -508,18 +521,24 @@ export async function saveCredential(
|
|
|
508
521
|
delete active.needsReauth;
|
|
509
522
|
return;
|
|
510
523
|
}
|
|
511
|
-
const id =
|
|
524
|
+
const id = distinctAccountId(safe, set.accounts);
|
|
525
|
+
set.accounts.push({ id, credential: safe, addedAt: Date.now() });
|
|
526
|
+
set.activeAccountId = id;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (opts.preserveIdentityless) {
|
|
530
|
+
const id = distinctAccountId(safe, set.accounts);
|
|
512
531
|
set.accounts.push({ id, credential: safe, addedAt: Date.now() });
|
|
513
532
|
set.activeAccountId = id;
|
|
514
533
|
return;
|
|
515
534
|
}
|
|
516
|
-
// No identity: replace the active slot in place
|
|
535
|
+
// No identity during a normal login: replace the active slot in place.
|
|
517
536
|
const active = set.accounts.find(a => a.id === set.activeAccountId);
|
|
518
537
|
if (active) {
|
|
519
538
|
active.credential = safe;
|
|
520
539
|
delete active.needsReauth;
|
|
521
540
|
} else {
|
|
522
|
-
const id =
|
|
541
|
+
const id = distinctAccountId(safe, set.accounts);
|
|
523
542
|
set.accounts.push({ id, credential: safe, addedAt: Date.now() });
|
|
524
543
|
set.activeAccountId = id;
|
|
525
544
|
}
|
|
@@ -541,15 +560,7 @@ export async function upsertCredentialByIdentity(
|
|
|
541
560
|
}
|
|
542
561
|
return await mutateStore(store => {
|
|
543
562
|
const set = store[provider];
|
|
544
|
-
const
|
|
545
|
-
if (safe.accountId) {
|
|
546
|
-
if (account.credential.accountId) return account.credential.accountId === safe.accountId;
|
|
547
|
-
return Boolean(
|
|
548
|
-
safe.email
|
|
549
|
-
&& account.credential.email
|
|
550
|
-
&& account.credential.email.toLowerCase() === safe.email.toLowerCase(),
|
|
551
|
-
);
|
|
552
|
-
}
|
|
563
|
+
const matchesEmailOnly = (account: ProviderAccount): boolean => {
|
|
553
564
|
if (account.credential.accountId) return false;
|
|
554
565
|
return Boolean(
|
|
555
566
|
safe.email
|
|
@@ -557,7 +568,10 @@ export async function upsertCredentialByIdentity(
|
|
|
557
568
|
&& account.credential.email.toLowerCase() === safe.email.toLowerCase(),
|
|
558
569
|
);
|
|
559
570
|
};
|
|
560
|
-
const existing =
|
|
571
|
+
const existing = safe.accountId
|
|
572
|
+
? set?.accounts.find(account => account.credential.accountId === safe.accountId)
|
|
573
|
+
?? set?.accounts.find(matchesEmailOnly)
|
|
574
|
+
: set?.accounts.find(matchesEmailOnly);
|
|
561
575
|
if (existing && set) {
|
|
562
576
|
existing.credential = safe;
|
|
563
577
|
delete existing.needsReauth;
|
|
@@ -14,6 +14,13 @@ import { isValidModelDiscoveryModelId, MODEL_DISCOVERY_MAX_MODELS } from "./mode
|
|
|
14
14
|
/** Current Antigravity Flash generation. */
|
|
15
15
|
const GEMINI_FLASH_CURRENT = "gemini-3.7-flash";
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Wire ID that CCA actually accepts for the current Flash generation.
|
|
19
|
+
* Google renamed the model to include a `-tiered` suffix; the picker-visible
|
|
20
|
+
* ID stays `gemini-3.7-flash` (stripped by `pickerModelIdForDiscoveredWireId`).
|
|
21
|
+
*/
|
|
22
|
+
const GEMINI_FLASH_WIRE_ID = "gemini-3.7-flash-tiered";
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* Retired Flash ids → the reasoning tier they used to encode.
|
|
19
26
|
*
|
|
@@ -42,7 +49,7 @@ const RETIRED_FLASH_TIERS: Record<string, string> = {
|
|
|
42
49
|
};
|
|
43
50
|
|
|
44
51
|
const ANTIGRAVITY_WIRE_MODELS = [
|
|
45
|
-
"gemini-3.7-flash",
|
|
52
|
+
"gemini-3.7-flash-tiered",
|
|
46
53
|
"gemini-3.1-pro-low",
|
|
47
54
|
"gemini-pro-agent",
|
|
48
55
|
"gemini-3.1-flash-image",
|
|
@@ -133,6 +140,19 @@ const ANTIGRAVITY_THINKING_LEVEL_MODELS: Record<string, string> = {
|
|
|
133
140
|
// Flash generation, where it is an error rather than a quieter tier.
|
|
134
141
|
const ANTIGRAVITY_THINKING_LEVELS = new Set(["low", "medium", "high"]);
|
|
135
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Picker-visible model IDs whose CCA wire ID differs (the `-tiered` rename).
|
|
145
|
+
* Models not listed here use themselves as the wire ID.
|
|
146
|
+
*/
|
|
147
|
+
const ANTIGRAVITY_PICKER_TO_WIRE: Record<string, string> = {
|
|
148
|
+
"gemini-3.7-flash": GEMINI_FLASH_WIRE_ID,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** Map a picker-visible base model to its CCA wire ID. Identity when no mapping exists. */
|
|
152
|
+
function pickerToWireId(pickerId: string): string {
|
|
153
|
+
return ANTIGRAVITY_PICKER_TO_WIRE[pickerId] ?? pickerId;
|
|
154
|
+
}
|
|
155
|
+
|
|
136
156
|
function resolveAntigravityThinkingLevel(effort: string): string | undefined {
|
|
137
157
|
if (effort === "xhigh" || effort === "max" || effort === "ultra") return "high";
|
|
138
158
|
return ANTIGRAVITY_THINKING_LEVELS.has(effort) ? effort : undefined;
|
|
@@ -157,7 +177,7 @@ const ANTIGRAVITY_COMPATIBILITY_MODEL_ALIASES: Record<string, string> = {
|
|
|
157
177
|
// because `parseAntigravityAvailableModels` uses THIS map to keep a stale CCA
|
|
158
178
|
// payload from republishing a dead wire id as a picker row.
|
|
159
179
|
...Object.fromEntries(
|
|
160
|
-
Object.keys(RETIRED_FLASH_TIERS).map(retired => [retired,
|
|
180
|
+
Object.keys(RETIRED_FLASH_TIERS).map(retired => [retired, GEMINI_FLASH_WIRE_ID]),
|
|
161
181
|
),
|
|
162
182
|
};
|
|
163
183
|
|
|
@@ -182,7 +202,7 @@ function isKnownAntigravityPickerModelId(value: string): boolean {
|
|
|
182
202
|
|
|
183
203
|
// Context windows from the upstream `:fetchAvailableModels` maxTokens per model.
|
|
184
204
|
const ANTIGRAVITY_WIRE_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
185
|
-
"gemini-3.7-flash": 1_048_576,
|
|
205
|
+
"gemini-3.7-flash-tiered": 1_048_576,
|
|
186
206
|
"gemini-3.1-pro-low": 1_048_576,
|
|
187
207
|
"gemini-pro-agent": 1_048_576,
|
|
188
208
|
"gemini-3.1-flash-image": 1_048_576,
|
|
@@ -357,7 +377,7 @@ export function resolveAntigravityEffortWireModel(
|
|
|
357
377
|
const retiredTier = retiredAntigravityFlashTier(modelId);
|
|
358
378
|
if (retiredTier) {
|
|
359
379
|
return {
|
|
360
|
-
wireModelId:
|
|
380
|
+
wireModelId: GEMINI_FLASH_WIRE_ID,
|
|
361
381
|
thinkingLevel: effort ? resolveAntigravityThinkingLevel(effort) ?? retiredTier : retiredTier,
|
|
362
382
|
};
|
|
363
383
|
}
|
|
@@ -372,7 +392,7 @@ export function resolveAntigravityEffortWireModel(
|
|
|
372
392
|
const defaultLevel = ANTIGRAVITY_THINKING_LEVEL_MODELS[modelId];
|
|
373
393
|
if (defaultLevel) {
|
|
374
394
|
return {
|
|
375
|
-
wireModelId: modelId,
|
|
395
|
+
wireModelId: pickerToWireId(modelId),
|
|
376
396
|
thinkingLevel: effort ? resolveAntigravityThinkingLevel(effort) ?? defaultLevel : defaultLevel,
|
|
377
397
|
};
|
|
378
398
|
}
|
|
@@ -82,7 +82,7 @@ const CONNECTABLE: Record<string, ConnectableOverride> = {
|
|
|
82
82
|
"cloudflare-ai": openAi("https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1", "https://dash.cloudflare.com/?to=/:account/ai/workers-ai", { supportLevel: "supported", verification: "official", documentationUrl: "https://developers.cloudflare.com/workers-ai/configuration/open-ai-compatibility/", discovery: "static", liveModels: false, models: ["@cf/meta/llama-3.3-70b-instruct-fp8-fast", "@cf/qwen/qwq-32b"] }),
|
|
83
83
|
cohere: openAi("https://api.cohere.com/compatibility/v1", "https://dashboard.cohere.com/api-keys", { supportLevel: "supported", verification: "official", documentationUrl: "https://docs.cohere.com/reference/list-models", modelsUrl: "https://api.cohere.com/compatibility/v1/models" }),
|
|
84
84
|
friendliai: openAi("https://api.friendli.ai/serverless/v1", "https://suite.friendli.ai", { modelsUrl: "https://api.friendli.ai/serverless/v1/models" }),
|
|
85
|
-
gemini: { baseUrl: "https://generativelanguage.googleapis.com", dashboardUrl: "https://aistudio.google.com/apikey", adapter: "google", authKind: "key", supportLevel: "supported", verification: "official", documentationUrl: "https://ai.google.dev/api/models", lastVerified: LAST_VERIFIED, discovery: "live", liveModels: true, googleMode: "ai-studio", models: ["gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview"] },
|
|
85
|
+
gemini: { baseUrl: "https://generativelanguage.googleapis.com", dashboardUrl: "https://aistudio.google.com/apikey", adapter: "google", authKind: "key", supportLevel: "supported", verification: "official", documentationUrl: "https://ai.google.dev/api/models", lastVerified: LAST_VERIFIED, discovery: "live", liveModels: true, googleMode: "ai-studio", models: ["gemini-3.7-flash", "gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview"] },
|
|
86
86
|
"github-models": openAi("https://models.github.ai/inference", "https://github.com/settings/tokens", { supportLevel: "supported", verification: "official", documentationUrl: "https://docs.github.com/en/github-models/prototyping-with-ai-models", discovery: "static", liveModels: false, models: ["openai/gpt-4.1", "meta/llama-4-scout-17b-16e-instruct"] }),
|
|
87
87
|
groq: openAi("https://api.groq.com/openai/v1", "https://console.groq.com/keys", { supportLevel: "supported", verification: "official", documentationUrl: "https://console.groq.com/docs/api-reference#models" }),
|
|
88
88
|
hackclub: openAi("https://ai.hackclub.com/proxy/v1", "https://ai.hackclub.com", { modelsUrl: "https://ai.hackclub.com/proxy/v1/models" }),
|
|
@@ -1438,12 +1438,13 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1438
1438
|
// devlog/_plan/260710_provider_hardening/001_research_frontier.md.
|
|
1439
1439
|
{
|
|
1440
1440
|
id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true,
|
|
1441
|
-
dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview"],
|
|
1442
|
-
modelContextWindows: { "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000, "gemini-3.5-flash-lite": 1_048_576 },
|
|
1443
|
-
modelInputModalities: { "gemini-3.6-flash": ["text", "image"], "gemini-3.5-flash-lite": ["text", "image"] },
|
|
1441
|
+
dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview", "gemini-3.7-flash"],
|
|
1442
|
+
modelContextWindows: { "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000, "gemini-3.5-flash-lite": 1_048_576, "gemini-3.7-flash": 1_048_576 },
|
|
1443
|
+
modelInputModalities: { "gemini-3.6-flash": ["text", "image"], "gemini-3.5-flash-lite": ["text", "image"], "gemini-3.7-flash": ["text", "image"] },
|
|
1444
1444
|
modelReasoningEfforts: {
|
|
1445
1445
|
"gemini-3.6-flash": ["minimal", "low", "medium", "high"],
|
|
1446
1446
|
"gemini-3.5-flash": ["minimal", "low", "medium", "high"],
|
|
1447
|
+
"gemini-3.7-flash": ["minimal", "low", "medium", "high"],
|
|
1447
1448
|
"gemini-3.1-pro-preview": ["low", "medium", "high"],
|
|
1448
1449
|
},
|
|
1449
1450
|
jawcodeBundle: "google", extraMetadataAliases: ["gemini"],
|
|
@@ -2419,6 +2420,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
2419
2420
|
dashboardUrl: "https://xiaomimimo.com",
|
|
2420
2421
|
defaultModel: "mimo-auto",
|
|
2421
2422
|
models: ["mimo-auto"],
|
|
2423
|
+
reasoningEfforts: ["low", "medium", "high"],
|
|
2424
|
+
reasoningEffortMap: { xhigh: "high", max: "high", ultra: "high" },
|
|
2422
2425
|
note: "No key needed — uses Xiaomi MiMo's free public tier (limited-time offer). A JWT is bootstrapped automatically with an anonymous random client id stored locally. The endpoint contract mirrors the official MiMoCode client and is not publicly documented — Xiaomi may change or restrict it at any time. Prompts may be processed/retained by Xiaomi; do not send confidential material.",
|
|
2423
2426
|
},
|
|
2424
2427
|
// Xiaomi MiMo paid token plan. Separate host and wire from both `xiaomi` (Anthropic) and
|
|
@@ -36,6 +36,17 @@ export interface ResponseSpillPayload {
|
|
|
36
36
|
createdAt: number;
|
|
37
37
|
clientThreadId?: string;
|
|
38
38
|
items: unknown[];
|
|
39
|
+
/**
|
|
40
|
+
* Index in `items` where the provider output begins, used by replay-overlap detection
|
|
41
|
+
* in state.ts. Optional so a payload written before this field still loads (it simply
|
|
42
|
+
* never authorizes a skip).
|
|
43
|
+
*
|
|
44
|
+
* Compatibility is FORWARD-ONLY: `validPayload` is a strict key allowlist, so a build
|
|
45
|
+
* predating this field rejects a payload carrying it as corrupt rather than ignoring
|
|
46
|
+
* it. Rolling back across this change invalidates spilled entries, which degrades to a
|
|
47
|
+
* replay miss — an already-handled path — not to corrupted live state.
|
|
48
|
+
*/
|
|
49
|
+
providerOutputStart?: number;
|
|
39
50
|
providers?: OcxProviderContinuationState;
|
|
40
51
|
}
|
|
41
52
|
|
|
@@ -261,12 +272,19 @@ function validPayload(value: unknown, responseId: string): value is ResponseSpil
|
|
|
261
272
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
262
273
|
const payload = value as Record<string, unknown>;
|
|
263
274
|
const keys = Object.keys(payload);
|
|
264
|
-
if (keys.some(key => !["version", "responseId", "createdAt", "clientThreadId", "items", "providers"].includes(key))) return false;
|
|
275
|
+
if (keys.some(key => !["version", "responseId", "createdAt", "clientThreadId", "items", "providerOutputStart", "providers"].includes(key))) return false;
|
|
265
276
|
if (payload.version !== 1 || payload.responseId !== responseId) return false;
|
|
266
277
|
if (typeof payload.createdAt !== "number" || !Number.isFinite(payload.createdAt)) return false;
|
|
267
278
|
if (payload.clientThreadId !== undefined
|
|
268
279
|
&& (typeof payload.clientThreadId !== "string" || payload.clientThreadId.trim().length === 0)) return false;
|
|
269
280
|
if (!Array.isArray(payload.items)) return false;
|
|
281
|
+
// A malformed boundary must degrade to "never skip", never to a bad index: reject the
|
|
282
|
+
// payload outright so materialization treats it as corrupt rather than trusting it.
|
|
283
|
+
if (payload.providerOutputStart !== undefined) {
|
|
284
|
+
const anchor = payload.providerOutputStart;
|
|
285
|
+
if (typeof anchor !== "number" || !Number.isSafeInteger(anchor)
|
|
286
|
+
|| anchor < 0 || anchor > payload.items.length) return false;
|
|
287
|
+
}
|
|
270
288
|
if (payload.providers !== undefined) {
|
|
271
289
|
if (!payload.providers || typeof payload.providers !== "object" || Array.isArray(payload.providers)) return false;
|
|
272
290
|
for (const providerState of Object.values(payload.providers)) {
|
|
@@ -289,6 +307,7 @@ export function writeResponseSpillDurably(
|
|
|
289
307
|
createdAt: state.createdAt,
|
|
290
308
|
...(state.clientThreadId ? { clientThreadId: state.clientThreadId } : {}),
|
|
291
309
|
items: state.items,
|
|
310
|
+
...(state.providerOutputStart !== undefined ? { providerOutputStart: state.providerOutputStart } : {}),
|
|
292
311
|
...(state.providers ? { providers: state.providers } : {}),
|
|
293
312
|
};
|
|
294
313
|
const serialized = JSON.stringify(payload);
|
package/src/responses/state.ts
CHANGED
|
@@ -39,6 +39,8 @@ interface ResidentResponseState {
|
|
|
39
39
|
createdAt: number;
|
|
40
40
|
clientThreadId?: string;
|
|
41
41
|
items: unknown[];
|
|
42
|
+
/** Index in `items` where provider output begins; see clientCarriedPrefixLength. */
|
|
43
|
+
providerOutputStart?: number;
|
|
42
44
|
providers?: OcxProviderContinuationState;
|
|
43
45
|
sizeBytes: number;
|
|
44
46
|
}
|
|
@@ -47,6 +49,8 @@ interface SpilledResponseState {
|
|
|
47
49
|
kind: "spill";
|
|
48
50
|
createdAt: number;
|
|
49
51
|
clientThreadId?: string;
|
|
52
|
+
/** Mirrors the spilled payload boundary so a spilled entry keeps its anchor. */
|
|
53
|
+
providerOutputStart?: number;
|
|
50
54
|
providers?: OcxProviderContinuationState;
|
|
51
55
|
spill: ResponseSpillRef;
|
|
52
56
|
sizeBytes: number;
|
|
@@ -130,6 +134,7 @@ function measureResidentEntry(id: string, entry: ResidentInput): ResidentRespons
|
|
|
130
134
|
createdAt: entry.createdAt,
|
|
131
135
|
...(entry.clientThreadId ? { clientThreadId: entry.clientThreadId } : {}),
|
|
132
136
|
items: entry.items,
|
|
137
|
+
...(entry.providerOutputStart !== undefined ? { providerOutputStart: entry.providerOutputStart } : {}),
|
|
133
138
|
...(entry.providers ? { providers: entry.providers } : {}),
|
|
134
139
|
});
|
|
135
140
|
return sizeBytes === null ? null : { kind: "resident", ...entry, sizeBytes };
|
|
@@ -252,12 +257,14 @@ function replaceSpillEntryAtomically(
|
|
|
252
257
|
createdAt: candidate.createdAt,
|
|
253
258
|
...(candidate.clientThreadId ? { clientThreadId: candidate.clientThreadId } : {}),
|
|
254
259
|
items: candidate.items,
|
|
260
|
+
...(candidate.providerOutputStart !== undefined ? { providerOutputStart: candidate.providerOutputStart } : {}),
|
|
255
261
|
...(candidate.providers ? { providers: candidate.providers } : {}),
|
|
256
262
|
});
|
|
257
263
|
const base: Omit<SpilledResponseState, "sizeBytes"> = {
|
|
258
264
|
kind: "spill",
|
|
259
265
|
createdAt: candidate.createdAt,
|
|
260
266
|
...(candidate.clientThreadId ? { clientThreadId: candidate.clientThreadId } : {}),
|
|
267
|
+
...(candidate.providerOutputStart !== undefined ? { providerOutputStart: candidate.providerOutputStart } : {}),
|
|
261
268
|
...(candidate.providers ? { providers: candidate.providers } : {}),
|
|
262
269
|
spill: ref,
|
|
263
270
|
};
|
|
@@ -332,6 +339,7 @@ function admitOversizedCandidate(
|
|
|
332
339
|
createdAt: candidate.createdAt,
|
|
333
340
|
...(candidate.clientThreadId ? { clientThreadId: candidate.clientThreadId } : {}),
|
|
334
341
|
items: candidate.items,
|
|
342
|
+
...(candidate.providerOutputStart !== undefined ? { providerOutputStart: candidate.providerOutputStart } : {}),
|
|
335
343
|
...(candidate.providers ? { providers: candidate.providers } : {}),
|
|
336
344
|
});
|
|
337
345
|
// Enforce the ceiling against the REAL envelope: the spill payload adds
|
|
@@ -373,9 +381,11 @@ function admitOversizedCandidate(
|
|
|
373
381
|
}
|
|
374
382
|
}
|
|
375
383
|
|
|
376
|
-
//
|
|
384
|
+
// Replay provenance must stay proxy-private: a WeakMap distinguishes replayed history from the
|
|
377
385
|
// newly appended input suffix without adding an unknown field that native passthrough could send
|
|
378
|
-
// upstream. The parser uses this boundary to acknowledge historical compaction markers exactly
|
|
386
|
+
// upstream. The parser uses this boundary to acknowledge historical compaction markers exactly
|
|
387
|
+
// once. It records the boundary whether the proxy prepended the history or the client already
|
|
388
|
+
// carried it — the boundary is the same either way, and only its provenance differs.
|
|
379
389
|
const replayedInputPrefixLengths = new WeakMap<object, number>();
|
|
380
390
|
const replayFailures = new WeakMap<object, PreviousResponseReplayFailure>();
|
|
381
391
|
let loaded = false;
|
|
@@ -419,12 +429,23 @@ function loadSnapshotEntry(id: string, value: unknown): void {
|
|
|
419
429
|
const clientThreadId = typeof rec.clientThreadId === "string" && rec.clientThreadId.trim().length > 0
|
|
420
430
|
? rec.clientThreadId.trim()
|
|
421
431
|
: undefined;
|
|
432
|
+
// A malformed boundary degrades to "never skip" rather than to a bad index: an untrusted
|
|
433
|
+
// snapshot must not be able to authorize dropping conversation history.
|
|
434
|
+
const anchorFor = (itemCount: number): number | undefined => {
|
|
435
|
+
const raw = (rec as { providerOutputStart?: unknown }).providerOutputStart;
|
|
436
|
+
return Number.isSafeInteger(raw) && (raw as number) >= 0 && (raw as number) <= itemCount
|
|
437
|
+
? raw as number
|
|
438
|
+
: undefined;
|
|
439
|
+
};
|
|
422
440
|
if (rec.kind === "spill") {
|
|
423
441
|
if (!isSpillRef(rec.spill)) return;
|
|
424
442
|
const base: Omit<SpilledResponseState, "sizeBytes"> = {
|
|
425
443
|
kind: "spill",
|
|
426
444
|
createdAt: rec.createdAt,
|
|
427
445
|
...(clientThreadId ? { clientThreadId } : {}),
|
|
446
|
+
// Item count is unknown until materialization, so accept any non-negative integer
|
|
447
|
+
// here; the spill payload validator re-checks it against the real array.
|
|
448
|
+
...(anchorFor(Number.MAX_SAFE_INTEGER) !== undefined ? { providerOutputStart: anchorFor(Number.MAX_SAFE_INTEGER) } : {}),
|
|
428
449
|
...(rec.providers ? { providers: rec.providers } : {}),
|
|
429
450
|
spill: rec.spill,
|
|
430
451
|
};
|
|
@@ -451,6 +472,7 @@ function loadSnapshotEntry(id: string, value: unknown): void {
|
|
|
451
472
|
createdAt: rec.createdAt,
|
|
452
473
|
...(clientThreadId ? { clientThreadId } : {}),
|
|
453
474
|
items: rec.items,
|
|
475
|
+
...(anchorFor(rec.items.length) !== undefined ? { providerOutputStart: anchorFor(rec.items.length) } : {}),
|
|
454
476
|
...(providers ? { providers } : {}),
|
|
455
477
|
});
|
|
456
478
|
if (!resident) {
|
|
@@ -740,6 +762,94 @@ function inputItems(input: unknown): unknown[] {
|
|
|
740
762
|
return [input];
|
|
741
763
|
}
|
|
742
764
|
|
|
765
|
+
/** Hard cap for canonicalizing ANY item. Past it, the item is not comparable. */
|
|
766
|
+
const REPLAY_FINGERPRINT_MAX_BYTES = 8 * 1024;
|
|
767
|
+
/** Depth ceiling so a pathologically nested item cannot blow the canonicalizer. */
|
|
768
|
+
const REPLAY_FINGERPRINT_MAX_DEPTH = 64;
|
|
769
|
+
|
|
770
|
+
let replayOverlapSkips = 0;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Canonical, order-stable fingerprint for one input item, or null when the item cannot be
|
|
774
|
+
* compared safely.
|
|
775
|
+
*
|
|
776
|
+
* Byte-counted DURING the walk rather than serialize-then-measure: a tool result can be
|
|
777
|
+
* megabytes and this runs on the request path, so the point of the cap is to stop early,
|
|
778
|
+
* not to discover afterwards that we should have. Object keys are sorted so two
|
|
779
|
+
* semantically identical items cannot differ by key order alone.
|
|
780
|
+
*
|
|
781
|
+
* The cap applies to EVERY item. An `id`/`call_id` is additional occurrence evidence, never
|
|
782
|
+
* a substitute for content equality, so an over-cap identified tool item is non-comparable
|
|
783
|
+
* exactly like an over-cap message.
|
|
784
|
+
*/
|
|
785
|
+
function replayItemFingerprint(item: unknown): string | null {
|
|
786
|
+
const out: string[] = [];
|
|
787
|
+
let bytes = 0;
|
|
788
|
+
const push = (text: string): boolean => {
|
|
789
|
+
bytes += Buffer.byteLength(text, "utf8");
|
|
790
|
+
if (bytes > REPLAY_FINGERPRINT_MAX_BYTES) return false;
|
|
791
|
+
out.push(text);
|
|
792
|
+
return true;
|
|
793
|
+
};
|
|
794
|
+
const walk = (value: unknown, depth: number): boolean => {
|
|
795
|
+
if (depth > REPLAY_FINGERPRINT_MAX_DEPTH) return false;
|
|
796
|
+
if (value === null || typeof value !== "object") return push(JSON.stringify(value) ?? "null");
|
|
797
|
+
if (Array.isArray(value)) {
|
|
798
|
+
if (!push("[")) return false;
|
|
799
|
+
for (const element of value) {
|
|
800
|
+
if (!walk(element, depth + 1)) return false;
|
|
801
|
+
if (!push(",")) return false;
|
|
802
|
+
}
|
|
803
|
+
return push("]");
|
|
804
|
+
}
|
|
805
|
+
if (!push("{")) return false;
|
|
806
|
+
for (const key of Object.keys(value as Record<string, unknown>).sort()) {
|
|
807
|
+
if (!push(JSON.stringify(key))) return false;
|
|
808
|
+
if (!walk((value as Record<string, unknown>)[key], depth + 1)) return false;
|
|
809
|
+
if (!push(",")) return false;
|
|
810
|
+
}
|
|
811
|
+
return push("}");
|
|
812
|
+
};
|
|
813
|
+
return walk(item, 0) ? out.join("") : null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/** Non-empty provider-issued `id`/`call_id` on an item, else null. */
|
|
817
|
+
function providerIssuedIdentity(item: unknown): string | null {
|
|
818
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
819
|
+
const record = item as { id?: unknown; call_id?: unknown };
|
|
820
|
+
for (const candidate of [record.id, record.call_id]) {
|
|
821
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) return candidate;
|
|
822
|
+
}
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Number of leading stored items the client already carries verbatim, or 0.
|
|
828
|
+
*
|
|
829
|
+
* Requires an exact ordered run: every stored item must match the client input item at the
|
|
830
|
+
* same index. Any not-comparable item aborts to 0 — skipping just that item could align two
|
|
831
|
+
* different occurrences and manufacture a false positive, and a false positive here deletes
|
|
832
|
+
* real conversation history.
|
|
833
|
+
*
|
|
834
|
+
* Known gap (FU-2): stored input can contain proxy-injected guidance the client never saw,
|
|
835
|
+
* and ids repaired after recording. Those sessions do not match here and expand as before.
|
|
836
|
+
*/
|
|
837
|
+
function clientCarriedPrefixLength(stored: readonly unknown[], clientInput: readonly unknown[]): number {
|
|
838
|
+
if (stored.length === 0 || clientInput.length < stored.length) return 0;
|
|
839
|
+
for (let index = 0; index < stored.length; index += 1) {
|
|
840
|
+
const storedPrint = replayItemFingerprint(stored[index]);
|
|
841
|
+
if (storedPrint === null) return 0;
|
|
842
|
+
const clientPrint = replayItemFingerprint(clientInput[index]);
|
|
843
|
+
if (clientPrint === null || storedPrint !== clientPrint) return 0;
|
|
844
|
+
}
|
|
845
|
+
return stored.length;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** Test-only: replay prepends skipped because the client already carried the history. */
|
|
849
|
+
export function replayOverlapSkipsForTests(): number {
|
|
850
|
+
return replayOverlapSkips;
|
|
851
|
+
}
|
|
852
|
+
|
|
743
853
|
function pruneResponses(at = now()): void {
|
|
744
854
|
for (const [id, state] of states) {
|
|
745
855
|
if (at - state.createdAt > RESPONSE_TTL_MS) deleteEntry(id);
|
|
@@ -765,6 +875,7 @@ function pruneResponses(at = now()): void {
|
|
|
765
875
|
createdAt: entry.createdAt,
|
|
766
876
|
...(entry.clientThreadId ? { clientThreadId: entry.clientThreadId } : {}),
|
|
767
877
|
items: entry.items,
|
|
878
|
+
...(entry.providerOutputStart !== undefined ? { providerOutputStart: entry.providerOutputStart } : {}),
|
|
768
879
|
...(entry.providers ? { providers: entry.providers } : {}),
|
|
769
880
|
});
|
|
770
881
|
if (swapResidentForSpill(oldestId, entry, ref)) spillCounters.writes += 1;
|
|
@@ -807,6 +918,7 @@ export function evictOldestResponseContinuationForBudget(): number {
|
|
|
807
918
|
createdAt: entry.createdAt,
|
|
808
919
|
...(entry.clientThreadId ? { clientThreadId: entry.clientThreadId } : {}),
|
|
809
920
|
items: entry.items,
|
|
921
|
+
...(entry.providerOutputStart !== undefined ? { providerOutputStart: entry.providerOutputStart } : {}),
|
|
810
922
|
...(entry.providers ? { providers: entry.providers } : {}),
|
|
811
923
|
});
|
|
812
924
|
if (swapResidentForSpill(id, entry, ref)) spillCounters.writes += 1;
|
|
@@ -848,6 +960,9 @@ function materializeEntry(
|
|
|
848
960
|
createdAt: result.payload.createdAt,
|
|
849
961
|
...(result.payload.clientThreadId ? { clientThreadId: result.payload.clientThreadId } : {}),
|
|
850
962
|
items: result.payload.items,
|
|
963
|
+
...(result.payload.providerOutputStart !== undefined
|
|
964
|
+
? { providerOutputStart: result.payload.providerOutputStart }
|
|
965
|
+
: {}),
|
|
851
966
|
...(result.payload.providers ? { providers: result.payload.providers } : {}),
|
|
852
967
|
});
|
|
853
968
|
if (!state) {
|
|
@@ -892,6 +1007,39 @@ export function expandPreviousResponseInput(body: unknown, clientThreadId?: stri
|
|
|
892
1007
|
replayScopeMismatchDrops += 1;
|
|
893
1008
|
return freshRequest;
|
|
894
1009
|
}
|
|
1010
|
+
// The client already replayed this history verbatim. Prepending the stored copy would
|
|
1011
|
+
// double it, and the doubled turn is stored again, so the next turn triples (#1412 saw
|
|
1012
|
+
// 127k of real context reach 1.3M tokens this way).
|
|
1013
|
+
//
|
|
1014
|
+
// Three conditions, all required. The run must cover the whole stored entry; it must reach
|
|
1015
|
+
// the provider-output region; and some matched item in that region must carry a
|
|
1016
|
+
// provider-issued id. The last one is the load-bearing part: content equality alone proves
|
|
1017
|
+
// two items look alike, not that they are the same occurrence, so a client that merely
|
|
1018
|
+
// repeats its own message would otherwise authorize a skip that deletes real history.
|
|
1019
|
+
// There is no invariant that provider output always carries ids, so an entry whose output
|
|
1020
|
+
// has none simply never skips.
|
|
1021
|
+
{
|
|
1022
|
+
const clientInput = inputItems(request.input);
|
|
1023
|
+
const stored = materialized.state.items;
|
|
1024
|
+
const anchor = materialized.state.providerOutputStart;
|
|
1025
|
+
const carried = clientCarriedPrefixLength(stored, clientInput);
|
|
1026
|
+
if (
|
|
1027
|
+
carried === stored.length
|
|
1028
|
+
&& anchor !== undefined
|
|
1029
|
+
&& carried > anchor
|
|
1030
|
+
&& stored.slice(anchor, carried).some(item => providerIssuedIdentity(item) !== null)
|
|
1031
|
+
) {
|
|
1032
|
+
replayOverlapSkips += 1;
|
|
1033
|
+
// Keep previous_response_id: Kiro and Cursor recover their conversation ids from it
|
|
1034
|
+
// (kiro-wire.ts, cursor/request-builder.ts). Only the concatenation is skipped.
|
|
1035
|
+
const unchanged = { ...request };
|
|
1036
|
+
// Same provenance boundary a real expansion would record, so the replayed prefix does
|
|
1037
|
+
// not re-acknowledge historical compaction markers (parser.ts) and stays visible to
|
|
1038
|
+
// guidance de-duplication (collaboration.ts).
|
|
1039
|
+
replayedInputPrefixLengths.set(unchanged, carried);
|
|
1040
|
+
return unchanged;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
895
1043
|
const expanded = {
|
|
896
1044
|
...request,
|
|
897
1045
|
input: [...materialized.state.items, ...inputItems(request.input)],
|
|
@@ -1044,10 +1192,17 @@ export function rememberResponseState(
|
|
|
1044
1192
|
});
|
|
1045
1193
|
}
|
|
1046
1194
|
const clientThreadId = normalizedClientThreadId(opts?.clientThreadId);
|
|
1195
|
+
// Compute the normalized array once and reuse it for both fields, so the recorded
|
|
1196
|
+
// boundary can never disagree with the items it indexes.
|
|
1197
|
+
const requestItems = inputItems(request.input);
|
|
1047
1198
|
setResidentEntry(response.id, {
|
|
1048
1199
|
createdAt: now(),
|
|
1049
1200
|
...(clientThreadId ? { clientThreadId } : {}),
|
|
1050
|
-
items: [...
|
|
1201
|
+
items: [...requestItems, ...response.output],
|
|
1202
|
+
// Where response.output begins. A replay skip requires a matched item at or past this
|
|
1203
|
+
// index that also carries a provider-issued id — position alone proves only that an item
|
|
1204
|
+
// sits on the provider side, not that the provider authored it.
|
|
1205
|
+
providerOutputStart: requestItems.length,
|
|
1051
1206
|
// Always preserve the Cursor conversation id so the next tool-result turn can continue the SAME
|
|
1052
1207
|
// Cursor conversation (multi-turn continuation). Separately track whether Cursor's own
|
|
1053
1208
|
// checkpoint/cache is safe to reuse: a turn that ended with a pending client tool call produced an
|
|
@@ -1093,6 +1248,7 @@ export function clearResponseStateMemoryForTests(): void {
|
|
|
1093
1248
|
spillCounters.writeFailures = 0;
|
|
1094
1249
|
spillCounters.readFailures = 0;
|
|
1095
1250
|
replayScopeMismatchDrops = 0;
|
|
1251
|
+
replayOverlapSkips = 0;
|
|
1096
1252
|
persistAttemptHookForTests = null;
|
|
1097
1253
|
loaded = false;
|
|
1098
1254
|
}
|
|
@@ -140,9 +140,11 @@ async function handleChatCompletionsWithBudget(
|
|
|
140
140
|
logCtx.usageLogInputTokens = Math.max(1, estimateTokens(parts.join("\n"), requestedModel));
|
|
141
141
|
}
|
|
142
142
|
if (internalBody.reasoning !== undefined) {
|
|
143
|
-
const { supportedLadderFor } = await import("./effort-policy");
|
|
143
|
+
const { stripEmptyLadderEffort, supportedLadderFor } = await import("./effort-policy");
|
|
144
144
|
const ladder = supportedLadderFor({ provider: route.provider, modelId: route.modelId });
|
|
145
|
-
|
|
145
|
+
const next = stripEmptyLadderEffort(internalBody.reasoning, ladder);
|
|
146
|
+
if (next === undefined) delete internalBody.reasoning;
|
|
147
|
+
else internalBody.reasoning = next;
|
|
146
148
|
}
|
|
147
149
|
} catch (err) {
|
|
148
150
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
@@ -98,6 +98,24 @@ export function effortCapAppliesTo(
|
|
|
98
98
|
* validates against that backend. A custom responses provider (key mode) serving a
|
|
99
99
|
* native-looking bare id must NOT inherit the unrelated native ladder.
|
|
100
100
|
*/
|
|
101
|
+
/**
|
|
102
|
+
* Empty ladders mean "no effort control", not "this model cannot emit reasoning".
|
|
103
|
+
* Drop only `effort` so a Chat Completions `include_reasoning` / `reasoning.summary`
|
|
104
|
+
* request still reaches parseRequest and is not hidden by hideThinkingSummary.
|
|
105
|
+
*/
|
|
106
|
+
export function stripEmptyLadderEffort(
|
|
107
|
+
reasoning: unknown,
|
|
108
|
+
ladder: readonly string[] | undefined,
|
|
109
|
+
): unknown {
|
|
110
|
+
if (ladder === undefined || ladder.length > 0) return reasoning;
|
|
111
|
+
if (reasoning === undefined || reasoning === null || typeof reasoning !== "object" || Array.isArray(reasoning)) {
|
|
112
|
+
return reasoning;
|
|
113
|
+
}
|
|
114
|
+
const next = { ...(reasoning as Record<string, unknown>) };
|
|
115
|
+
delete next.effort;
|
|
116
|
+
return Object.keys(next).length > 0 ? next : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
export function supportedLadderFor(route: { provider: OcxProviderConfig; modelId: string }): string[] | undefined {
|
|
102
120
|
const { provider, modelId } = route;
|
|
103
121
|
if (modelInList(provider.noReasoningModels, modelId)) return [];
|
package/src/server/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../p
|
|
|
53
53
|
import { providerContextCap } from "../providers/context-cap";
|
|
54
54
|
import { providerCodexAccountMode } from "../providers/registry";
|
|
55
55
|
import type { StorageCleanupPolicy } from "../types";
|
|
56
|
+
import { MAX_DECOMPRESSED_BODY_BYTES } from "./request-decompress";
|
|
56
57
|
import {
|
|
57
58
|
CodexAccountCooldownError,
|
|
58
59
|
cooldownErrorMessage,
|
|
@@ -412,6 +413,8 @@ function attachLiveSidebandUpstream(
|
|
|
412
413
|
// upstream cannot hold Codex open after response.completed; darwin no-rewrite traffic
|
|
413
414
|
// requires explicit config-eager opt-in (`auto` always stays tee on darwin).
|
|
414
415
|
// selectEagerPath(process.platform, needsClientRewrite, config.streamMode ?? "auto")
|
|
416
|
+
// Codex upstream WS runtime gating and the forced bounded single-reader branch
|
|
417
|
+
// are owned by responses/ws-upstream.ts and responses/core.ts respectively.
|
|
415
418
|
// relaySseEagerBounded(upstreamResponse.body, turnAc,
|
|
416
419
|
// new Response(eagerBody,
|
|
417
420
|
// Default shape (tee + background inspection):
|
|
@@ -721,6 +724,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
721
724
|
userCostOverlayReconciler = startUserCostOverlayReconciler({ liveConfig: config });
|
|
722
725
|
const serveOptions = {
|
|
723
726
|
idleTimeout: 255,
|
|
727
|
+
maxRequestBodySize: MAX_DECOMPRESSED_BODY_BYTES,
|
|
724
728
|
async fetch(req: Request, requestServer: Server<WsData>): Promise<Response> {
|
|
725
729
|
// The unauthenticated loopback listener (#1102) serves a fixed allowlist and nothing
|
|
726
730
|
// else. Rejecting here, before any handler runs, is what keeps the surface from growing
|
|
@@ -1181,7 +1185,6 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
1181
1185
|
}
|
|
1182
1186
|
|
|
1183
1187
|
if (url.pathname === "/v1/responses" && req.method === "POST") {
|
|
1184
|
-
disableResponsesRequestTimeout(req, requestServer);
|
|
1185
1188
|
if (isDraining()) {
|
|
1186
1189
|
return drainingResponse(req, policy);
|
|
1187
1190
|
}
|
|
@@ -1210,6 +1213,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
|
|
|
1210
1213
|
return runAdmittedHttpTurn(req, policy, async turnAdmissionLease => {
|
|
1211
1214
|
const response = await handleResponses(req, config, logCtx, {
|
|
1212
1215
|
turnAdmissionLease,
|
|
1216
|
+
onRequestBodyRead: () => disableResponsesRequestTimeout(req, requestServer),
|
|
1213
1217
|
abortSignal: req.signal,
|
|
1214
1218
|
onFirstOutput: () => recordFirstOutput(logCtx, start),
|
|
1215
1219
|
onNativePassthroughTerminal: status => {
|