@bitkyc08/opencodex 2.40.0 → 2.41.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 (72) hide show
  1. package/README.md +4 -0
  2. package/gui/dist/assets/index-B2YjLA-i.css +1 -0
  3. package/gui/dist/assets/{index-BHe2rl_C.js → index-aPup8CKb.js} +20 -20
  4. package/gui/dist/index.html +2 -2
  5. package/gui/dist/provider-icons/meta.svg +1 -0
  6. package/package.json +4 -3
  7. package/src/adapters/cursor/catalog.ts +71 -29
  8. package/src/adapters/cursor/claude-id.ts +76 -0
  9. package/src/adapters/cursor/discovery.ts +16 -3
  10. package/src/adapters/cursor/effort-map.ts +27 -12
  11. package/src/adapters/google.ts +39 -2
  12. package/src/adapters/openai-responses.ts +14 -1
  13. package/src/cli/claude.ts +11 -2
  14. package/src/cli/connect.ts +7 -1
  15. package/src/cli/registry.ts +1 -1
  16. package/src/cli/status.ts +19 -4
  17. package/src/client/connect.ts +5 -1
  18. package/src/client/hub-client.ts +29 -5
  19. package/src/clients/config-export.ts +12 -2
  20. package/src/codex/catalog/aggregation.ts +8 -0
  21. package/src/codex/catalog/metadata.ts +5 -0
  22. package/src/codex/catalog/parsing.ts +2 -0
  23. package/src/codex/catalog/provider-fetch.ts +163 -26
  24. package/src/codex/catalog.ts +1 -1
  25. package/src/codex/convergence-types.ts +1 -0
  26. package/src/codex/desired-state.ts +18 -11
  27. package/src/combos/failover.ts +185 -6
  28. package/src/combos/index.ts +6 -0
  29. package/src/combos/resolve.ts +43 -6
  30. package/src/config.ts +5 -1
  31. package/src/generated/compatibility-version.json +85 -61
  32. package/src/generated/model-metadata.ts +1 -1
  33. package/src/grok/sync.ts +10 -2
  34. package/src/integrations/cursor-effort-table.ts +143 -0
  35. package/src/integrations/state.ts +1 -1
  36. package/src/integrations/writer.ts +2 -2
  37. package/src/lib/app-owned-memory-stores.ts +27 -8
  38. package/src/lib/bounded-body.ts +16 -1
  39. package/src/oauth/generic-account-failover.ts +2 -2
  40. package/src/oauth/index.ts +11 -0
  41. package/src/oauth/meta-muse.ts +235 -0
  42. package/src/providers/antigravity-models.ts +71 -13
  43. package/src/providers/command-code-efforts.ts +15 -0
  44. package/src/providers/free-directory.ts +4 -1
  45. package/src/providers/registry.ts +116 -8
  46. package/src/responses/code-mode-helper-compat.ts +4 -1
  47. package/src/responses/state.ts +5 -4
  48. package/src/server/auth-cors.ts +241 -56
  49. package/src/server/chat-completions.ts +11 -2
  50. package/src/server/chat-native.ts +30 -4
  51. package/src/server/claude-messages.ts +17 -3
  52. package/src/server/effort-row.ts +131 -0
  53. package/src/server/index.ts +67 -38
  54. package/src/server/management/api-key-rotation.ts +2 -1
  55. package/src/server/management/api-key-usage.ts +97 -43
  56. package/src/server/management/context.ts +3 -0
  57. package/src/server/management/cursor-integration-routes.ts +36 -7
  58. package/src/server/management/logs-usage-routes.ts +64 -87
  59. package/src/server/management/provider-routes.ts +218 -1
  60. package/src/server/management/route-registry.ts +1 -0
  61. package/src/server/management/usage-aggregate-cache.ts +464 -0
  62. package/src/server/management/usage-summary-cache.ts +4 -0
  63. package/src/server/models-capabilities.ts +60 -5
  64. package/src/server/responses/core.ts +61 -7
  65. package/src/types/config.ts +10 -1
  66. package/src/types/tools.ts +12 -9
  67. package/src/usage/expected-prices.ts +43 -7
  68. package/src/usage/ledger-scanner.ts +448 -0
  69. package/src/usage/log.ts +1 -1
  70. package/src/usage/summary.ts +915 -655
  71. package/src/web-search/index.ts +1 -1
  72. package/gui/dist/assets/index-CJSb3HPe.css +0 -1
@@ -1,5 +1,6 @@
1
1
  import { MAX_REMOTE_CATALOG_BYTES } from "../server/catalog-download";
