@rigelbuild/omp-catalog 18.0.3

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 (117) hide show
  1. package/CHANGELOG.md +1204 -0
  2. package/LICENSE +22 -0
  3. package/README.md +43 -0
  4. package/THIRD-PARTY-NOTICES.txt +22909 -0
  5. package/dist/types/build.d.ts +19 -0
  6. package/dist/types/compat/anthropic.d.ts +18 -0
  7. package/dist/types/compat/apply.d.ts +7 -0
  8. package/dist/types/compat/bedrock.d.ts +3 -0
  9. package/dist/types/compat/devin.d.ts +9 -0
  10. package/dist/types/compat/openai.d.ts +28 -0
  11. package/dist/types/discovery/antigravity.d.ts +70 -0
  12. package/dist/types/discovery/codex.d.ts +36 -0
  13. package/dist/types/discovery/cursor-proto.d.ts +5368 -0
  14. package/dist/types/discovery/cursor.d.ts +23 -0
  15. package/dist/types/discovery/devin-proto.d.ts +1549 -0
  16. package/dist/types/discovery/devin.d.ts +24 -0
  17. package/dist/types/discovery/gemini-cli.d.ts +33 -0
  18. package/dist/types/discovery/gemini.d.ts +25 -0
  19. package/dist/types/discovery/gitlab-duo-workflow.d.ts +30 -0
  20. package/dist/types/discovery/index.d.ts +7 -0
  21. package/dist/types/discovery/openai-compatible.d.ts +89 -0
  22. package/dist/types/discovery/protobuf.d.ts +102 -0
  23. package/dist/types/effort.d.ts +10 -0
  24. package/dist/types/fireworks-model-id.d.ts +23 -0
  25. package/dist/types/hosts.d.ts +169 -0
  26. package/dist/types/identity/bundled.d.ts +3 -0
  27. package/dist/types/identity/classify.d.ts +69 -0
  28. package/dist/types/identity/dialect.d.ts +3 -0
  29. package/dist/types/identity/family.d.ts +239 -0
  30. package/dist/types/identity/id.d.ts +12 -0
  31. package/dist/types/identity/index.d.ts +8 -0
  32. package/dist/types/identity/markers.d.ts +4 -0
  33. package/dist/types/identity/priority.d.ts +1 -0
  34. package/dist/types/identity/reference.d.ts +29 -0
  35. package/dist/types/index.d.ts +19 -0
  36. package/dist/types/model-cache.d.ts +23 -0
  37. package/dist/types/model-manager.d.ts +77 -0
  38. package/dist/types/model-thinking.d.ts +70 -0
  39. package/dist/types/model-tokenizer.d.ts +8 -0
  40. package/dist/types/models.d.ts +14 -0
  41. package/dist/types/openai-pricing.d.ts +17 -0
  42. package/dist/types/provider-models/bundled-references.d.ts +13 -0
  43. package/dist/types/provider-models/cache-provider-id.d.ts +11 -0
  44. package/dist/types/provider-models/descriptor-types.d.ts +76 -0
  45. package/dist/types/provider-models/descriptors.d.ts +505 -0
  46. package/dist/types/provider-models/google.d.ts +29 -0
  47. package/dist/types/provider-models/index.d.ts +7 -0
  48. package/dist/types/provider-models/ollama.d.ts +22 -0
  49. package/dist/types/provider-models/openai-compat.d.ts +646 -0
  50. package/dist/types/provider-models/special.d.ts +53 -0
  51. package/dist/types/types.d.ts +876 -0
  52. package/dist/types/utils.d.ts +20 -0
  53. package/dist/types/variant-collapse.d.ts +137 -0
  54. package/dist/types/wire/alibaba-token-plan.d.ts +22 -0
  55. package/dist/types/wire/codex.d.ts +66 -0
  56. package/dist/types/wire/coreweave.d.ts +12 -0
  57. package/dist/types/wire/gemini-headers.d.ts +62 -0
  58. package/dist/types/wire/github-copilot.d.ts +47 -0
  59. package/dist/types/wire/image-fetchers.d.ts +76 -0
  60. package/package.json +102 -0
  61. package/src/build.ts +103 -0
  62. package/src/compat/anthropic.ts +179 -0
  63. package/src/compat/apply.ts +15 -0
  64. package/src/compat/bedrock.ts +153 -0
  65. package/src/compat/devin.ts +12 -0
  66. package/src/compat/openai.ts +864 -0
  67. package/src/discovery/antigravity.ts +257 -0
  68. package/src/discovery/codex.ts +456 -0
  69. package/src/discovery/cursor-proto.ts +8329 -0
  70. package/src/discovery/cursor.ts +409 -0
  71. package/src/discovery/devin-proto.ts +2037 -0
  72. package/src/discovery/devin.ts +152 -0
  73. package/src/discovery/gemini-cli.ts +198 -0
  74. package/src/discovery/gemini.ts +260 -0
  75. package/src/discovery/gitlab-duo-workflow.ts +859 -0
  76. package/src/discovery/index.ts +7 -0
  77. package/src/discovery/openai-compatible.ts +266 -0
  78. package/src/discovery/protobuf.ts +1073 -0
  79. package/src/effort.ts +18 -0
  80. package/src/fireworks-model-id.ts +50 -0
  81. package/src/hosts.ts +178 -0
  82. package/src/identity/bundled.ts +28 -0
  83. package/src/identity/classify.ts +242 -0
  84. package/src/identity/dialect.ts +42 -0
  85. package/src/identity/family.ts +477 -0
  86. package/src/identity/id.ts +81 -0
  87. package/src/identity/index.ts +8 -0
  88. package/src/identity/markers.ts +49 -0
  89. package/src/identity/priority.ts +59 -0
  90. package/src/identity/reference.ts +167 -0
  91. package/src/index.ts +19 -0
  92. package/src/model-cache.ts +346 -0
  93. package/src/model-manager.ts +683 -0
  94. package/src/model-thinking.ts +925 -0
  95. package/src/model-tokenizer.ts +75 -0
  96. package/src/models.json +304657 -0
  97. package/src/models.json.d.ts +9 -0
  98. package/src/models.ts +108 -0
  99. package/src/openai-pricing.ts +29 -0
  100. package/src/provider-models/bundled-references.ts +87 -0
  101. package/src/provider-models/cache-provider-id.ts +97 -0
  102. package/src/provider-models/descriptor-types.ts +85 -0
  103. package/src/provider-models/descriptors.ts +585 -0
  104. package/src/provider-models/google.ts +117 -0
  105. package/src/provider-models/index.ts +7 -0
  106. package/src/provider-models/ollama.ts +204 -0
  107. package/src/provider-models/openai-compat.ts +6405 -0
  108. package/src/provider-models/special.ts +213 -0
  109. package/src/types.ts +1032 -0
  110. package/src/utils.ts +69 -0
  111. package/src/variant-collapse.ts +1555 -0
  112. package/src/wire/alibaba-token-plan.ts +54 -0
  113. package/src/wire/codex.ts +117 -0
  114. package/src/wire/coreweave.ts +51 -0
  115. package/src/wire/gemini-headers.ts +135 -0
  116. package/src/wire/github-copilot.ts +147 -0
  117. package/src/wire/image-fetchers.ts +159 -0
