@centerforagenticai/pi-multi-account 0.1.1
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/LICENSE +21 -0
- package/NOTICE +29 -0
- package/README.md +999 -0
- package/config/models/pi-multi-account.v1.json +32 -0
- package/config/subscription-plans.v1.json +122 -0
- package/package.json +76 -0
- package/packages/pi-anthropic-oauth/LICENSE +21 -0
- package/packages/pi-anthropic-oauth/package.json +54 -0
- package/packages/pi-anthropic-oauth/src/auth.ts +396 -0
- package/packages/pi-anthropic-oauth/src/context.ts +116 -0
- package/packages/pi-anthropic-oauth/src/convert.ts +303 -0
- package/packages/pi-anthropic-oauth/src/index.ts +37 -0
- package/packages/pi-anthropic-oauth/src/prompt.ts +137 -0
- package/packages/pi-anthropic-oauth/src/stream.ts +476 -0
- package/packages/pi-antigravity/LICENSE +21 -0
- package/packages/pi-antigravity/package.json +77 -0
- package/packages/pi-antigravity/src/auth/index.ts +14 -0
- package/packages/pi-antigravity/src/auth/oauth.ts +442 -0
- package/packages/pi-antigravity/src/client/client.ts +561 -0
- package/packages/pi-antigravity/src/client/index.ts +1 -0
- package/packages/pi-antigravity/src/context.ts +110 -0
- package/packages/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
- package/packages/pi-antigravity/src/diagnostics/index.ts +1 -0
- package/packages/pi-antigravity/src/image/image.ts +336 -0
- package/packages/pi-antigravity/src/image/index.ts +1 -0
- package/packages/pi-antigravity/src/index.ts +280 -0
- package/packages/pi-antigravity/src/models/discovery.ts +154 -0
- package/packages/pi-antigravity/src/models/grouping.ts +424 -0
- package/packages/pi-antigravity/src/models/index.ts +3 -0
- package/packages/pi-antigravity/src/models/models.ts +500 -0
- package/packages/pi-antigravity/src/stream/index.ts +1 -0
- package/packages/pi-antigravity/src/stream/stream.ts +1478 -0
- package/packages/pi-antigravity/src/types/enums.ts +42 -0
- package/packages/pi-antigravity/src/types/index.ts +2 -0
- package/packages/pi-antigravity/src/types/types.ts +292 -0
- package/packages/pi-antigravity/src/usage/index.ts +1 -0
- package/packages/pi-antigravity/src/usage/usage.ts +416 -0
- package/packages/pi-antigravity/src/utils/http.ts +91 -0
- package/packages/pi-antigravity/src/utils/index.ts +3 -0
- package/packages/pi-antigravity/src/utils/security.ts +73 -0
- package/packages/pi-antigravity/src/utils/util.ts +132 -0
- package/scripts/multi-account.mjs +44 -0
- package/src/account-labels.ts +223 -0
- package/src/account-plan-assignment.ts +340 -0
- package/src/account-rate-history.ts +372 -0
- package/src/anthropic-adaptive-stream.ts +531 -0
- package/src/anthropic-alias-stream.ts +140 -0
- package/src/anthropic-context-compat.ts +80 -0
- package/src/api-pricing.ts +579 -0
- package/src/bounded-file-lines.ts +97 -0
- package/src/catalog-rebinding.ts +177 -0
- package/src/catalog-registration-probe.ts +111 -0
- package/src/codex-adapter.ts +345 -0
- package/src/codex-model-defaults.ts +785 -0
- package/src/command-completions.ts +404 -0
- package/src/commands.ts +2000 -0
- package/src/compaction.ts +14 -0
- package/src/config.ts +1317 -0
- package/src/continuation.ts +569 -0
- package/src/cooldowns.ts +110 -0
- package/src/cost-digest-store.ts +332 -0
- package/src/cost-digest.ts +1044 -0
- package/src/cost-history.ts +251 -0
- package/src/cost-period-closer.ts +160 -0
- package/src/cost-report-json.ts +318 -0
- package/src/cost-report-reader.ts +368 -0
- package/src/cost-report-render.ts +207 -0
- package/src/cost-report.ts +1104 -0
- package/src/coverage-attestation.ts +397 -0
- package/src/credential-lifecycle.ts +169 -0
- package/src/credential-refresh.ts +248 -0
- package/src/declaration-notice-marker.ts +238 -0
- package/src/diagnostic-store.ts +276 -0
- package/src/diagnostics.ts +309 -0
- package/src/discovery.ts +471 -0
- package/src/duration.ts +13 -0
- package/src/error-classification.ts +256 -0
- package/src/fuzzy.ts +15 -0
- package/src/group-policy.ts +81 -0
- package/src/history-store.ts +897 -0
- package/src/index.ts +5572 -0
- package/src/lifecycle.ts +378 -0
- package/src/logical-dispatch.ts +279 -0
- package/src/logical-model-selector.ts +254 -0
- package/src/logical-model-switcher.ts +430 -0
- package/src/logical-provider-attribution.ts +544 -0
- package/src/logical-provider.ts +1237 -0
- package/src/logical-route-indicator.ts +215 -0
- package/src/machine-lease.ts +445 -0
- package/src/model-support.ts +66 -0
- package/src/models-declaration.ts +1091 -0
- package/src/openai-adapter.ts +117 -0
- package/src/openrouter-budget.ts +304 -0
- package/src/openrouter-fallback.ts +146 -0
- package/src/period-boundaries.ts +376 -0
- package/src/pi-anthropic-oauth.d.ts +6 -0
- package/src/preflight.ts +253 -0
- package/src/pricing-cache.ts +235 -0
- package/src/project-identity.ts +100 -0
- package/src/provider-registration.ts +942 -0
- package/src/rate-formula.ts +163 -0
- package/src/recovery-engine.ts +853 -0
- package/src/recovery-output.ts +837 -0
- package/src/recovery-plan.ts +239 -0
- package/src/report-range.ts +203 -0
- package/src/route-resolver.ts +789 -0
- package/src/routing-config-transaction.ts +232 -0
- package/src/routing.ts +1163 -0
- package/src/runtime-state.ts +630 -0
- package/src/session-account-groups.ts +284 -0
- package/src/session-restore.ts +287 -0
- package/src/shared-usage.ts +1392 -0
- package/src/standalone-cli.ts +720 -0
- package/src/status-view.ts +578 -0
- package/src/subscription-plan-catalog.ts +346 -0
- package/src/tier-model-resolver.ts +46 -0
- package/src/upstream-anthropic.ts +315 -0
- package/src/upstream-antigravity.ts +327 -0
- package/src/usage-fetch.ts +1634 -0
- package/src/usage.ts +1026 -0
- package/src/vendor.ts +87 -0
- package/src/warmer.ts +231 -0
- package/src/watchdog.ts +219 -0
- package/src/window-history.ts +270 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { Compile } from "typebox/compile";
|
|
3
|
+
import {
|
|
4
|
+
buildModelDeclaration,
|
|
5
|
+
type BuiltDeclaration,
|
|
6
|
+
type ModelDeclarationCatalogs,
|
|
7
|
+
} from "./models-declaration.js";
|
|
8
|
+
|
|
9
|
+
/** Operator-approved context-window default for the bounded live Codex allowlist. */
|
|
10
|
+
export const CODEX_CONTEXT_WINDOW = 1_050_000;
|
|
11
|
+
|
|
12
|
+
export const CODEX_LONG_CONTEXT_MODEL_IDS = [
|
|
13
|
+
"gpt-5.4",
|
|
14
|
+
"gpt-5.5",
|
|
15
|
+
"gpt-5.6-sol",
|
|
16
|
+
"gpt-5.6-terra",
|
|
17
|
+
"gpt-5.6-luna",
|
|
18
|
+
"gpt-6-astra",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
const CODEX_CONTEXT_WINDOW_BY_MODEL_ID: ReadonlyMap<string, number> = new Map(
|
|
22
|
+
CODEX_LONG_CONTEXT_MODEL_IDS.map(
|
|
23
|
+
(modelId) => [modelId, CODEX_CONTEXT_WINDOW] as const,
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const CODEX_MODEL_DOCUMENTATION_BASE_URL =
|
|
28
|
+
"https://developers.openai.com/api/docs/models/";
|
|
29
|
+
const CODEX_MODEL_DOCUMENTATION_MAX_CANDIDATES = 8;
|
|
30
|
+
const CODEX_MODEL_DOCUMENTATION_MAX_BYTES = 64 * 1024;
|
|
31
|
+
const CODEX_MODEL_DOCUMENTATION_TIMEOUT_MS = 2_500;
|
|
32
|
+
const UTC_DAY_MS = 24 * 60 * 60 * 1_000;
|
|
33
|
+
const CODEX_MODEL_ID_MAX_LENGTH = 128;
|
|
34
|
+
const CODEX_MODEL_ID = /^(?=.*\d)[a-z0-9]+(?:[.-][a-z0-9]+)*$/u;
|
|
35
|
+
|
|
36
|
+
type CodexEvidenceFailure =
|
|
37
|
+
| "invalid-id"
|
|
38
|
+
| "candidate-limit"
|
|
39
|
+
| "unavailable"
|
|
40
|
+
| "redirect"
|
|
41
|
+
| "response-size"
|
|
42
|
+
| "metadata"
|
|
43
|
+
| "network-or-timeout";
|
|
44
|
+
|
|
45
|
+
type CodexEvidenceOutcome =
|
|
46
|
+
| { readonly status: "verified"; readonly modelId: string }
|
|
47
|
+
| { readonly status: "skipped"; readonly reason: CodexEvidenceFailure };
|
|
48
|
+
|
|
49
|
+
export type CodexLongContextResolution = {
|
|
50
|
+
readonly contextWindowByModelId: ReadonlyMap<string, number>;
|
|
51
|
+
readonly liveCompleteOfflineModelIds: ReadonlySet<string>;
|
|
52
|
+
readonly diagnostics: readonly string[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type CodexLongContextResolutionOptions = {
|
|
56
|
+
/** Test seam and explicit transport boundary. Production uses global fetch. */
|
|
57
|
+
readonly fetchDocumentation?: typeof fetch;
|
|
58
|
+
/** Deterministic clock seam for the UTC-day candidate rotation. */
|
|
59
|
+
readonly now?: () => number;
|
|
60
|
+
/** Test-only lower bounds; callers cannot raise the production caps. */
|
|
61
|
+
readonly maxCandidates?: number;
|
|
62
|
+
readonly maxResponseBytes?: number;
|
|
63
|
+
readonly timeoutMs?: number;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type CodexModelDefaultChange = {
|
|
67
|
+
readonly modelId: string;
|
|
68
|
+
readonly previousContextWindow: number | undefined;
|
|
69
|
+
readonly contextWindow: number | undefined;
|
|
70
|
+
readonly hadOverride: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type CodexModelDefaultsPlan = {
|
|
74
|
+
readonly providers: Record<string, Record<string, unknown>>;
|
|
75
|
+
readonly changes: readonly CodexModelDefaultChange[];
|
|
76
|
+
readonly contextWindowByModelId: ReadonlyMap<string, number>;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
class CodexEvidenceError extends Error {
|
|
80
|
+
readonly reason: CodexEvidenceFailure;
|
|
81
|
+
|
|
82
|
+
constructor(reason: CodexEvidenceFailure) {
|
|
83
|
+
super(reason);
|
|
84
|
+
this.reason = reason;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function lowerBoundedLimit(
|
|
89
|
+
value: number | undefined,
|
|
90
|
+
productionMaximum: number,
|
|
91
|
+
): number {
|
|
92
|
+
return Number.isSafeInteger(value) && (value ?? 0) > 0
|
|
93
|
+
? Math.min(value as number, productionMaximum)
|
|
94
|
+
: productionMaximum;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isRestrictedModelId(modelId: string): boolean {
|
|
98
|
+
return (
|
|
99
|
+
modelId.length <= CODEX_MODEL_ID_MAX_LENGTH && CODEX_MODEL_ID.test(modelId)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function selectBoundedCandidates(
|
|
104
|
+
candidates: readonly string[],
|
|
105
|
+
maximumCandidates: number,
|
|
106
|
+
nowMs: number,
|
|
107
|
+
): readonly string[] {
|
|
108
|
+
if (candidates.length <= maximumCandidates) return candidates;
|
|
109
|
+
const utcDay = Number.isFinite(nowMs) ? Math.floor(nowMs / UTC_DAY_MS) : 0;
|
|
110
|
+
const start = ((utcDay % candidates.length) + candidates.length) % candidates.length;
|
|
111
|
+
return Array.from(
|
|
112
|
+
{ length: maximumCandidates },
|
|
113
|
+
(_value, index) => candidates[(start + index) % candidates.length] as string,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
118
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function safeCatalogFacts(model: unknown):
|
|
122
|
+
| { readonly id: string; readonly contextWindow: number | undefined }
|
|
123
|
+
| undefined {
|
|
124
|
+
try {
|
|
125
|
+
if (typeof model !== "object" || model === null) return undefined;
|
|
126
|
+
const descriptors = Object.getOwnPropertyDescriptors(model);
|
|
127
|
+
const idDescriptor = descriptors.id;
|
|
128
|
+
if (
|
|
129
|
+
idDescriptor === undefined ||
|
|
130
|
+
!("value" in idDescriptor) ||
|
|
131
|
+
typeof idDescriptor.value !== "string"
|
|
132
|
+
) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
const contextWindowDescriptor = descriptors.contextWindow;
|
|
136
|
+
const contextWindow =
|
|
137
|
+
contextWindowDescriptor !== undefined &&
|
|
138
|
+
"value" in contextWindowDescriptor &&
|
|
139
|
+
typeof contextWindowDescriptor.value === "number"
|
|
140
|
+
? contextWindowDescriptor.value
|
|
141
|
+
: undefined;
|
|
142
|
+
return { id: idDescriptor.value, contextWindow };
|
|
143
|
+
} catch {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function readBoundedDocumentation(
|
|
149
|
+
response: Response,
|
|
150
|
+
maximumBytes: number,
|
|
151
|
+
signal: AbortSignal,
|
|
152
|
+
): Promise<string> {
|
|
153
|
+
const contentLength = response.headers.get("content-length");
|
|
154
|
+
if (
|
|
155
|
+
contentLength !== null &&
|
|
156
|
+
/^\d+$/u.test(contentLength) &&
|
|
157
|
+
Number(contentLength) > maximumBytes
|
|
158
|
+
) {
|
|
159
|
+
await response.body?.cancel();
|
|
160
|
+
throw new CodexEvidenceError("response-size");
|
|
161
|
+
}
|
|
162
|
+
if (response.body === null) return "";
|
|
163
|
+
|
|
164
|
+
const reader = response.body.getReader();
|
|
165
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
166
|
+
let byteCount = 0;
|
|
167
|
+
let text = "";
|
|
168
|
+
let rejectAborted: ((reason?: unknown) => void) | undefined;
|
|
169
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
170
|
+
rejectAborted = reject;
|
|
171
|
+
});
|
|
172
|
+
const onAbort = () => rejectAborted?.(new CodexEvidenceError("network-or-timeout"));
|
|
173
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
174
|
+
try {
|
|
175
|
+
signal.throwIfAborted();
|
|
176
|
+
while (true) {
|
|
177
|
+
const chunk = await Promise.race([reader.read(), aborted]);
|
|
178
|
+
if (chunk.done) break;
|
|
179
|
+
byteCount += chunk.value.byteLength;
|
|
180
|
+
if (byteCount > maximumBytes) {
|
|
181
|
+
await reader.cancel();
|
|
182
|
+
throw new CodexEvidenceError("response-size");
|
|
183
|
+
}
|
|
184
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
185
|
+
}
|
|
186
|
+
text += decoder.decode();
|
|
187
|
+
return text;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
await reader.cancel().catch(() => undefined);
|
|
190
|
+
throw error;
|
|
191
|
+
} finally {
|
|
192
|
+
signal.removeEventListener("abort", onAbort);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function visibleMarkdownLines(markdown: string): string[] | undefined {
|
|
197
|
+
const lines = markdown.split(/\r?\n/u);
|
|
198
|
+
let fence: { readonly character: "`" | "~"; readonly length: number } | undefined;
|
|
199
|
+
let inHtmlComment = false;
|
|
200
|
+
let hasRawHtml = false;
|
|
201
|
+
const visibleLines = lines.map((line) => {
|
|
202
|
+
if (fence !== undefined) {
|
|
203
|
+
const closing = line.match(/^ {0,3}(`+|~+)[ \t]*$/u)?.[1];
|
|
204
|
+
if (
|
|
205
|
+
closing !== undefined &&
|
|
206
|
+
closing[0] === fence.character &&
|
|
207
|
+
closing.length >= fence.length
|
|
208
|
+
) {
|
|
209
|
+
fence = undefined;
|
|
210
|
+
}
|
|
211
|
+
return "";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (inHtmlComment) {
|
|
215
|
+
if (line.includes("-->")) inHtmlComment = false;
|
|
216
|
+
return "";
|
|
217
|
+
}
|
|
218
|
+
const commentStart = line.indexOf("<!--");
|
|
219
|
+
if (commentStart >= 0) {
|
|
220
|
+
if (!line.slice(commentStart + 4).includes("-->")) inHtmlComment = true;
|
|
221
|
+
return "";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const opening = line.match(/^ {0,3}(`{3,}|~{3,})/u)?.[1];
|
|
225
|
+
if (opening !== undefined) {
|
|
226
|
+
fence = {
|
|
227
|
+
character: opening[0] as "`" | "~",
|
|
228
|
+
length: opening.length,
|
|
229
|
+
};
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
/^ {0,3}(?:<\?|<!\[CDATA\[|<![A-Z])/iu.test(line) ||
|
|
235
|
+
/^ {0,3}<\/?[A-Za-z][A-Za-z0-9-]*(?=[\s>/]|$)/u.test(line)
|
|
236
|
+
) {
|
|
237
|
+
hasRawHtml = true;
|
|
238
|
+
return "";
|
|
239
|
+
}
|
|
240
|
+
return line;
|
|
241
|
+
});
|
|
242
|
+
return hasRawHtml ? undefined : visibleLines;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function hasExactLongContextEvidence(markdown: string, modelId: string): boolean {
|
|
246
|
+
const lines = visibleMarkdownLines(markdown);
|
|
247
|
+
if (lines === undefined) return false;
|
|
248
|
+
// Treat every visible Model ID mention as metadata. Counting only the exact
|
|
249
|
+
// line would accept an alias page that also mentioned the requested ID.
|
|
250
|
+
const modelIdEntries = lines.flatMap((line, index) =>
|
|
251
|
+
line.includes("Model ID:") ? [{ line, index }] : [],
|
|
252
|
+
);
|
|
253
|
+
if (
|
|
254
|
+
modelIdEntries.length !== 1 ||
|
|
255
|
+
modelIdEntries[0]?.line !== `Model ID: \`${modelId}\``
|
|
256
|
+
) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const detailHeadings = lines
|
|
261
|
+
.map((line, index) => (line === "## Model details" ? index : -1))
|
|
262
|
+
.filter((index) => index >= 0);
|
|
263
|
+
if (
|
|
264
|
+
detailHeadings.length !== 1 ||
|
|
265
|
+
(modelIdEntries[0]?.index ?? Number.POSITIVE_INFINITY) >=
|
|
266
|
+
(detailHeadings[0] ?? Number.NEGATIVE_INFINITY)
|
|
267
|
+
) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
const detailsStart = (detailHeadings[0] ?? -1) + 1;
|
|
271
|
+
let detailsEnd = lines.length;
|
|
272
|
+
for (let index = detailsStart; index < lines.length; index += 1) {
|
|
273
|
+
const line = lines[index] ?? "";
|
|
274
|
+
if (/^ {0,3}#{1,2}(?:[ \t]+|$)/u.test(line)) {
|
|
275
|
+
detailsEnd = index;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
if (
|
|
279
|
+
/^ {0,3}(?:=+|-+)[ \t]*$/u.test(line) &&
|
|
280
|
+
(lines[index - 1]?.trim().length ?? 0) > 0
|
|
281
|
+
) {
|
|
282
|
+
detailsEnd = Math.max(detailsStart, index - 1);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Any second context-window claim makes the model details ambiguous, even if
|
|
287
|
+
// it uses a different number format (for example `1M` or `200,000-token`).
|
|
288
|
+
const contextWindowLines = lines
|
|
289
|
+
.slice(detailsStart, detailsEnd)
|
|
290
|
+
.filter((line) => /context(?:[\s-]+)window/iu.test(line));
|
|
291
|
+
return (
|
|
292
|
+
contextWindowLines.length === 1 &&
|
|
293
|
+
contextWindowLines[0] === "- 1,050,000 context window"
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function inspectOfficialModelDocumentation(
|
|
298
|
+
modelId: string,
|
|
299
|
+
fetchDocumentation: typeof fetch,
|
|
300
|
+
maximumBytes: number,
|
|
301
|
+
timeoutMs: number,
|
|
302
|
+
): Promise<CodexEvidenceOutcome> {
|
|
303
|
+
const controller = new AbortController();
|
|
304
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
305
|
+
const timedOut = new Promise<never>((_resolve, reject) => {
|
|
306
|
+
timeout = setTimeout(() => {
|
|
307
|
+
controller.abort();
|
|
308
|
+
reject(new CodexEvidenceError("network-or-timeout"));
|
|
309
|
+
}, timeoutMs);
|
|
310
|
+
});
|
|
311
|
+
try {
|
|
312
|
+
return await Promise.race([
|
|
313
|
+
(async (): Promise<CodexEvidenceOutcome> => {
|
|
314
|
+
const response = await fetchDocumentation(
|
|
315
|
+
`${CODEX_MODEL_DOCUMENTATION_BASE_URL}${modelId}.md`,
|
|
316
|
+
{
|
|
317
|
+
method: "GET",
|
|
318
|
+
redirect: "error",
|
|
319
|
+
credentials: "omit",
|
|
320
|
+
cache: "no-store",
|
|
321
|
+
headers: { Accept: "text/markdown" },
|
|
322
|
+
signal: controller.signal,
|
|
323
|
+
},
|
|
324
|
+
);
|
|
325
|
+
if (response.redirected) {
|
|
326
|
+
return { status: "skipped", reason: "redirect" };
|
|
327
|
+
}
|
|
328
|
+
if (response.status !== 200) {
|
|
329
|
+
return { status: "skipped", reason: "unavailable" };
|
|
330
|
+
}
|
|
331
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
332
|
+
if (!/^text\/markdown(?:;|$)/iu.test(contentType)) {
|
|
333
|
+
return { status: "skipped", reason: "metadata" };
|
|
334
|
+
}
|
|
335
|
+
const markdown = await readBoundedDocumentation(
|
|
336
|
+
response,
|
|
337
|
+
maximumBytes,
|
|
338
|
+
controller.signal,
|
|
339
|
+
);
|
|
340
|
+
return hasExactLongContextEvidence(markdown, modelId)
|
|
341
|
+
? { status: "verified", modelId }
|
|
342
|
+
: { status: "skipped", reason: "metadata" };
|
|
343
|
+
})(),
|
|
344
|
+
timedOut,
|
|
345
|
+
]);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return {
|
|
348
|
+
status: "skipped",
|
|
349
|
+
reason:
|
|
350
|
+
error instanceof CodexEvidenceError
|
|
351
|
+
? error.reason
|
|
352
|
+
: "network-or-timeout",
|
|
353
|
+
};
|
|
354
|
+
} finally {
|
|
355
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
356
|
+
controller.abort();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function renderEvidenceDiagnostics(
|
|
361
|
+
verifiedCount: number,
|
|
362
|
+
failures: ReadonlyMap<CodexEvidenceFailure, number>,
|
|
363
|
+
): readonly string[] {
|
|
364
|
+
const diagnostics: string[] = [];
|
|
365
|
+
if (verifiedCount > 0) {
|
|
366
|
+
diagnostics.push(
|
|
367
|
+
`verified ${verifiedCount} additional Codex ${verifiedCount === 1 ? "model" : "models"} from exact official model documentation`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const orderedReasons: readonly CodexEvidenceFailure[] = [
|
|
371
|
+
"invalid-id",
|
|
372
|
+
"candidate-limit",
|
|
373
|
+
"unavailable",
|
|
374
|
+
"redirect",
|
|
375
|
+
"response-size",
|
|
376
|
+
"metadata",
|
|
377
|
+
"network-or-timeout",
|
|
378
|
+
];
|
|
379
|
+
const skippedCount = orderedReasons.reduce(
|
|
380
|
+
(total, reason) => total + (failures.get(reason) ?? 0),
|
|
381
|
+
0,
|
|
382
|
+
);
|
|
383
|
+
if (skippedCount > 0) {
|
|
384
|
+
const reasonSummary = orderedReasons
|
|
385
|
+
.flatMap((reason) => {
|
|
386
|
+
const count = failures.get(reason) ?? 0;
|
|
387
|
+
return count > 0 ? [`${reason}: ${count}`] : [];
|
|
388
|
+
})
|
|
389
|
+
.join(", ");
|
|
390
|
+
diagnostics.push(
|
|
391
|
+
`skipped ${skippedCount} unverified Codex documentation ${skippedCount === 1 ? "candidate" : "candidates"} (${reasonSummary})`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
return diagnostics;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Resolve offline-approved defaults plus bounded, exact official-document evidence
|
|
399
|
+
* for new IDs already present in the live Codex catalog.
|
|
400
|
+
*/
|
|
401
|
+
export async function resolveCodexLongContextDefaults(
|
|
402
|
+
catalog: ModelDeclarationCatalogs["openai-codex"],
|
|
403
|
+
options: CodexLongContextResolutionOptions = {},
|
|
404
|
+
): Promise<CodexLongContextResolution> {
|
|
405
|
+
const maximumCandidates = lowerBoundedLimit(
|
|
406
|
+
options.maxCandidates,
|
|
407
|
+
CODEX_MODEL_DOCUMENTATION_MAX_CANDIDATES,
|
|
408
|
+
);
|
|
409
|
+
const maximumBytes = lowerBoundedLimit(
|
|
410
|
+
options.maxResponseBytes,
|
|
411
|
+
CODEX_MODEL_DOCUMENTATION_MAX_BYTES,
|
|
412
|
+
);
|
|
413
|
+
const timeoutMs = lowerBoundedLimit(
|
|
414
|
+
options.timeoutMs,
|
|
415
|
+
CODEX_MODEL_DOCUMENTATION_TIMEOUT_MS,
|
|
416
|
+
);
|
|
417
|
+
const failures = new Map<CodexEvidenceFailure, number>();
|
|
418
|
+
const addFailure = (reason: CodexEvidenceFailure, count = 1): void => {
|
|
419
|
+
failures.set(reason, (failures.get(reason) ?? 0) + count);
|
|
420
|
+
};
|
|
421
|
+
const resolved = new Map(CODEX_CONTEXT_WINDOW_BY_MODEL_ID);
|
|
422
|
+
const liveCompleteOfflineModelIds = new Set<string>();
|
|
423
|
+
const seen = new Set<string>();
|
|
424
|
+
const candidates: string[] = [];
|
|
425
|
+
for (const model of catalog) {
|
|
426
|
+
const facts = safeCatalogFacts(model);
|
|
427
|
+
if (facts === undefined || seen.has(facts.id)) continue;
|
|
428
|
+
seen.add(facts.id);
|
|
429
|
+
const isOfflineApproved = CODEX_CONTEXT_WINDOW_BY_MODEL_ID.has(facts.id);
|
|
430
|
+
const hasRestrictedId = isRestrictedModelId(facts.id);
|
|
431
|
+
const providerContextWindow = facts.contextWindow;
|
|
432
|
+
if (
|
|
433
|
+
typeof providerContextWindow === "number" &&
|
|
434
|
+
Number.isFinite(providerContextWindow) &&
|
|
435
|
+
providerContextWindow >= CODEX_CONTEXT_WINDOW
|
|
436
|
+
) {
|
|
437
|
+
if (isOfflineApproved || hasRestrictedId) {
|
|
438
|
+
// The live catalog already carries sufficient context. Preserve its exact
|
|
439
|
+
// value in the unified copy and do not create a physical override.
|
|
440
|
+
resolved.delete(facts.id);
|
|
441
|
+
if (isOfflineApproved) liveCompleteOfflineModelIds.add(facts.id);
|
|
442
|
+
} else {
|
|
443
|
+
addFailure("invalid-id");
|
|
444
|
+
}
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (isOfflineApproved) continue;
|
|
448
|
+
if (!hasRestrictedId) {
|
|
449
|
+
addFailure("invalid-id");
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
candidates.push(facts.id);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const boundedCandidates = selectBoundedCandidates(
|
|
456
|
+
candidates,
|
|
457
|
+
maximumCandidates,
|
|
458
|
+
(options.now ?? Date.now)(),
|
|
459
|
+
);
|
|
460
|
+
if (candidates.length > boundedCandidates.length) {
|
|
461
|
+
addFailure("candidate-limit", candidates.length - boundedCandidates.length);
|
|
462
|
+
}
|
|
463
|
+
const fetchDocumentation = options.fetchDocumentation ?? globalThis.fetch;
|
|
464
|
+
const outcomes = await Promise.all(
|
|
465
|
+
boundedCandidates.map((modelId) =>
|
|
466
|
+
inspectOfficialModelDocumentation(
|
|
467
|
+
modelId,
|
|
468
|
+
fetchDocumentation,
|
|
469
|
+
maximumBytes,
|
|
470
|
+
timeoutMs,
|
|
471
|
+
),
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
let verifiedCount = 0;
|
|
475
|
+
for (const outcome of outcomes) {
|
|
476
|
+
if (outcome.status === "verified") {
|
|
477
|
+
resolved.set(outcome.modelId, CODEX_CONTEXT_WINDOW);
|
|
478
|
+
verifiedCount += 1;
|
|
479
|
+
} else {
|
|
480
|
+
addFailure(outcome.reason);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
contextWindowByModelId: resolved,
|
|
485
|
+
liveCompleteOfflineModelIds,
|
|
486
|
+
diagnostics: renderEvidenceDiagnostics(verifiedCount, failures),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Mirrors Pi 0.84.4's private `ModelOverrideSchema`. The host does not export
|
|
492
|
+
* this validator; keep it aligned when the exact host dependency pin moves.
|
|
493
|
+
*/
|
|
494
|
+
const PercentileCutoffsSchema = Type.Object({
|
|
495
|
+
p50: Type.Optional(Type.Number()),
|
|
496
|
+
p75: Type.Optional(Type.Number()),
|
|
497
|
+
p90: Type.Optional(Type.Number()),
|
|
498
|
+
p99: Type.Optional(Type.Number()),
|
|
499
|
+
});
|
|
500
|
+
const OpenRouterRoutingSchema = Type.Object({
|
|
501
|
+
allow_fallbacks: Type.Optional(Type.Boolean()),
|
|
502
|
+
require_parameters: Type.Optional(Type.Boolean()),
|
|
503
|
+
data_collection: Type.Optional(
|
|
504
|
+
Type.Union([Type.Literal("deny"), Type.Literal("allow")]),
|
|
505
|
+
),
|
|
506
|
+
zdr: Type.Optional(Type.Boolean()),
|
|
507
|
+
enforce_distillable_text: Type.Optional(Type.Boolean()),
|
|
508
|
+
order: Type.Optional(Type.Array(Type.String())),
|
|
509
|
+
only: Type.Optional(Type.Array(Type.String())),
|
|
510
|
+
ignore: Type.Optional(Type.Array(Type.String())),
|
|
511
|
+
quantizations: Type.Optional(Type.Array(Type.String())),
|
|
512
|
+
sort: Type.Optional(
|
|
513
|
+
Type.Union([
|
|
514
|
+
Type.String(),
|
|
515
|
+
Type.Object({
|
|
516
|
+
by: Type.Optional(Type.String()),
|
|
517
|
+
partition: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
518
|
+
}),
|
|
519
|
+
]),
|
|
520
|
+
),
|
|
521
|
+
max_price: Type.Optional(
|
|
522
|
+
Type.Object({
|
|
523
|
+
prompt: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
|
524
|
+
completion: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
|
525
|
+
image: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
|
526
|
+
audio: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
|
527
|
+
request: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
|
528
|
+
}),
|
|
529
|
+
),
|
|
530
|
+
preferred_min_throughput: Type.Optional(
|
|
531
|
+
Type.Union([Type.Number(), PercentileCutoffsSchema]),
|
|
532
|
+
),
|
|
533
|
+
preferred_max_latency: Type.Optional(
|
|
534
|
+
Type.Union([Type.Number(), PercentileCutoffsSchema]),
|
|
535
|
+
),
|
|
536
|
+
});
|
|
537
|
+
const VercelGatewayRoutingSchema = Type.Object({
|
|
538
|
+
only: Type.Optional(Type.Array(Type.String())),
|
|
539
|
+
order: Type.Optional(Type.Array(Type.String())),
|
|
540
|
+
});
|
|
541
|
+
const ThinkingLevelMapValueSchema = Type.Union([Type.String(), Type.Null()]);
|
|
542
|
+
const ThinkingLevelMapSchema = Type.Object({
|
|
543
|
+
off: Type.Optional(ThinkingLevelMapValueSchema),
|
|
544
|
+
minimal: Type.Optional(ThinkingLevelMapValueSchema),
|
|
545
|
+
low: Type.Optional(ThinkingLevelMapValueSchema),
|
|
546
|
+
medium: Type.Optional(ThinkingLevelMapValueSchema),
|
|
547
|
+
high: Type.Optional(ThinkingLevelMapValueSchema),
|
|
548
|
+
xhigh: Type.Optional(ThinkingLevelMapValueSchema),
|
|
549
|
+
max: Type.Optional(ThinkingLevelMapValueSchema),
|
|
550
|
+
});
|
|
551
|
+
const ChatTemplateKwargScalarSchema = Type.Union([
|
|
552
|
+
Type.String(),
|
|
553
|
+
Type.Number(),
|
|
554
|
+
Type.Boolean(),
|
|
555
|
+
Type.Null(),
|
|
556
|
+
]);
|
|
557
|
+
const ChatTemplateKwargVariableSchema = Type.Object({
|
|
558
|
+
$var: Type.Union([
|
|
559
|
+
Type.Literal("thinking.enabled"),
|
|
560
|
+
Type.Literal("thinking.effort"),
|
|
561
|
+
]),
|
|
562
|
+
omitWhenOff: Type.Optional(Type.Boolean()),
|
|
563
|
+
});
|
|
564
|
+
const ChatTemplateKwargSchema = Type.Union([
|
|
565
|
+
ChatTemplateKwargScalarSchema,
|
|
566
|
+
ChatTemplateKwargVariableSchema,
|
|
567
|
+
]);
|
|
568
|
+
const OpenAICompletionsCompatSchema = Type.Object({
|
|
569
|
+
supportsStore: Type.Optional(Type.Boolean()),
|
|
570
|
+
supportsDeveloperRole: Type.Optional(Type.Boolean()),
|
|
571
|
+
supportsReasoningEffort: Type.Optional(Type.Boolean()),
|
|
572
|
+
supportsUsageInStreaming: Type.Optional(Type.Boolean()),
|
|
573
|
+
supportsFinishReason: Type.Optional(Type.Boolean()),
|
|
574
|
+
maxTokensField: Type.Optional(
|
|
575
|
+
Type.Union([
|
|
576
|
+
Type.Literal("max_completion_tokens"),
|
|
577
|
+
Type.Literal("max_tokens"),
|
|
578
|
+
]),
|
|
579
|
+
),
|
|
580
|
+
requiresToolResultName: Type.Optional(Type.Boolean()),
|
|
581
|
+
requiresAssistantAfterToolResult: Type.Optional(Type.Boolean()),
|
|
582
|
+
requiresThinkingAsText: Type.Optional(Type.Boolean()),
|
|
583
|
+
requiresReasoningContentOnAssistantMessages: Type.Optional(Type.Boolean()),
|
|
584
|
+
thinkingFormat: Type.Optional(
|
|
585
|
+
Type.Union(
|
|
586
|
+
[
|
|
587
|
+
"openai",
|
|
588
|
+
"openrouter",
|
|
589
|
+
"together",
|
|
590
|
+
"baseten",
|
|
591
|
+
"deepseek",
|
|
592
|
+
"zai",
|
|
593
|
+
"qwen",
|
|
594
|
+
"chat-template",
|
|
595
|
+
"qwen-chat-template",
|
|
596
|
+
"string-thinking",
|
|
597
|
+
"ant-ling",
|
|
598
|
+
].map((value) => Type.Literal(value)),
|
|
599
|
+
),
|
|
600
|
+
),
|
|
601
|
+
chatTemplateKwargs: Type.Optional(
|
|
602
|
+
Type.Record(Type.String(), ChatTemplateKwargSchema),
|
|
603
|
+
),
|
|
604
|
+
chatTemplateArgs: Type.Optional(
|
|
605
|
+
Type.Record(Type.String(), ChatTemplateKwargSchema),
|
|
606
|
+
),
|
|
607
|
+
cacheControlFormat: Type.Optional(Type.Literal("anthropic")),
|
|
608
|
+
openRouterRouting: Type.Optional(OpenRouterRoutingSchema),
|
|
609
|
+
vercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),
|
|
610
|
+
supportsOpenAIGrammarTools: Type.Optional(Type.Boolean()),
|
|
611
|
+
supportsStrictMode: Type.Optional(Type.Boolean()),
|
|
612
|
+
sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
|
|
613
|
+
deferredToolsMode: Type.Optional(Type.Literal("kimi")),
|
|
614
|
+
sessionAffinityFormat: Type.Optional(
|
|
615
|
+
Type.Union([
|
|
616
|
+
Type.Literal("openai"),
|
|
617
|
+
Type.Literal("openai-nosession"),
|
|
618
|
+
Type.Literal("openrouter"),
|
|
619
|
+
]),
|
|
620
|
+
),
|
|
621
|
+
supportsLongCacheRetention: Type.Optional(Type.Boolean()),
|
|
622
|
+
});
|
|
623
|
+
const OpenAIResponsesCompatSchema = Type.Object({
|
|
624
|
+
supportsDeveloperRole: Type.Optional(Type.Boolean()),
|
|
625
|
+
sessionAffinityFormat: Type.Optional(
|
|
626
|
+
Type.Union([
|
|
627
|
+
Type.Literal("openai"),
|
|
628
|
+
Type.Literal("openai-nosession"),
|
|
629
|
+
Type.Literal("openrouter"),
|
|
630
|
+
]),
|
|
631
|
+
),
|
|
632
|
+
supportsLongCacheRetention: Type.Optional(Type.Boolean()),
|
|
633
|
+
supportsStrictMode: Type.Optional(Type.Boolean()),
|
|
634
|
+
supportsOpenAIGrammarTools: Type.Optional(Type.Boolean()),
|
|
635
|
+
supportsAdditionalTools: Type.Optional(Type.Boolean()),
|
|
636
|
+
supportsToolSearch: Type.Optional(Type.Boolean()),
|
|
637
|
+
});
|
|
638
|
+
const AnthropicMessagesCompatSchema = Type.Object({
|
|
639
|
+
supportsEagerToolInputStreaming: Type.Optional(Type.Boolean()),
|
|
640
|
+
supportsLongCacheRetention: Type.Optional(Type.Boolean()),
|
|
641
|
+
sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
|
|
642
|
+
supportsCacheControlOnTools: Type.Optional(Type.Boolean()),
|
|
643
|
+
supportsTemperature: Type.Optional(Type.Boolean()),
|
|
644
|
+
forceAdaptiveThinking: Type.Optional(Type.Boolean()),
|
|
645
|
+
allowEmptySignature: Type.Optional(Type.Boolean()),
|
|
646
|
+
supportsStrictTools: Type.Optional(Type.Boolean()),
|
|
647
|
+
supportsToolReferences: Type.Optional(Type.Boolean()),
|
|
648
|
+
});
|
|
649
|
+
const ProviderCompatSchema = Type.Union([
|
|
650
|
+
OpenAICompletionsCompatSchema,
|
|
651
|
+
OpenAIResponsesCompatSchema,
|
|
652
|
+
AnthropicMessagesCompatSchema,
|
|
653
|
+
]);
|
|
654
|
+
const ModelCostTierSchema = Type.Object({
|
|
655
|
+
inputTokensAbove: Type.Number(),
|
|
656
|
+
input: Type.Number(),
|
|
657
|
+
output: Type.Number(),
|
|
658
|
+
cacheRead: Type.Number(),
|
|
659
|
+
cacheWrite: Type.Number(),
|
|
660
|
+
});
|
|
661
|
+
const ModelOverrideSchema = Type.Object({
|
|
662
|
+
name: Type.Optional(Type.String({ minLength: 1 })),
|
|
663
|
+
reasoning: Type.Optional(Type.Boolean()),
|
|
664
|
+
thinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),
|
|
665
|
+
input: Type.Optional(
|
|
666
|
+
Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
|
|
667
|
+
),
|
|
668
|
+
cost: Type.Optional(
|
|
669
|
+
Type.Object({
|
|
670
|
+
input: Type.Optional(Type.Number()),
|
|
671
|
+
output: Type.Optional(Type.Number()),
|
|
672
|
+
cacheRead: Type.Optional(Type.Number()),
|
|
673
|
+
cacheWrite: Type.Optional(Type.Number()),
|
|
674
|
+
tiers: Type.Optional(Type.Array(ModelCostTierSchema)),
|
|
675
|
+
}),
|
|
676
|
+
),
|
|
677
|
+
contextWindow: Type.Optional(Type.Number()),
|
|
678
|
+
maxTokens: Type.Optional(Type.Number()),
|
|
679
|
+
samplingParams: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
680
|
+
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
681
|
+
compat: Type.Optional(ProviderCompatSchema),
|
|
682
|
+
});
|
|
683
|
+
const validateModelOverride = Compile(ModelOverrideSchema);
|
|
684
|
+
|
|
685
|
+
function invalidModelOverride(): never {
|
|
686
|
+
throw new Error(
|
|
687
|
+
"The openai-codex provider has an invalid model override; it will not be replaced.",
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** Validate operator overrides and plan only the resolved context-window mutations. */
|
|
692
|
+
export function planCodexModelDefaults(
|
|
693
|
+
providersValue: unknown,
|
|
694
|
+
contextWindowByModelId: ReadonlyMap<string, number> =
|
|
695
|
+
CODEX_CONTEXT_WINDOW_BY_MODEL_ID,
|
|
696
|
+
liveCompleteOfflineModelIds: ReadonlySet<string> = new Set(),
|
|
697
|
+
): CodexModelDefaultsPlan {
|
|
698
|
+
if (providersValue !== undefined && !isRecord(providersValue)) {
|
|
699
|
+
return invalidModelOverride();
|
|
700
|
+
}
|
|
701
|
+
const providers = providersValue ?? {};
|
|
702
|
+
const codexValue = providers["openai-codex"];
|
|
703
|
+
if (codexValue !== undefined && !isRecord(codexValue)) {
|
|
704
|
+
return invalidModelOverride();
|
|
705
|
+
}
|
|
706
|
+
const codex = codexValue ?? {};
|
|
707
|
+
const overridesValue = codex.modelOverrides;
|
|
708
|
+
if (overridesValue !== undefined && !isRecord(overridesValue)) {
|
|
709
|
+
return invalidModelOverride();
|
|
710
|
+
}
|
|
711
|
+
const existingOverrides = overridesValue ?? {};
|
|
712
|
+
for (const override of Object.values(existingOverrides)) {
|
|
713
|
+
if (!validateModelOverride.Check(override)) return invalidModelOverride();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const mergedOverrides: Record<string, unknown> = { ...existingOverrides };
|
|
717
|
+
const changes: CodexModelDefaultChange[] = [];
|
|
718
|
+
for (const modelId of liveCompleteOfflineModelIds) {
|
|
719
|
+
if (!CODEX_CONTEXT_WINDOW_BY_MODEL_ID.has(modelId)) continue;
|
|
720
|
+
const existing = existingOverrides[modelId] as
|
|
721
|
+
| Record<string, unknown>
|
|
722
|
+
| undefined;
|
|
723
|
+
if (existing?.contextWindow !== CODEX_CONTEXT_WINDOW) continue;
|
|
724
|
+
const remaining = { ...existing };
|
|
725
|
+
delete remaining.contextWindow;
|
|
726
|
+
if (Object.keys(remaining).length === 0) {
|
|
727
|
+
delete mergedOverrides[modelId];
|
|
728
|
+
} else {
|
|
729
|
+
mergedOverrides[modelId] = remaining;
|
|
730
|
+
}
|
|
731
|
+
changes.push({
|
|
732
|
+
modelId,
|
|
733
|
+
previousContextWindow: CODEX_CONTEXT_WINDOW,
|
|
734
|
+
contextWindow: undefined,
|
|
735
|
+
hadOverride: true,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
for (const [modelId, contextWindow] of contextWindowByModelId) {
|
|
739
|
+
const hadOverride = Object.hasOwn(existingOverrides, modelId);
|
|
740
|
+
if (hadOverride && !CODEX_CONTEXT_WINDOW_BY_MODEL_ID.has(modelId)) {
|
|
741
|
+
// Exact documentation authorizes a dynamic default; it does not transfer
|
|
742
|
+
// ownership of an operator's existing override to this transaction.
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
const existing = existingOverrides[modelId] as
|
|
746
|
+
| Record<string, unknown>
|
|
747
|
+
| undefined;
|
|
748
|
+
const previousContextWindow = existing?.contextWindow as number | undefined;
|
|
749
|
+
if (previousContextWindow !== contextWindow) {
|
|
750
|
+
changes.push({
|
|
751
|
+
modelId,
|
|
752
|
+
previousContextWindow,
|
|
753
|
+
contextWindow,
|
|
754
|
+
hadOverride,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
mergedOverrides[modelId] = {
|
|
758
|
+
...existing,
|
|
759
|
+
contextWindow,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return {
|
|
764
|
+
providers: {
|
|
765
|
+
...providers,
|
|
766
|
+
"openai-codex": {
|
|
767
|
+
...codex,
|
|
768
|
+
modelOverrides: mergedOverrides,
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
changes,
|
|
772
|
+
contextWindowByModelId,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/** Build a declaration whose validated Codex row copies carry the resolved defaults. */
|
|
777
|
+
export function buildModelDeclarationWithCodexDefaults(
|
|
778
|
+
catalogs: ModelDeclarationCatalogs,
|
|
779
|
+
contextWindowByModelId: ReadonlyMap<string, number> =
|
|
780
|
+
CODEX_CONTEXT_WINDOW_BY_MODEL_ID,
|
|
781
|
+
): BuiltDeclaration {
|
|
782
|
+
return buildModelDeclaration(catalogs, {
|
|
783
|
+
codexContextWindowByModelId: contextWindowByModelId,
|
|
784
|
+
});
|
|
785
|
+
}
|