2
2
  import { readBoundedResponseBytes } from "../lib/bounded-body";
3
+ import { clearableDeadline } from "../lib/abort";
3
4
 
4
5
  /**
5
6
  * A pairing grant may cross loopback or authenticated HTTPS, and nothing else.
@@ -84,13 +85,17 @@ async function fetchBounded(
84
85
  url: string,
85
86
  init: RequestInit,
86
87
  timeoutMs: number | undefined,
88
+ timeoutScope: "request" | "headers" = "request",
87
89
  ): Promise<Response> {
90
+ const timeout = safeTimeout(timeoutMs);
91
+ const headerDeadline = timeoutScope === "headers" ? clearableDeadline(timeout) : null;
88
92
  try {
89
93
  const response = await fetchImpl(url, {
90
94
  ...init,
91
95
  redirect: "manual",
92
- signal: AbortSignal.timeout(safeTimeout(timeoutMs)),
96
+ signal: headerDeadline?.signal ?? AbortSignal.timeout(timeout),
93
97
  });
98
+ headerDeadline?.clear();
94
99
  if (response.status >= 300 && response.status < 400 && response.status !== 304) {
95
100
  throw new HubClientError("redirect_refused", "Hub request redirect was refused", response.status);
96
101
  }
@@ -98,15 +103,24 @@ async function fetchBounded(
98
103
  } catch (error) {
99
104
  if (error instanceof HubClientError) throw error;
100
105
  throw new HubClientError("unreachable", "Hub request did not complete", undefined, { cause: error });
106
+ } finally {
107
+ headerDeadline?.clear();
101
108
  }
102
109
  }
103
110
 
104
- async function boundedText(response: Response, maxBytes: number): Promise<string> {
111
+ async function boundedText(
112
+ response: Response,
113
+ maxBytes: number,
114
+ options: { inactivityTimeoutMs?: number } = {},
115
+ ): Promise<string> {
105
116
  const declared = Number(response.headers.get("content-length") ?? "0");
106
117
  if (Number.isFinite(declared) && declared > maxBytes) {
107
118
  throw new HubClientError("body_too_large", "Hub response exceeded the allowed size", response.status);
108
119
  }
109
- const result = await readBoundedResponseBytes(response, { maxBytes });
120
+ const result = await readBoundedResponseBytes(response, {
121
+ maxBytes,
122
+ ...(options.inactivityTimeoutMs === undefined ? {} : { inactivityTimeoutMs: options.inactivityTimeoutMs }),
123
+ });
110
124
  if (result.oversized) {
111
125
  throw new HubClientError("body_too_large", "Hub response exceeded the allowed size", response.status);
112
126
  }
@@ -422,7 +436,7 @@ export async function downloadClientCatalog(
422
436
  const response = await fetchBounded(options.fetchImpl ?? fetch, `${origin}/v1/catalog`, {
423
437
  method: "GET",
424
438
  headers,
425
- }, options.timeoutMs);
439
+ }, options.timeoutMs, "headers");
426
440
  if (response.status === 304) {
427
441
  throw new HubClientError("catalog_unexpected_304", "Hub answered 304 to an unconditional catalog request", 304);
428
442
  }
@@ -434,7 +448,17 @@ export async function downloadClientCatalog(
434
448
  try { await response.body?.cancel(); } catch { /* best effort */ }
435
449
  throw new HubClientError("catalog_content_type_invalid", "Hub catalog response was not JSON", response.status);
436
450
  }
437
- const body = await boundedText(response, options.maxBytes ?? MAX_REMOTE_CATALOG_BYTES);
451
+ let body: string;
452
+ try {
453
+ body = await boundedText(response, options.maxBytes ?? MAX_REMOTE_CATALOG_BYTES, {
454
+ inactivityTimeoutMs: safeTimeout(options.timeoutMs),
455
+ });
456
+ } catch (error) {
457
+ if (error instanceof DOMException && error.name === "TimeoutError") {
458
+ throw new HubClientError("unreachable", "Hub catalog download stalled", undefined, { cause: error });
459
+ }
460
+ throw error;
461
+ }
438
462
  const parsed = parseJson(body, "catalog_invalid");
439
463
  validateRemoteCatalog(parsed);
440
464
  const keyId = response.headers.get("x-opencodex-key-id")?.trim() || undefined;
@@ -22,7 +22,7 @@
22
22
  import { homedir } from "node:os";
23
23
  import { existsSync, readFileSync } from "node:fs";
