@bitkyc08/opencodex 2.17.0 → 2.18.2

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 (37) hide show
  1. package/gui/dist/assets/{index-DOKr6RBR.js → index-CXI1262_.js} +1 -1
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/adapters/client-fingerprint.ts +2 -2
  5. package/src/adapters/cursor/live-transport.ts +17 -5
  6. package/src/adapters/cursor/protobuf-events.ts +662 -20
  7. package/src/adapters/cursor/tool-definitions.ts +12 -6
  8. package/src/adapters/google.ts +21 -2
  9. package/src/bridge.ts +12 -2
  10. package/src/cli/index.ts +11 -0
  11. package/src/codex/app-server-processes.ts +3 -3
  12. package/src/codex/user-identity.ts +36 -6
  13. package/src/config.ts +0 -2
  14. package/src/generated/compatibility-version.json +37 -33
  15. package/src/lib/token-estimate.ts +19 -2
  16. package/src/lib/windows-elevation.ts +37 -0
  17. package/src/lib/windows-secret-acl.ts +7 -0
  18. package/src/lib/windows-text.ts +106 -0
  19. package/src/lib/windows-user-principal.ts +0 -2
  20. package/src/oauth/index.ts +1 -1
  21. package/src/oauth/store.ts +32 -18
  22. package/src/providers/antigravity-models.ts +25 -5
  23. package/src/providers/free-directory.ts +1 -1
  24. package/src/providers/registry.ts +4 -3
  25. package/src/server/index.ts +5 -1
  26. package/src/server/management/logs-usage-routes.ts +7 -22
  27. package/src/server/request-log.ts +48 -3
  28. package/src/server/responses/core.ts +38 -13
  29. package/src/server/responses/encrypted-payload.ts +58 -38
  30. package/src/server/responses/fetch-helpers.ts +12 -4
  31. package/src/server/responses/policy-fallback.ts +13 -2
  32. package/src/server/responses/ws-upstream.ts +115 -6
  33. package/src/service-manager-probe.ts +21 -34
  34. package/src/service.ts +233 -25
  35. package/src/tray/windows.ts +0 -2
  36. package/src/update/job.ts +2 -2
  37. package/src/usage/summary.ts +21 -4
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Decode bounded text emitted by Windows system tools.
3
+ *
4
+ * [Decision Log]
5
+ * - Purpose: preserve non-ASCII paths when a Windows tool writes the active
6
+ * legacy code page instead of UTF-8.
7
+ * - Existing constraint: generated service assets may be UTF-16, while
8
+ * redirected `schtasks` output follows the Windows locale on affected hosts.
9
+ * - Alternatives considered: replacement-character heuristics and a new
10
+ * iconv dependency. The former can reinterpret valid text; the latter widens
11
+ * the install/security surface for two small, already-supported codecs.
12
+ * - Choice: recognize UTF-16 first, accept only strict UTF-8 next, then use the
13
+ * locale-appropriate WHATWG decoder (CP949 through `euc-kr`, or Windows-1252
14
+ * only for locales that actually use that family). Unknown/unsupported
15
+ * locales fail back to the old replacement-preserving UTF-8 result instead
16
+ * of guessing another code page or throwing in diagnostics.
17
+ * - Impact: decoding stays dependency-free and bounded, but this deliberately
18
+ * does not guess arbitrary OEM code pages that the runtime cannot identify.
19
+ */
20
+
21
+ function trimWindowsText(value: string): string {
22
+ return value.replace(/^\uFEFF/, "").trim();
23
+ }
24
+
25
+ function currentWindowsLocale(): string {
26
+ try {
27
+ return Intl.DateTimeFormat().resolvedOptions().locale;
28
+ } catch {
29
+ return "en-US";
30
+ }
31
+ }
32
+
33
+ function decodeStrict(buffer: Uint8Array, encoding: string): string | null {
34
+ try {
35
+ return trimWindowsText(new TextDecoder(encoding, { fatal: true }).decode(buffer));
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ function decodeUtf16Be(buffer: Uint8Array): string {
42
+ const payloadLength = buffer.length - 2;
43
+ const swapped = Buffer.alloc(payloadLength - (payloadLength % 2));
44
+ for (let i = 2; i + 1 < buffer.length; i += 2) {
45
+ swapped[i - 2] = buffer[i + 1]!;
46
+ swapped[i - 1] = buffer[i]!;
47
+ }
48
+ return trimWindowsText(swapped.toString("utf16le"));
49
+ }
50
+
51
+ /**
52
+ * CP949 is exposed by the Encoding Standard under the `euc-kr` label. Keep the
53
+ * Western fallback deliberately narrow: treating CP932, CP1250, or CP1251
54
+ * bytes as Windows-1252 can fabricate a different valid-looking filesystem
55
+ * path, which is worse than the previous replacement-character refusal.
56
+ */
57
+ function legacyEncodingForLocale(locale: string): "euc-kr" | "windows-1252" | null {
58
+ const language = locale.trim().split(/[-_]/, 1)[0]?.toLowerCase();
59
+ if (language === "ko") return "euc-kr";
60
+ if (language && WINDOWS_1252_LANGUAGES.has(language)) return "windows-1252";
61
+ return null;
62
+ }
63
+
64
+ const WINDOWS_1252_LANGUAGES = new Set([
65
+ "af", "br", "ca", "co", "cy", "da", "de", "en", "es", "eu", "fi", "fo", "fr",
66
+ "ga", "gd", "gl", "id", "is", "it", "lb", "ms", "nl", "no", "oc", "pt", "sq",
67
+ "sv", "sw",
68
+ ]);
69
+
70
+ export interface WindowsTextDecodeOptions {
71
+ /** Test seam and explicit locale override; production uses the active Intl locale. */
72
+ readonly locale?: string;
73
+ }
74
+
75
+ export function decodeWindowsTextBytes(
76
+ buffer: Uint8Array,
77
+ options: WindowsTextDecodeOptions = {},
78
+ ): string {
79
+ if (buffer.length === 0) return "";
80
+
81
+ const bomUtf16Le = buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe;
82
+ const bomUtf16Be = buffer.length >= 2 && buffer[0] === 0xfe && buffer[1] === 0xff;
83
+ const looksUtf16Le = buffer.length >= 4
84
+ && buffer[1] === 0x00
85
+ && buffer[3] === 0x00
86
+ && buffer[0] !== 0x00;
87
+
88
+ if (bomUtf16Le || looksUtf16Le) {
89
+ return trimWindowsText(Buffer.from(buffer).toString("utf16le"));
90
+ }
91
+ if (bomUtf16Be) return decodeUtf16Be(buffer);
92
+
93
+ const utf8 = decodeStrict(buffer, "utf-8");
94
+ if (utf8 !== null) return utf8;
95
+
96
+ const locale = options.locale ?? currentWindowsLocale();
97
+ const legacyEncoding = legacyEncodingForLocale(locale);
98
+ if (legacyEncoding !== null) {
99
+ const legacy = decodeStrict(buffer, legacyEncoding);
100
+ if (legacy !== null) return legacy;
101
+ }
102
+
103
+ // Preserve the previous fail-soft behavior when the runtime lacks a codec or
104
+ // the bytes are malformed even for the selected Windows code page.
105
+ return trimWindowsText(new TextDecoder("utf-8").decode(buffer));
106
+ }
@@ -105,8 +105,6 @@ const POWERSHELL_ARGS = [
105
105
  "-NoLogo",
106
106
  "-NoProfile",
107
107
  "-NonInteractive",
108
- "-WindowStyle",
109
- "Hidden",
110
108
  "-Command",
111
109
  SID_EXPRESSION,
112
110
  ] as const;
@@ -1124,7 +1124,7 @@ export async function runLogin(
1124
1124
  await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, cred);
1125
1125
  } else {
1126
1126
  await (deps.saveCredential ?? saveCredential)(provider, cred, {
1127
- preserveIdentityless: provider === "kiro" && opts?.forceLogin === true,
1127
+ preserveIdentityless: opts?.forceLogin === true,
1128
1128
  });
1129
1129
  }
1130
1130
  if (provider !== "chatgpt") {
@@ -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
- * instead of appending: their refresh tokens rotate, so a derived id would duplicate the
15
- * same human on every re-login. Kimi extracts JWT `user_id`/`sub` as accountId; Cursor
16
- * extracts JWT `sub` both append distinct accounts under multiauth.
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 = newAccountId(safe);
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 (single-account semantics).
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 = newAccountId(safe);
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 matches = (account: ProviderAccount): boolean => {
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 = set?.accounts.find(matches);
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, GEMINI_FLASH_CURRENT]),
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: GEMINI_FLASH_CURRENT,
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"],
@@ -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 => {
@@ -55,7 +55,7 @@ import {
55
55
  type PersistedUsageEntry,
56
56
  } from "../../usage/log";
57
57
  import { getUsageDebugLogEntries } from "../../usage/debug";
58
- import { parseRange, parseUsageSurface, summarizeUsage, type UsageRange, type UsageSummary, type UsageSurface } from "../../usage/summary";
58
+ import { parseRange, parseUsageSurface, rangeWindow, summarizeUsage, type UsageRange, type UsageSummary, type UsageSurface } from "../../usage/summary";
59
59
  import { stripCodexRuntimeProviderFields } from "../../codex/auth-context";
60
60
  import { getProviderRegistryEntry } from "../../providers/registry";
61
61
  import { getDebugLogEntries } from "../../lib/debug-log-buffer";
@@ -87,14 +87,6 @@ import {
87
87
  } from "./usage-summary-cache";
88
88
  import { cacheApiKeyUsageFromSnapshot } from "./api-key-usage";
89
89
 
90
- const USAGE_DAY_MS = 86_400_000;
91
- function usageEntryMatchesSurface(entry: PersistedUsageEntry, surface: UsageSurface): boolean {
92
- if (surface === "claude") return entry.surface === "claude" || entry.surface === "claude-desktop";
93
- if (surface === "grok") return entry.surface === "grok";
94
- if (surface === "codex") return entry.surface === undefined;
95
- return true;
96
- }
97
-
98
90
  function nextLocalMidnight(now: number): number {
99
91
  const next = new Date(now);
100
92
  next.setHours(24, 0, 0, 0);
@@ -102,24 +94,16 @@ function nextLocalMidnight(now: number): number {
102
94
  }
103
95
 
104
96
  function usageSummaryExpiresAt(
105
- entries: PersistedUsageEntry[],
106
- range: UsageRange,
107
- surface: UsageSurface,
97
+ _entries: PersistedUsageEntry[],
98
+ _range: UsageRange,
99
+ _surface: UsageSurface,
108
100
  now: number,
109
101
  ): number {
110
- let expiresAt = nextLocalMidnight(now);
111
- const windowMs = range === "7d" ? 7 * USAGE_DAY_MS : range === "30d" ? 30 * USAGE_DAY_MS : null;
112
- if (windowMs === null) return expiresAt;
113
- for (const entry of entries) {
114
- if (!usageEntryMatchesSurface(entry, surface)) continue;
115
- const expiry = entry.timestamp + windowMs;
116
- if (expiry > now && expiry < expiresAt) expiresAt = expiry;
117
- }
118
- return expiresAt;
102
+ return nextLocalMidnight(now);
119
103
  }
120
104
 
121
105
  function refreshedUsageSummary<T extends UsageSummary & { historyTruncated: boolean }>(summary: T, range: UsageRange, now: number): T {
122
- const since = range === "7d" ? now - 7 * USAGE_DAY_MS : range === "30d" ? now - 30 * USAGE_DAY_MS : null;
106
+ const { since } = rangeWindow(range, now);
123
107
  return { ...summary, since, generatedAt: now };
124
108
  }
125
109
 
@@ -226,6 +210,7 @@ export async function handleLogsUsageRoutes(ctx: ManagementContext): Promise<Res
226
210
  && cached.maxReadBytes === effectiveReadLimit
227
211
  && cached.overlayVersion === userCostOverlayVersion()
228
212
  && now < cached.freshUntil
213
+ && now < cached.expiresAt
229
214
  && observedSize >= cached.lastSeenSize) {
230
215
  return jsonResponse(refreshedUsageSummary(cached.summary, range, now));
231
216
  }
@@ -38,6 +38,10 @@ import {
38
38
  } from "../usage/debug";
39
39
  import { matchesLogConversationId } from "./request-log-conversation";
40
40
  import { enforceAppOwnedMemoryBudget, type RetainedStoreSnapshot } from "../lib/app-owned-memory";
41
+ import { capEstimateAtContextWindow } from "../lib/token-estimate";
42
+ import { inferCursorContextWindow } from "../adapters/cursor/discovery";
43
+ import { KIRO_MODEL_CONTEXT_WINDOWS, normalizeKiroModelId } from "../providers/kiro-models";
44
+ import { modelRecordValue } from "../reasoning-effort";
41
45
 
42
46
  export interface RequestLogContext {
43
47
  model: string;
@@ -839,6 +843,7 @@ export function addFinalRequestLog(
839
843
  logCtx.providerAdapter ?? logCtx.provider,
840
844
  logCtx.usage,
841
845
  logCtx.usageLogInputTokens,
846
+ contextWindowForModel(logCtx.providerAdapter ?? logCtx.provider, logCtx.model),
842
847
  );
843
848
  const attempts = logCtx.attempts?.map(attempt => ({
844
849
  ...attempt,
@@ -960,15 +965,40 @@ interface FinalizedUsageResult {
960
965
  totalTokens?: number;
961
966
  }
962
967
 
968
+ /**
969
+ * Context window for the routed model, used to cap the token estimate (codex-router PR #140):
970
+ * a request the provider answered cannot have exceeded the window, so the estimate must never
971
+ * claim it did. The family is picked by the route ADAPTER, not the model id alone, because
972
+ * claude-family ids are shared between Kiro and Cursor with different windows. Kiro "auto" is
973
+ * a router with no fixed window and is never guessed; unknown adapters/models stay uncapped.
974
+ */
975
+ function contextWindowForModel(adapter: string, modelId: string | undefined): number | undefined {
976
+ if (!modelId) return undefined;
977
+ if (adapter === "kiro" || adapter.startsWith("kiro-")) {
978
+ const normalized = normalizeKiroModelId(modelId);
979
+ if (normalized === "auto") return undefined;
980
+ return modelRecordValue(KIRO_MODEL_CONTEXT_WINDOWS, modelId)
981
+ ?? modelRecordValue(KIRO_MODEL_CONTEXT_WINDOWS, normalized);
982
+ }
983
+ if (adapter === "cursor" || adapter.startsWith("cursor-")) {
984
+ return inferCursorContextWindow(modelId);
985
+ }
986
+ return undefined;
987
+ }
988
+
963
989
  function finalizedUsage(
964
990
  adapter: string,
965
991
  usage: OcxUsage | undefined,
966
992
  inputTokenEstimate: number | undefined,
993
+ contextWindow: number | undefined,
967
994
  ): FinalizedUsageResult {
995
+ // The ESTIMATE itself is capped at the model's context window (codex-router PR #140). The
996
+ // combined value below keeps its max(inputTokens, estimate) behavior — a provider-reported
997
+ // positive count is never reduced by this cap, only the estimate that could substitute it.
968
998
  const estimate = typeof inputTokenEstimate === "number"
969
999
  && Number.isFinite(inputTokenEstimate)
970
1000
  && inputTokenEstimate >= 0
971
- ? inputTokenEstimate
1001
+ ? capEstimateAtContextWindow(inputTokenEstimate, contextWindow)
972
1002
  : undefined;
973
1003
  const finalUsage = usageForFinalLog(adapter, usage);
974
1004
  const usageFallback = !finalUsage && estimate !== undefined
@@ -980,7 +1010,16 @@ function finalizedUsage(
980
1010
  inputTokens: Math.max(finalUsage.inputTokens, estimate),
981
1011
  estimated: true,
982
1012
  }
983
- : (finalUsage ?? usageFallback);
1013
+ : finalUsage
1014
+ // When the adapter alone produced an estimated count and no local estimate
1015
+ // exists, cap it at the context window — an adapter estimate above the window
1016
+ // misleads the usage dashboard. The combined branch (above) already caps the
1017
+ // ESTIMATE via capEstimateAtContextWindow, and Math.max preserves a real
1018
+ // provider-reported count, so it needs no further reduction.
1019
+ ? (finalUsage.estimated && contextWindow !== undefined && finalUsage.inputTokens > contextWindow
1020
+ ? { ...finalUsage, inputTokens: contextWindow }
1021
+ : finalUsage)
1022
+ : usageFallback;
984
1023
  const totalTokens = usageTotalTokens(loggedUsage);
985
1024
  return {
986
1025
  status: usageStatusForFinalLog(loggedUsage),
@@ -1030,7 +1069,12 @@ export function noteAttemptSend(
1030
1069
  if (typeof inputTokenEstimate === "number"
1031
1070
  && Number.isFinite(inputTokenEstimate)
1032
1071
  && inputTokenEstimate >= 0) {
1033
- attempt.inputTokenEstimate = inputTokenEstimate;
1072
+ // Store the ESTIMATE field already capped at the model's window (codex-router PR #140):
1073
+ // what gets persisted, and later merged into usage, never claims a count above the window.
1074
+ attempt.inputTokenEstimate = capEstimateAtContextWindow(
1075
+ inputTokenEstimate,
1076
+ contextWindowForModel(attempt.adapter, attempt.model),
1077
+ );
1034
1078
  }
1035
1079
  if (recovery && !attempt.recoveryKinds.includes(recovery)) {
1036
1080
  attempt.recoveryKinds.push(recovery);
@@ -1047,6 +1091,7 @@ export function finishRequestAttempt(
1047
1091
  attempt.adapter,
1048
1092
  usage ?? attempt.usage,
1049
1093
  attempt.inputTokenEstimate,
1094
+ contextWindowForModel(attempt.adapter, attempt.model),
1050
1095
  );
1051
1096
  attempt.status = status;
1052
1097
  attempt.durationMs = Math.max(0, durationMs);