@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.
- package/CHANGELOG.md +1204 -0
- package/LICENSE +22 -0
- package/README.md +43 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/build.d.ts +19 -0
- package/dist/types/compat/anthropic.d.ts +18 -0
- package/dist/types/compat/apply.d.ts +7 -0
- package/dist/types/compat/bedrock.d.ts +3 -0
- package/dist/types/compat/devin.d.ts +9 -0
- package/dist/types/compat/openai.d.ts +28 -0
- package/dist/types/discovery/antigravity.d.ts +70 -0
- package/dist/types/discovery/codex.d.ts +36 -0
- package/dist/types/discovery/cursor-proto.d.ts +5368 -0
- package/dist/types/discovery/cursor.d.ts +23 -0
- package/dist/types/discovery/devin-proto.d.ts +1549 -0
- package/dist/types/discovery/devin.d.ts +24 -0
- package/dist/types/discovery/gemini-cli.d.ts +33 -0
- package/dist/types/discovery/gemini.d.ts +25 -0
- package/dist/types/discovery/gitlab-duo-workflow.d.ts +30 -0
- package/dist/types/discovery/index.d.ts +7 -0
- package/dist/types/discovery/openai-compatible.d.ts +89 -0
- package/dist/types/discovery/protobuf.d.ts +102 -0
- package/dist/types/effort.d.ts +10 -0
- package/dist/types/fireworks-model-id.d.ts +23 -0
- package/dist/types/hosts.d.ts +169 -0
- package/dist/types/identity/bundled.d.ts +3 -0
- package/dist/types/identity/classify.d.ts +69 -0
- package/dist/types/identity/dialect.d.ts +3 -0
- package/dist/types/identity/family.d.ts +239 -0
- package/dist/types/identity/id.d.ts +12 -0
- package/dist/types/identity/index.d.ts +8 -0
- package/dist/types/identity/markers.d.ts +4 -0
- package/dist/types/identity/priority.d.ts +1 -0
- package/dist/types/identity/reference.d.ts +29 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/model-cache.d.ts +23 -0
- package/dist/types/model-manager.d.ts +77 -0
- package/dist/types/model-thinking.d.ts +70 -0
- package/dist/types/model-tokenizer.d.ts +8 -0
- package/dist/types/models.d.ts +14 -0
- package/dist/types/openai-pricing.d.ts +17 -0
- package/dist/types/provider-models/bundled-references.d.ts +13 -0
- package/dist/types/provider-models/cache-provider-id.d.ts +11 -0
- package/dist/types/provider-models/descriptor-types.d.ts +76 -0
- package/dist/types/provider-models/descriptors.d.ts +505 -0
- package/dist/types/provider-models/google.d.ts +29 -0
- package/dist/types/provider-models/index.d.ts +7 -0
- package/dist/types/provider-models/ollama.d.ts +22 -0
- package/dist/types/provider-models/openai-compat.d.ts +646 -0
- package/dist/types/provider-models/special.d.ts +53 -0
- package/dist/types/types.d.ts +876 -0
- package/dist/types/utils.d.ts +20 -0
- package/dist/types/variant-collapse.d.ts +137 -0
- package/dist/types/wire/alibaba-token-plan.d.ts +22 -0
- package/dist/types/wire/codex.d.ts +66 -0
- package/dist/types/wire/coreweave.d.ts +12 -0
- package/dist/types/wire/gemini-headers.d.ts +62 -0
- package/dist/types/wire/github-copilot.d.ts +47 -0
- package/dist/types/wire/image-fetchers.d.ts +76 -0
- package/package.json +102 -0
- package/src/build.ts +103 -0
- package/src/compat/anthropic.ts +179 -0
- package/src/compat/apply.ts +15 -0
- package/src/compat/bedrock.ts +153 -0
- package/src/compat/devin.ts +12 -0
- package/src/compat/openai.ts +864 -0
- package/src/discovery/antigravity.ts +257 -0
- package/src/discovery/codex.ts +456 -0
- package/src/discovery/cursor-proto.ts +8329 -0
- package/src/discovery/cursor.ts +409 -0
- package/src/discovery/devin-proto.ts +2037 -0
- package/src/discovery/devin.ts +152 -0
- package/src/discovery/gemini-cli.ts +198 -0
- package/src/discovery/gemini.ts +260 -0
- package/src/discovery/gitlab-duo-workflow.ts +859 -0
- package/src/discovery/index.ts +7 -0
- package/src/discovery/openai-compatible.ts +266 -0
- package/src/discovery/protobuf.ts +1073 -0
- package/src/effort.ts +18 -0
- package/src/fireworks-model-id.ts +50 -0
- package/src/hosts.ts +178 -0
- package/src/identity/bundled.ts +28 -0
- package/src/identity/classify.ts +242 -0
- package/src/identity/dialect.ts +42 -0
- package/src/identity/family.ts +477 -0
- package/src/identity/id.ts +81 -0
- package/src/identity/index.ts +8 -0
- package/src/identity/markers.ts +49 -0
- package/src/identity/priority.ts +59 -0
- package/src/identity/reference.ts +167 -0
- package/src/index.ts +19 -0
- package/src/model-cache.ts +346 -0
- package/src/model-manager.ts +683 -0
- package/src/model-thinking.ts +925 -0
- package/src/model-tokenizer.ts +75 -0
- package/src/models.json +304657 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +108 -0
- package/src/openai-pricing.ts +29 -0
- package/src/provider-models/bundled-references.ts +87 -0
- package/src/provider-models/cache-provider-id.ts +97 -0
- package/src/provider-models/descriptor-types.ts +85 -0
- package/src/provider-models/descriptors.ts +585 -0
- package/src/provider-models/google.ts +117 -0
- package/src/provider-models/index.ts +7 -0
- package/src/provider-models/ollama.ts +204 -0
- package/src/provider-models/openai-compat.ts +6405 -0
- package/src/provider-models/special.ts +213 -0
- package/src/types.ts +1032 -0
- package/src/utils.ts +69 -0
- package/src/variant-collapse.ts +1555 -0
- package/src/wire/alibaba-token-plan.ts +54 -0
- package/src/wire/codex.ts +117 -0
- package/src/wire/coreweave.ts +51 -0
- package/src/wire/gemini-headers.ts +135 -0
- package/src/wire/github-copilot.ts +147 -0
- 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
|
+
}
|