@@ -0,0 +1,456 @@
1
+ import { type } from "@rigelbuild/omp-omptype";
2
+ import { parseKnownModel, semverEqual } from "../identity/classify";
3
+ import { getBundledModels } from "../models";
4
+ import { resolveOpenAIDaybreakStandardCost } from "../openai-pricing";
5
+ import type { FetchImpl, ModelSpec } from "../types";
6
+ import { discoveryFetch } from "../utils";
7
+ import { CODEX_BASE_URL, CODEX_CLIENT_VERSION, OPENAI_HEADER_VALUES, OPENAI_HEADERS } from "../wire/codex";
8
+
9
+ const DEFAULT_MODEL_LIST_PATHS = ["/codex/models", "/models"] as const;
10
+ const DEFAULT_CONTEXT_WINDOW = 272_000;
11
+ const DEFAULT_MAX_TOKENS = 128_000;
12
+ /**
13
+ * Fallback for GPT-5.6-family SKUs when upstream omits `context_window`: the
14
+ * generic {@link DEFAULT_CONTEXT_WINDOW} (272000) understates the registry's
15
+ * former 372000 hard capacity (#5705).
16
+ */
17
+ const GPT_5_6_CONTEXT_WINDOW = 372_000;
18
+ /**
19
+ * OpenAI enabled a 1M-token window for subscription Codex on GPT-5.6
20
+ * luna/sol/terra (2026-08-16), but the Codex model registry still reports the
21
+ * stale 272000 — so the reported value must be floored, not just defaulted
22
+ * (openai/codex#38917; Codex CLI override `model_context_window = 1000000`).
23
+ */
24
+ const GPT_5_6_1M_CONTEXT_WINDOW = 1_000_000;
25
+ const CODEX_GPT_5_6_1M_SLUGS: ReadonlySet<string> = new Set(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"]);
26
+ /**
27
+ * Codex advertises worker-mode SKUs under a `-wm` suffix (`gpt-5.6-luna-wm`).
28
+ *
29
+ * Those rows route through the same Codex backend as their plain SKU, but an
30
+ * authoritative discovery list that only advertises the `-wm` slug prunes the
31
+ * bundled plain model, leaving a configured `openai-codex/gpt-5.6-luna`
32
+ * unresolvable except via fuzzy fallback onto the `-wm` row — which this user's
33
+ * ChatGPT account rejects. The compatibility rule, scoped to Codex discovery:
34
+ * a `-wm` slug whose plain counterpart exists in the bundled Codex catalog is
35
+ * ALSO registered under its plain id. Both listings derive their base-model
36
+ * metadata (1M-window floor, daybreak pricing, context fallback) from the
37
+ * canonical plain slug — the suffix is a routing variant, not a different
38
+ * model, so the `-wm` row no longer keeps stale backend-parsed capability
39
+ * values while its plain listing is enriched.
40
+ *
41
+ * Deliberate boundary: the "safe" gate is the bundled Codex catalog. A `-wm`
42
+ * slug whose plain counterpart is only a user-local models.yml entry (not
43
+ * bundled) stays verbatim — authoritative discovery for genuinely distinct
44
+ * `-wm` SKUs is preserved, and a hidden plain backend entry can be re-surfaced
45
+ * through its advertised `-wm` row because the configured plain slug must
46
+ * resolve.
47
+ */
48
+ const CODEX_WORKER_SUFFIX = "-wm";
49
+ const CODEX_REMOTE_COMPACTION = {
50
+ enabled: true,
51
+ api: "openai-codex-responses",
52
+ v2StreamingEnabled: true,
53
+ } as const;
54
+
55
+ const codexReasoningPresetSchema = type({
56
+ "effort?": "unknown",
57
+ });
58
+
59
+ const codexModelEntrySchema = type({
60
+ "slug?": "unknown",
61
+ "id?": "unknown",
62
+ "display_name?": "unknown",
63
+ "context_window?": "unknown",
64
+ "default_reasoning_level?": "unknown",
65
+ "supported_reasoning_levels?": "unknown",
66
+ "input_modalities?": "unknown",
67
+ "visibility?": "unknown",
68
+ "priority?": "unknown",
69
+ "prefer_websockets?": "unknown",
70
+ "use_responses_lite?": "unknown",
71
+ "tool_mode?": "unknown",
72
+ });
73
+
74
+ const codexModelsResponseSchema = type({
75
+ "models?": "unknown[]",
76
+ "data?": "unknown[]",
77
+ });
78
+
79
+ type CodexModelEntry = typeof codexModelEntrySchema.infer;
80
+ interface NormalizedCodexModel {
81
+ model: ModelSpec<"openai-codex-responses">;
82
+ priority: number;
83
+ }
84
+
85
+ /**
86
+ * Fetch options for OpenAI Codex model discovery.
87
+ */
88
+ export interface CodexModelDiscoveryOptions {
89
+ /** OAuth access token used for `Authorization: Bearer ...`. */
90
+ accessToken: string;
91
+ /** ChatGPT account id value used for `chatgpt-account-id` header. */
92
+ accountId?: string;
93
+ /** Base URL for Codex backend. Defaults to `https://chatgpt.com/backend-api`. */
94
+ baseUrl?: string;
95
+ /** Optional client version attached as `client_version` query parameter. */
96
+ clientVersion?: string;
97
+ /** Optional endpoint path candidates. Defaults to `/codex/models`, then `/models`. */
98
+ paths?: readonly string[];
99
+ /** Additional headers merged on top of required Codex headers. */
100
+ headers?: Record<string, string>;
101
+ /** Abort signal for network request cancellation. */
102
+ signal?: AbortSignal;
103
+ /** Optional fetch implementation override for tests. */
104
+ fetchFn?: FetchImpl;
105
+ }
106
+
107
+ /**
108
+ * Normalized Codex discovery response.
109
+ */
110
+ export interface CodexModelDiscoveryResult {
111
+ models: ModelSpec<"openai-codex-responses">[];
112
+ etag?: string;
113
+ }
114
+
115
+ /**
116
+ * Fetches model metadata from Codex backend and normalizes it for pi model management.
117
+ *
118
+ * Returns `null` when no supported model-list route can be fetched/parsed.
119
+ * Returns `{ models: [] }` when a route succeeds but yields no usable models.
120
+ */
121
+ export async function fetchCodexModels(options: CodexModelDiscoveryOptions): Promise<CodexModelDiscoveryResult | null> {
122
+ const fetchFn = discoveryFetch(options.fetchFn);
123
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
124
+ const paths = normalizePaths(options.paths);
125
+ const clientVersion = normalizeClientVersion(options.clientVersion) ?? CODEX_CLIENT_VERSION;
126
+ const headers = buildCodexHeaders(options, clientVersion);
127
+
128
+ let sawSuccessfulResponse = false;
129
+ for (const path of paths) {
130
+ const requestUrl = buildModelsUrl(baseUrl, path, clientVersion);
131
+ let response: Response;
132
+ try {
133
+ response = await fetchFn(requestUrl, {
134
+ method: "GET",
135
+ headers,
136
+ signal: options.signal,
137
+ });
138
+ } catch {
139
+ continue;
140
+ }
141
+
142
+ if (!response.ok) {
143
+ continue;
144
+ }
145
+
146
+ let payload: unknown;
147
+ try {
148
+ payload = await response.json();
149
+ } catch {
150
+ continue;
151
+ }
152
+
153
+ const models = normalizeCodexModels(payload, baseUrl);
154
+ if (models === null) {
155
+ continue;
156
+ }
157
+ sawSuccessfulResponse = true;
158
+ const etag = getResponseEtag(response.headers);
159
+ return etag ? { models, etag } : { models };
160
+ }
161
+ return sawSuccessfulResponse ? { models: [] } : null;
162
+ }
163
+
164
+ function normalizeBaseUrl(baseUrl: string | undefined): string {
165
+ const raw = (baseUrl ?? CODEX_BASE_URL).trim();
166
+ if (!raw) {
167
+ return CODEX_BASE_URL;
168
+ }
169
+ return raw.replace(/\/+$/, "");
170
+ }
171
+
172
+ function normalizePaths(paths: readonly string[] | undefined): string[] {
173
+ if (!paths || paths.length === 0) {
174
+ return [...DEFAULT_MODEL_LIST_PATHS];
175
+ }
176
+ const normalized = paths
177
+ .map(path => path.trim())
178
+ .filter(path => path.length > 0)
179
+ .map(path => (path.startsWith("/") ? path : `/${path}`));
180
+ return normalized.length > 0 ? normalized : [...DEFAULT_MODEL_LIST_PATHS];
181
+ }
182
+
183
+ function buildModelsUrl(baseUrl: string, path: string, clientVersion: string | undefined): string {
184
+ const url = new URL(`${baseUrl}${path}`);
185
+ if (clientVersion && clientVersion.trim().length > 0) {
186
+ url.searchParams.set("client_version", clientVersion.trim());
187
+ }
188
+ return url.toString();
189
+ }
190
+
191
+ function buildCodexHeaders(options: CodexModelDiscoveryOptions, clientVersion: string): Headers {
192
+ const headers = new Headers(options.headers);
193
+ headers.set("Authorization", `Bearer ${options.accessToken}`);
194
+ if (options.accountId && options.accountId.trim().length > 0) {
195
+ headers.set(OPENAI_HEADERS.ACCOUNT_ID, options.accountId);
196
+ }
197
+ headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
198
+ headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
199
+ headers.set(OPENAI_HEADERS.VERSION, clientVersion);
200
+ headers.set("accept", "application/json");
201
+ return headers;
202
+ }
203
+
204
+ function normalizeClientVersion(value: unknown): string | undefined {
205
+ if (typeof value !== "string") {
206
+ return undefined;
207
+ }
208
+ const trimmed = value.trim();
209
+ if (!/^\d+\.\d+\.\d+$/.test(trimmed)) {
210
+ return undefined;
211
+ }
212
+ return trimmed;
213
+ }
214
+
215
+ function normalizeCodexModels(payload: unknown, baseUrl: string): ModelSpec<"openai-codex-responses">[] | null {
216
+ const parsedResponse = codexModelsResponseSchema(payload);
217
+ if (parsedResponse instanceof type.errors) {
218
+ return null;
219
+ }
220
+
221
+ const entries = parsedResponse.models ?? parsedResponse.data ?? [];
222
+ const parsedEntries: ParsedCodexModelEntry[] = [];
223
+ for (const entry of entries) {
224
+ const parsed = parseCodexModelEntry(entry);
225
+ if (parsed) {
226
+ parsedEntries.push(parsed);
227
+ }
228
+ }
229
+
230
+ // A worker `-wm` slug gets an extra plain-id route only when the bundled
231
+ // catalog ships the plain SKU (the "safe" precondition); the backend's own
232
+ // plain slug wins over any synthesized clone, and unknown `-wm` SKUs stay
233
+ // verbatim. Both listings of a safe `-wm` model carry the same base-model
234
+ // metadata (context-window floor, daybreak pricing) derived from the
235
+ // canonical plain slug — the suffix is a routing variant, not a different
236
+ // model.
237
+ const advertisedSlugs = new Set(parsedEntries.map(parsed => parsed.slug));
238
+ const bundledCodexModelIds = getBundledCodexModelIds();
239
+ const normalized: NormalizedCodexModel[] = [];
240
+ for (const parsed of parsedEntries) {
241
+ const canonicalSlug = plainCounterpartForWorkerSlug(parsed.slug, bundledCodexModelIds) ?? parsed.slug;
242
+ normalized.push(buildNormalizedCodexModel(parsed, parsed.slug, canonicalSlug, baseUrl));
243
+ const plainSlug = canonicalSlug !== parsed.slug ? canonicalSlug : null;
244
+ if (plainSlug && !advertisedSlugs.has(plainSlug)) {
245
+ normalized.push(buildNormalizedCodexModel(parsed, plainSlug, canonicalSlug, baseUrl));
246
+ }
247
+ }
248
+
249
+ normalized.sort((left, right) => {
250
+ if (left.priority !== right.priority) {
251
+ return left.priority - right.priority;
252
+ }
253
+ return left.model.id.localeCompare(right.model.id);
254
+ });
255
+
256
+ return normalized.map(item => item.model);
257
+ }
258
+
259
+ /** Ids of the bundled Codex catalog, consulted once per discovery run. */
260
+ function getBundledCodexModelIds(): ReadonlySet<string> {
261
+ const ids = new Set(getBundledModels("openai-codex").map(model => model.id));
262
+ return ids;
263
+ }
264
+
265
+ /**
266
+ * Map a Codex worker `-wm` slug to its plain counterpart when the bundled
267
+ * catalog registers that plain SKU. Returns `null` for non-worker slugs and
268
+ * for `-wm` slugs without a safe plain counterpart.
269
+ */
270
+ function plainCounterpartForWorkerSlug(slug: string, bundledCodexModelIds: ReadonlySet<string>): string | null {
271
+ if (!slug.endsWith(CODEX_WORKER_SUFFIX)) {
272
+ return null;
273
+ }
274
+ const plain = slug.slice(0, -CODEX_WORKER_SUFFIX.length);
275
+ return plain.length > 0 && bundledCodexModelIds.has(plain) ? plain : null;
276
+ }
277
+
278
+ interface ParsedCodexModelEntry {
279
+ slug: string;
280
+ name: string;
281
+ contextWindow: number | null;
282
+ reasoning: boolean;
283
+ input: ("text" | "image")[];
284
+ preferWebsockets: boolean;
285
+ useResponsesLite: boolean;
286
+ toolMode: boolean;
287
+ priority: number;
288
+ }
289
+
290
+ function parseCodexModelEntry(entry: unknown): ParsedCodexModelEntry | null {
291
+ const parsedEntry = codexModelEntrySchema(entry);
292
+ if (parsedEntry instanceof type.errors) {
293
+ return null;
294
+ }
295
+
296
+ const payload: CodexModelEntry = parsedEntry;
297
+ const slug = toNonEmptyString(payload.slug) ?? toNonEmptyString(payload.id);
298
+ if (!slug) {
299
+ return null;
300
+ }
301
+
302
+ const visibility = toNonEmptyString(payload.visibility)?.toLowerCase();
303
+ if (visibility === "hide" || visibility === "hidden") {
304
+ return null;
305
+ }
306
+
307
+ return {
308
+ slug,
309
+ name: toNonEmptyString(payload.display_name) ?? slug,
310
+ contextWindow: toPositiveInt(payload.context_window),
311
+ reasoning: supportsReasoning(payload.default_reasoning_level, payload.supported_reasoning_levels),
312
+ input: normalizeInputModalities(payload.input_modalities),
313
+ preferWebsockets: toBoolean(payload.prefer_websockets) === true,
314
+ useResponsesLite: toBoolean(payload.use_responses_lite) === true,
315
+ toolMode: payload.tool_mode === "code_mode_only",
316
+ priority: toFiniteNumber(payload.priority) ?? Number.MAX_SAFE_INTEGER,
317
+ };
318
+ }
319
+
320
+ /**
321
+ * Build a normalized Codex model spec. `slug` is the registered id (either the
322
+ * advertised slug or a synthesized plain counterpart); `canonicalSlug` names
323
+ * the model's bundled SKU (`slug` itself for plain/unknown rows, the plain
324
+ * counterpart for a safe `-wm` row) and owns the base-model metadata derivation
325
+ * so both listings of a model report the same context window and pricing.
326
+ */
327
+ function buildNormalizedCodexModel(
328
+ parsed: ParsedCodexModelEntry,
329
+ slug: string,
330
+ canonicalSlug: string,
331
+ baseUrl: string,
332
+ ): NormalizedCodexModel {
333
+ // Codex discovery historically omitted `context_window` for GPT-5.6-family
334
+ // SKUs (#5705); luna/sol/terra additionally floor the reported value because
335
+ // the registry still declares the pre-1M 272000 window. Keyed on the
336
+ // canonical slug so a safe `gpt-5.6-luna-wm` row gets the same floor as its
337
+ // plain listing.
338
+ const parsedKnown = parseKnownModel(canonicalSlug);
339
+ const fallbackContextWindow =
340
+ parsedKnown.family === "openai" && semverEqual(parsedKnown.version, "5.6")
341
+ ? GPT_5_6_CONTEXT_WINDOW
342
+ : DEFAULT_CONTEXT_WINDOW;
343
+ const reportedContextWindow = parsed.contextWindow ?? fallbackContextWindow;
344
+ const contextWindow = CODEX_GPT_5_6_1M_SLUGS.has(canonicalSlug)
345
+ ? Math.max(reportedContextWindow, GPT_5_6_1M_CONTEXT_WINDOW)
346
+ : reportedContextWindow;
347
+ const maxTokens = Math.min(DEFAULT_MAX_TOKENS, contextWindow);
348
+ const daybreakCost = resolveOpenAIDaybreakStandardCost(canonicalSlug);
349
+
350
+ return {
351
+ priority: parsed.priority,
352
+ model: {
353
+ id: slug,
354
+ name: parsed.name,
355
+ api: "openai-codex-responses",
356
+ provider: "openai-codex",
357
+ baseUrl,
358
+ reasoning: parsed.reasoning,
359
+ input: parsed.input,
360
+ cost: daybreakCost ? { ...daybreakCost } : { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
361
+ remoteCompaction: CODEX_REMOTE_COMPACTION,
362
+ contextWindow,
363
+ maxTokens,
364
+ ...(parsed.preferWebsockets ? { preferWebsockets: true } : {}),
365
+ ...(parsed.useResponsesLite ? { useResponsesLite: true } : {}),
366
+ ...(parsed.toolMode ? { toolMode: "code_mode_only" as const } : {}),
367
+ ...(parsed.priority !== Number.MAX_SAFE_INTEGER ? { priority: parsed.priority } : {}),
368
+ },
369
+ };
370
+ }
371
+
372
+ function supportsReasoning(defaultReasoningLevel: unknown, supportedReasoningLevels: unknown): boolean {
373
+ const defaultLevel = toNonEmptyString(defaultReasoningLevel)?.toLowerCase();
374
+ if (defaultLevel && defaultLevel !== "none") {
375
+ return true;
376
+ }
377
+
378
+ if (!Array.isArray(supportedReasoningLevels)) {
379
+ return false;
380
+ }
381
+
382
+ for (const level of supportedReasoningLevels) {
383
+ const parsedLevel = codexReasoningPresetSchema(level);
384
+ if (parsedLevel instanceof type.errors) {
385
+ continue;
386
+ }
387
+ const effort = toNonEmptyString(parsedLevel.effort)?.toLowerCase();
388
+ if (effort && effort !== "none") {
389
+ return true;
390
+ }
391
+ }
392
+
393
+ return false;
394
+ }
395
+
396
+ function normalizeInputModalities(inputModalities: unknown): ("text" | "image")[] {
397
+ if (!Array.isArray(inputModalities)) {
398
+ return ["text", "image"];
399
+ }
400
+
401
+ const set = new Set<"text" | "image">();
402
+ for (const modality of inputModalities) {
403
+ const normalized = toNonEmptyString(modality)?.toLowerCase();
404
+ if (normalized === "text" || normalized === "image") {
405
+ set.add(normalized);
406
+ }
407
+ }
408
+
409
+ if (set.size === 0) {
410
+ return ["text", "image"];
411
+ }
412
+
413
+ const canonical: ("text" | "image")[] = ["text", "image"];
414
+ return canonical.filter(modality => set.has(modality));
415
+ }
416
+
417
+ function getResponseEtag(headers: Headers): string | undefined {
418
+ const etag = headers.get("etag");
419
+ if (!etag) {
420
+ return undefined;
421
+ }
422
+ const trimmed = etag.trim();
423
+ return trimmed.length > 0 ? trimmed : undefined;
424
+ }
425
+
426
+ function toNonEmptyString(value: unknown): string | null {
427
+ if (typeof value !== "string") {
428
+ return null;
429
+ }
430
+ const trimmed = value.trim();
431
+ return trimmed.length > 0 ? trimmed : null;
432
+ }
433
+
434
+ function toPositiveInt(value: unknown): number | null {
435
+ if (typeof value !== "number" || !Number.isFinite(value)) {
436
+ return null;
437
+ }
438
+ if (value <= 0) {
439
+ return null;
440
+ }
441
+ return Math.trunc(value);
442
+ }
443
+
444
+ function toFiniteNumber(value: unknown): number | null {
445
+ if (typeof value !== "number" || !Number.isFinite(value)) {
446
+ return null;
447
+ }
448
+ return value;
449
+ }
450
+
451
+ function toBoolean(value: unknown): boolean | null {
452
+ if (typeof value !== "boolean") {
453
+ return null;
454
+ }
455
+ return value;
456
+ }