24
24
  import { isAbsolute, join, resolve } from "node:path";
25
- import { shouldInjectApiAuthHeader } from "../codex/inject";
25
+ import { shouldInjectApiAuthHeader, standaloneCodexRoutingTarget } from "../codex/inject";
26
26
  import { FORMAT_MEDIA_TYPE, serializeDocument, type ConfigFormat } from "../integrations/serialize";
27
27
  import { providerCodexAccountMode } from "../providers/registry";
28
28
  import { canonicalizeReasoningEfforts, sanitizeCodexReasoningEfforts } from "../reasoning-effort";
@@ -301,7 +301,17 @@ export function ompModelsConfigPath(env: OpencodeLaunchEnv = process.env, home:
301
301
  }
302
302
 
303
303
  /** Compose the OpenAI-compatible proxy base URL from a live probe result. */
304
- export function opencodeProxyBaseUrl(port: number, hostname?: string): string {
304
+ export function opencodeProxyBaseUrl(
305
+ port: number,
306
+ hostname?: string,
307
+ config?: Pick<OcxConfig, "unauthenticatedLoopbackListener">,
308
+ ): string {
309
+ if (config?.unauthenticatedLoopbackListener?.enabled) {
310
+ return standaloneCodexRoutingTarget(port, {
311
+ hostname,
312
+ unauthenticatedLoopbackListener: config.unauthenticatedLoopbackListener,
313
+ }).baseUrl;
314
+ }
305
315
  return `http://${probeHostname(hostname)}:${port}/v1`;
306
316
  }
307
317
 
@@ -162,6 +162,12 @@ export function deriveComboCatalogModel(
162
162
  contextWindow,
163
163
  ...members.map(member => member.maxInputTokens ?? member.contextWindow!),
164
164
  );
165
+ const knownMaxOutputTokens = members
166
+ .map(member => member.maxOutputTokens)
167
+ .filter((value): value is number => typeof value === "number" && value > 0);
168
+ const maxOutputTokens = knownMaxOutputTokens.length === members.length
169
+ ? Math.min(...knownMaxOutputTokens)
170
+ : undefined;
165
171
  const autoCompactTokenLimit = Math.min(
166
172
  ...members.map(member => clampAutoCompactTokenLimit(
167
173
  member.contextWindow!,
@@ -180,6 +186,7 @@ export function deriveComboCatalogModel(
180
186
  owned_by: COMBO_NAMESPACE,
181
187
  contextWindow,
182
188
  maxInputTokens,
189
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
183
190
  autoCompactTokenLimit,
184
191
  ...(hasLimitingContextCapMetadata ? { contextCapped } : {}),
185
192
  inputModalities,
@@ -320,6 +327,7 @@ export function normalizedOpenAiApiSignature(model: CatalogModel): string {
320
327
  id: model.id,
321
328
  contextWindow: model.contextWindow ?? null,
322
329
  maxInputTokens: model.maxInputTokens ?? null,
330
+ maxOutputTokens: model.maxOutputTokens ?? null,
323
331
  autoCompactTokenLimit: model.autoCompactTokenLimit ?? null,
324
332
  inputModalities: [...new Set(model.inputModalities ?? [])].sort(),
325
333
  reasoningEfforts: [...new Set(model.reasoningEfforts ?? [])].sort(),
@@ -271,6 +271,11 @@ export function nativeOpenAiContextWindow(slug: string, limits?: NativeContextLi
271
271
  return narrowToLimits(raw, slug, limits);
272
272
  }
273
273
 
274
+ export function nativeOpenAiMaxOutputTokens(slug: string): number | undefined {
275
+ const sourceSlug = nativeOpenAiCapabilitySourceSlug(slug);
276
+ return positiveInt(getModelMetadata("openai", sourceSlug)?.maxTokens);
277
+ }
278
+
274
279
  /**
275
280
  * Long-context tier for a native slug as a (default, long) pair, for clients that let the user
276
281
  * pick a window per request (Cursor's local-agent "Context" selector). The pair is the family's
@@ -112,6 +112,8 @@ export interface CatalogModel {
112
112
  defaultReasoningEffort?: string;
113
113
  contextWindow?: number;
114
114
  maxInputTokens?: number;
115
+ /** Model-scoped output-token ceiling; omitted when no authoritative value is known. */
116
+ maxOutputTokens?: number;
115
117
  /** Soft client compaction threshold; hard context/input limits remain authoritative. */
116
118
  autoCompactTokenLimit?: number;
117
119
  contextCap?: number;