codex-multi-auth 2.9.2 → 2.11.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.
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +2 -1
- package/config/README.md +3 -1
- package/config/codex-legacy.json +300 -0
- package/config/codex-modern.json +96 -0
- package/config/schema/config.schema.json +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +62 -2
- package/dist/lib/accounts.d.ts +25 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +26 -6
- package/dist/lib/capability-policy.d.ts.map +1 -1
- package/dist/lib/capability-policy.js +16 -1
- package/dist/lib/codex-manager/backend-settings-helpers.d.ts.map +1 -1
- package/dist/lib/codex-manager/backend-settings-helpers.js +10 -1
- package/dist/lib/codex-manager/backend-settings-schema.d.ts +18 -0
- package/dist/lib/codex-manager/backend-settings-schema.d.ts.map +1 -1
- package/dist/lib/codex-manager/backend-settings-schema.js +19 -0
- package/dist/lib/codex-manager/experimental-settings-prompt.d.ts +1 -0
- package/dist/lib/codex-manager/experimental-settings-prompt.d.ts.map +1 -1
- package/dist/lib/codex-manager/experimental-settings-prompt.js +12 -0
- package/dist/lib/codex-manager/experimental-settings-schema.d.ts +2 -0
- package/dist/lib/codex-manager/experimental-settings-schema.d.ts.map +1 -1
- package/dist/lib/codex-manager/experimental-settings-schema.js +2 -0
- package/dist/lib/config.d.ts +29 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +91 -1
- package/dist/lib/context-budget/model-context-windows.d.ts +25 -0
- package/dist/lib/context-budget/model-context-windows.d.ts.map +1 -0
- package/dist/lib/context-budget/model-context-windows.js +130 -0
- package/dist/lib/context-budget-guard.d.ts +100 -0
- package/dist/lib/context-budget-guard.d.ts.map +1 -0
- package/dist/lib/context-budget-guard.js +183 -0
- package/dist/lib/context-budget-response.d.ts +25 -0
- package/dist/lib/context-budget-response.d.ts.map +1 -0
- package/dist/lib/context-budget-response.js +57 -0
- package/dist/lib/context-overflow.d.ts.map +1 -1
- package/dist/lib/context-overflow.js +5 -60
- package/dist/lib/model-capability-matrix.d.ts.map +1 -1
- package/dist/lib/model-capability-matrix.js +9 -1
- package/dist/lib/request/error-classification.d.ts.map +1 -1
- package/dist/lib/request/error-classification.js +43 -2
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +181 -0
- package/dist/lib/request/rate-limit-decision.d.ts +4 -5
- package/dist/lib/request/rate-limit-decision.d.ts.map +1 -1
- package/dist/lib/request/rate-limit-decision.js +33 -4
- package/dist/lib/request/response-metadata.d.ts.map +1 -1
- package/dist/lib/request/response-metadata.js +1 -0
- package/dist/lib/request/stream-failover-runtime.d.ts +3 -1
- package/dist/lib/request/stream-failover-runtime.d.ts.map +1 -1
- package/dist/lib/request/stream-failover-runtime.js +7 -2
- package/dist/lib/runtime/context-budget-settings.d.ts +13 -0
- package/dist/lib/runtime/context-budget-settings.d.ts.map +1 -0
- package/dist/lib/runtime/context-budget-settings.js +8 -0
- package/dist/lib/runtime/loader-setup.d.ts +1 -0
- package/dist/lib/runtime/loader-setup.d.ts.map +1 -1
- package/dist/lib/runtime/loader-setup.js +1 -0
- package/dist/lib/runtime/rotation-account-selection.d.ts +23 -0
- package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-account-selection.js +15 -6
- package/dist/lib/runtime/rotation-proxy-state.d.ts +2 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-server-types.d.ts +11 -0
- package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +251 -43
- package/dist/lib/schemas.d.ts +4 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +10 -0
- package/dist/lib/synthetic-response.d.ts +31 -0
- package/dist/lib/synthetic-response.d.ts.map +1 -0
- package/dist/lib/synthetic-response.js +79 -0
- package/dist/lib/ui/ui-copy.d.ts +2 -1
- package/dist/lib/ui/ui-copy.d.ts.map +1 -1
- package/dist/lib/ui/ui-copy.js +2 -1
- package/dist/lib/usage/pricing.d.ts +1 -1
- package/dist/lib/usage/pricing.d.ts.map +1 -1
- package/dist/lib/usage/pricing.js +33 -0
- package/package.json +1 -1
- package/scripts/codex.js +153 -2
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getNormalizedModel } from "../request/helpers/model-map.js";
|
|
2
|
+
/**
|
|
3
|
+
* Context-window size estimates for the Context Budget Guard.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `lib/usage/pricing.ts`, the numbers below are NOT published,
|
|
6
|
+
* contractual facts. Per `docs/releases/v2.5.0.md` ("Where the model facts
|
|
7
|
+
* came from"), the context window OpenAI's published API docs advertise is
|
|
8
|
+
* for the API surface, not the ChatGPT Codex backend this wrapper actually
|
|
9
|
+
* talks to — and the docs pages don't even agree with each other. Treating a
|
|
10
|
+
* hardcoded number here as ground truth would repeat that exact mistake for
|
|
11
|
+
* a safety feature, which is worse than not shipping the feature at all.
|
|
12
|
+
*
|
|
13
|
+
* So this table is a fallback starting point ONLY. `getEffectiveContextWindow`
|
|
14
|
+
* always prefers a caller-supplied override (from
|
|
15
|
+
* `contextBudgetGuardModelWindowOverrides`, the setting a user fills in once
|
|
16
|
+
* they know their real ceiling) over anything listed here.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Best-effort estimates, in tokens. Deliberately conservative: an estimate
|
|
20
|
+
* that is too LOW makes the guard fire early (annoying, safe); an estimate
|
|
21
|
+
* that is too HIGH makes the guard fire late or never (silent, unsafe). When
|
|
22
|
+
* in doubt about a model's real ceiling, leave it out of this table and add
|
|
23
|
+
* it to UNESTIMATED_ROUTABLE_MODELS instead.
|
|
24
|
+
*/
|
|
25
|
+
const ESTIMATED_MODEL_CONTEXT_WINDOWS = {
|
|
26
|
+
"gpt-5.1": 260_000,
|
|
27
|
+
"gpt-5.2": 260_000,
|
|
28
|
+
"gpt-5.2-pro": 260_000,
|
|
29
|
+
"gpt-5.3-codex": 260_000,
|
|
30
|
+
"gpt-5.4": 260_000,
|
|
31
|
+
"gpt-5.4-mini": 260_000,
|
|
32
|
+
"gpt-5.4-nano": 260_000,
|
|
33
|
+
"gpt-5.4-pro": 260_000,
|
|
34
|
+
"gpt-5.5": 260_000,
|
|
35
|
+
"gpt-5.5-pro": 260_000,
|
|
36
|
+
"gpt-5-mini": 260_000,
|
|
37
|
+
"gpt-5-nano": 260_000,
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Routable models this guard consciously does not estimate a window for.
|
|
41
|
+
* `getEffectiveContextWindow` returns `null` for these absent an override,
|
|
42
|
+
* and the guard treats `null` as "cannot evaluate; skip" — never a guessed
|
|
43
|
+
* percentage. The 5.6 family ships with materially different per-tier
|
|
44
|
+
* behavior (see the v2.5.0 release notes) closely enough to launch that no
|
|
45
|
+
* estimate here has been reviewed for it yet.
|
|
46
|
+
*/
|
|
47
|
+
export const UNESTIMATED_ROUTABLE_MODELS = [
|
|
48
|
+
"gpt-5.6-sol",
|
|
49
|
+
"gpt-5.6-terra",
|
|
50
|
+
"gpt-5.6-luna",
|
|
51
|
+
// GPT-6 Astra published two different numbers on day one: 1.05M for the API
|
|
52
|
+
// surface and 272K as the Codex context. This wrapper talks to the Codex
|
|
53
|
+
// backend, and the same split is what the v2.5.0 notes warn about — so no
|
|
54
|
+
// estimate is entered here. `gpt-daybreak-red-latest` is the one model whose
|
|
55
|
+
// upstream catalog entry pins `context_window` and `max_context_window` to
|
|
56
|
+
// the same 372000, but that is the Codex picker's number rather than a
|
|
57
|
+
// measured ceiling for this transport, so it stays unestimated too. Set
|
|
58
|
+
// `contextBudgetGuardModelWindowOverrides` once you know your real ceiling.
|
|
59
|
+
"gpt-6-astra",
|
|
60
|
+
"gpt-6-astra-aeon",
|
|
61
|
+
"gpt-daybreak-blue-latest",
|
|
62
|
+
"gpt-daybreak-red-latest",
|
|
63
|
+
];
|
|
64
|
+
function normalizeModelName(model) {
|
|
65
|
+
const trimmed = model?.trim().toLowerCase();
|
|
66
|
+
return trimmed && trimmed.length > 0 ? trimmed : null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Model ids to try, in order, against the override map and the estimate table.
|
|
70
|
+
*
|
|
71
|
+
* The guard is fed the model string exactly as the client sent it: the
|
|
72
|
+
* rotation proxy is a pass-through and `buildResponsesRequestContext` copies
|
|
73
|
+
* `body.model` verbatim. That raw string is very often NOT a key of this
|
|
74
|
+
* file's table — Codex CLI's own default is `gpt-5-codex`, and every
|
|
75
|
+
* reasoning-suffixed alias (`gpt-5.3-codex-high`, …) is an alias-map entry
|
|
76
|
+
* rather than a catalog model. Looking up only the raw string made the guard
|
|
77
|
+
* silently return "no window, skip" for the most common model in the product.
|
|
78
|
+
*
|
|
79
|
+
* So: try the raw (lowercased) string first, so a user override keyed exactly
|
|
80
|
+
* the way they type the model still wins, then the catalog's canonical id for
|
|
81
|
+
* it. `getNormalizedModel` is deliberately the exact/alias-only resolver —
|
|
82
|
+
* `resolveNormalizedModel` falls back to `DEFAULT_MODEL` for anything it does
|
|
83
|
+
* not recognize, which would hand an unknown model gpt-5.5's window and
|
|
84
|
+
* evaluate a real session against a fabricated number.
|
|
85
|
+
*/
|
|
86
|
+
function windowLookupCandidates(model) {
|
|
87
|
+
const raw = normalizeModelName(model);
|
|
88
|
+
if (!raw)
|
|
89
|
+
return [];
|
|
90
|
+
const canonical = normalizeModelName(getNormalizedModel(raw));
|
|
91
|
+
return canonical && canonical !== raw ? [raw, canonical] : [raw];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the context-window size to evaluate a session's budget against.
|
|
95
|
+
*
|
|
96
|
+
* Precedence: an explicit override always wins, even for a model this file
|
|
97
|
+
* has never heard of — the whole point of the override setting is to let a
|
|
98
|
+
* user correct or extend past this file's guesses. Failing that, fall back
|
|
99
|
+
* to the estimate table. Returns `null` when neither source has a value, so
|
|
100
|
+
* the guard can no-op rather than evaluate against a fabricated number.
|
|
101
|
+
*/
|
|
102
|
+
export function getEffectiveContextWindow(model, overrides) {
|
|
103
|
+
const candidates = windowLookupCandidates(model);
|
|
104
|
+
if (candidates.length === 0)
|
|
105
|
+
return null;
|
|
106
|
+
for (const candidate of candidates) {
|
|
107
|
+
const override = overrides?.[candidate];
|
|
108
|
+
if (typeof override !== "number" || !Number.isFinite(override))
|
|
109
|
+
continue;
|
|
110
|
+
// Floor BEFORE the positivity check, not after. Checking `override > 0`
|
|
111
|
+
// on the raw value admits anything in (0, 1), which then floors to 0 and
|
|
112
|
+
// returns a zero-token window -- breaking this function's contract that a
|
|
113
|
+
// window it cannot resolve comes back as null, and handing any caller
|
|
114
|
+
// that trusts it a division by zero.
|
|
115
|
+
const tokens = Math.floor(override);
|
|
116
|
+
if (tokens > 0) {
|
|
117
|
+
return { tokens, source: "override" };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const candidate of candidates) {
|
|
121
|
+
const estimate = ESTIMATED_MODEL_CONTEXT_WINDOWS[candidate];
|
|
122
|
+
if (typeof estimate === "number") {
|
|
123
|
+
return { tokens: estimate, source: "estimate" };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
export function listEstimatedModelContextWindows() {
|
|
129
|
+
return { ...ESTIMATED_MODEL_CONTEXT_WINDOWS };
|
|
130
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export interface ContextBudgetSnapshot {
|
|
2
|
+
model: string;
|
|
3
|
+
/**
|
|
4
|
+
* Tokens the NEXT turn on this session is expected to resend as context:
|
|
5
|
+
* this turn's `input_tokens` plus the part of its output that becomes
|
|
6
|
+
* conversation history. Deliberately NOT `total_tokens`, which also counts
|
|
7
|
+
* `reasoning_tokens` that the Responses API drops rather than resends —
|
|
8
|
+
* charging those against the window inflates every measurement by this
|
|
9
|
+
* turn's thinking budget and fires the guard early on long-reasoning turns.
|
|
10
|
+
*/
|
|
11
|
+
contextTokens: number;
|
|
12
|
+
updatedAt: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ContextBudgetOptions {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
softPercent?: number;
|
|
17
|
+
hardPercent?: number;
|
|
18
|
+
modelWindowOverrides?: Record<string, number>;
|
|
19
|
+
}
|
|
20
|
+
interface ContextBudgetPressure {
|
|
21
|
+
percent: number;
|
|
22
|
+
contextTokens: number;
|
|
23
|
+
windowTokens: number;
|
|
24
|
+
windowSource: "override" | "estimate";
|
|
25
|
+
model: string;
|
|
26
|
+
}
|
|
27
|
+
export type ContextBudgetAdvisory = {
|
|
28
|
+
level: "ok";
|
|
29
|
+
} | ({
|
|
30
|
+
level: "soft";
|
|
31
|
+
} & ContextBudgetPressure) | ({
|
|
32
|
+
level: "hard";
|
|
33
|
+
} & ContextBudgetPressure);
|
|
34
|
+
/**
|
|
35
|
+
* Tracks how full each session's context window is and decides, before the
|
|
36
|
+
* NEXT request on that session goes out, whether to nudge (soft) or pause
|
|
37
|
+
* (hard) the caller.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately shaped like `PreemptiveQuotaScheduler`
|
|
40
|
+
* (`lib/preemptive-quota-scheduler.ts`): a per-key `Map` snapshot, a
|
|
41
|
+
* `configure()`/decision-getter split, and a lazy `prune()` on the same
|
|
42
|
+
* cadence. Same reason in both places — the decision needs to survive
|
|
43
|
+
* across the request that observed the usage and the next request that
|
|
44
|
+
* asks "are we still OK," and a scheduled sweep keeps memory bounded
|
|
45
|
+
* without a dedicated timer.
|
|
46
|
+
*/
|
|
47
|
+
export declare class ContextBudgetGuard {
|
|
48
|
+
private readonly snapshots;
|
|
49
|
+
private enabled;
|
|
50
|
+
private softPercent;
|
|
51
|
+
private hardPercent;
|
|
52
|
+
private modelWindowOverrides;
|
|
53
|
+
private lastPruneAt;
|
|
54
|
+
constructor(options?: ContextBudgetOptions);
|
|
55
|
+
configure(options?: ContextBudgetOptions): void;
|
|
56
|
+
private maybePrune;
|
|
57
|
+
/**
|
|
58
|
+
* Record the latest known context size for a session, from that turn's usage.
|
|
59
|
+
*
|
|
60
|
+
* No-ops while disabled. The call sites sit on the hot path of every
|
|
61
|
+
* forwarded Responses turn, so a default-off install must not pay for a
|
|
62
|
+
* per-session map it will never read.
|
|
63
|
+
*/
|
|
64
|
+
update(key: string, snapshot: ContextBudgetSnapshot): void;
|
|
65
|
+
/**
|
|
66
|
+
* Evaluate before forwarding the NEXT request on this session key.
|
|
67
|
+
*
|
|
68
|
+
* `requestModel` is the model that request will use, and it is what the
|
|
69
|
+
* window is resolved from -- NOT the model of the last recorded turn. The
|
|
70
|
+
* two differ whenever a session switches models, and then the snapshot's
|
|
71
|
+
* model is the wrong denominator: a session carrying 205k tokens is 79% of
|
|
72
|
+
* a 260k window and a fifth of a 1M one. Using the stale model also let
|
|
73
|
+
* the guard pause a request for a model it deliberately refuses to
|
|
74
|
+
* estimate, and print that stale model's name in the notice. Falls back to
|
|
75
|
+
* the snapshot's model only when the request declares none.
|
|
76
|
+
*/
|
|
77
|
+
getAdvisory(key: string, now?: number, requestModel?: string | null): ContextBudgetAdvisory;
|
|
78
|
+
/** Drop sessions untouched for a while so the map does not grow unbounded. */
|
|
79
|
+
prune(now?: number): number;
|
|
80
|
+
/** Forget a single session's tracked usage, e.g. once it has visibly compacted. */
|
|
81
|
+
forget(key: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Call immediately after emitting a hard pause for `key`.
|
|
84
|
+
*
|
|
85
|
+
* The pause is synthetic: it short-circuits before the request is forwarded,
|
|
86
|
+
* so `update()` never runs for it and the recorded usage can never fall on
|
|
87
|
+
* its own. Left in place, the first crossing of the hard threshold would
|
|
88
|
+
* block that session forever — including the `/compact` turn the pause
|
|
89
|
+
* message tells the user to run, which travels on the same session key.
|
|
90
|
+
*
|
|
91
|
+
* Dropping the snapshot makes the pause one-shot per measurement: the next
|
|
92
|
+
* request is forwarded and re-measures. A session that really did compact
|
|
93
|
+
* comes back under the threshold and stays quiet; one that did not gets
|
|
94
|
+
* paused again on the turn after, so the warning still repeats while the
|
|
95
|
+
* session is over budget, without ever becoming a dead end.
|
|
96
|
+
*/
|
|
97
|
+
noteHardPauseEmitted(key: string): void;
|
|
98
|
+
}
|
|
99
|
+
export {};
|
|
100
|
+
//# sourceMappingURL=context-budget-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-budget-guard.d.ts","sourceRoot":"","sources":["../../lib/context-budget-guard.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED,UAAU,qBAAqB;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,qBAAqB,GAC9B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,qBAAqB,CAAC,GAC3C,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,qBAAqB,CAAC,CAAC;AAuB/C;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4C;IACtE,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,oBAAoB,CAAqC;IACjE,OAAO,CAAC,WAAW,CAAK;gBAEZ,OAAO,GAAE,oBAAyB;IAI9C,SAAS,CAAC,OAAO,GAAE,oBAAyB,GAAG,IAAI;IAgCnD,OAAO,CAAC,UAAU;IAMlB;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAmB1D;;;;;;;;;;;OAWG;IACH,WAAW,CACV,GAAG,EAAE,MAAM,EACX,GAAG,SAAa,EAChB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,qBAAqB;IAuCxB,8EAA8E;IAC9E,KAAK,CAAC,GAAG,SAAa,GAAG,MAAM;IAW/B,mFAAmF;IACnF,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAGvC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { getEffectiveContextWindow } from "./context-budget/model-context-windows.js";
|
|
2
|
+
const DEFAULT_SOFT_PERCENT = 65;
|
|
3
|
+
const DEFAULT_HARD_PERCENT = 69;
|
|
4
|
+
/**
|
|
5
|
+
* Lowest hard threshold that can describe a real budget.
|
|
6
|
+
*
|
|
7
|
+
* `getContextBudgetGuardHardPercent` clamps rather than rejects (that is what
|
|
8
|
+
* every other numeric setting does), so without a floor a configured `0` is a
|
|
9
|
+
* legal value that makes `percent >= hardPercent` true for every snapshot —
|
|
10
|
+
* every session pauses from its first recorded turn onward. Anything under
|
|
11
|
+
* 10% of a window is that same failure in slower motion, so clamp up to a
|
|
12
|
+
* value that at least can't fire on turn one of a normal session.
|
|
13
|
+
*/
|
|
14
|
+
const MIN_HARD_PERCENT = 10;
|
|
15
|
+
/** Sessions idle longer than this are dropped on the next prune sweep. */
|
|
16
|
+
const SESSION_TTL_MS = 6 * 60 * 60_000;
|
|
17
|
+
const PRUNE_INTERVAL_MS = 60_000;
|
|
18
|
+
function clampInt(value, min, max) {
|
|
19
|
+
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Tracks how full each session's context window is and decides, before the
|
|
23
|
+
* NEXT request on that session goes out, whether to nudge (soft) or pause
|
|
24
|
+
* (hard) the caller.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately shaped like `PreemptiveQuotaScheduler`
|
|
27
|
+
* (`lib/preemptive-quota-scheduler.ts`): a per-key `Map` snapshot, a
|
|
28
|
+
* `configure()`/decision-getter split, and a lazy `prune()` on the same
|
|
29
|
+
* cadence. Same reason in both places — the decision needs to survive
|
|
30
|
+
* across the request that observed the usage and the next request that
|
|
31
|
+
* asks "are we still OK," and a scheduled sweep keeps memory bounded
|
|
32
|
+
* without a dedicated timer.
|
|
33
|
+
*/
|
|
34
|
+
export class ContextBudgetGuard {
|
|
35
|
+
snapshots = new Map();
|
|
36
|
+
enabled = false;
|
|
37
|
+
softPercent = DEFAULT_SOFT_PERCENT;
|
|
38
|
+
hardPercent = DEFAULT_HARD_PERCENT;
|
|
39
|
+
modelWindowOverrides;
|
|
40
|
+
lastPruneAt = 0;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.configure(options);
|
|
43
|
+
}
|
|
44
|
+
configure(options = {}) {
|
|
45
|
+
if (typeof options.enabled === "boolean") {
|
|
46
|
+
// Turning the guard off must also drop what it already tracked, or a
|
|
47
|
+
// disable/re-enable cycle re-evaluates against usage measured under
|
|
48
|
+
// the old settings and can pause a session on its first request back.
|
|
49
|
+
if (!options.enabled && this.enabled) {
|
|
50
|
+
this.snapshots.clear();
|
|
51
|
+
}
|
|
52
|
+
this.enabled = options.enabled;
|
|
53
|
+
}
|
|
54
|
+
if (typeof options.softPercent === "number" && Number.isFinite(options.softPercent)) {
|
|
55
|
+
this.softPercent = clampInt(options.softPercent, 0, 100);
|
|
56
|
+
}
|
|
57
|
+
if (typeof options.hardPercent === "number" && Number.isFinite(options.hardPercent)) {
|
|
58
|
+
this.hardPercent = clampInt(options.hardPercent, MIN_HARD_PERCENT, 100);
|
|
59
|
+
}
|
|
60
|
+
// A misconfigured soft >= hard must degrade safely (guard fires later
|
|
61
|
+
// than intended) rather than throw or produce a nonsensical "hard but
|
|
62
|
+
// not soft" state. Never silently widen hard to fix it — narrowing soft
|
|
63
|
+
// is the change least likely to surprise whoever set hard deliberately.
|
|
64
|
+
if (this.softPercent >= this.hardPercent) {
|
|
65
|
+
this.softPercent = Math.max(0, this.hardPercent - 1);
|
|
66
|
+
}
|
|
67
|
+
if (options.modelWindowOverrides) {
|
|
68
|
+
this.modelWindowOverrides = options.modelWindowOverrides;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
maybePrune(now) {
|
|
72
|
+
if (now - this.lastPruneAt < PRUNE_INTERVAL_MS)
|
|
73
|
+
return;
|
|
74
|
+
this.prune(now);
|
|
75
|
+
this.lastPruneAt = now;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Record the latest known context size for a session, from that turn's usage.
|
|
79
|
+
*
|
|
80
|
+
* No-ops while disabled. The call sites sit on the hot path of every
|
|
81
|
+
* forwarded Responses turn, so a default-off install must not pay for a
|
|
82
|
+
* per-session map it will never read.
|
|
83
|
+
*/
|
|
84
|
+
update(key, snapshot) {
|
|
85
|
+
if (!this.enabled || !key)
|
|
86
|
+
return;
|
|
87
|
+
// Both production call sites source contextTokens from
|
|
88
|
+
// lib/usage/usage-extraction.ts, whose nonNegativeInteger already returns
|
|
89
|
+
// 0 for anything non-finite, so this is a boundary guard rather than a
|
|
90
|
+
// reachable path today. It is here because percent is a division by the
|
|
91
|
+
// window: a non-finite count would put `Infinity%` in the pause notice a
|
|
92
|
+
// user reads, and every threshold comparison against NaN is false, which
|
|
93
|
+
// disables the guard silently.
|
|
94
|
+
if (!Number.isFinite(snapshot.contextTokens) ||
|
|
95
|
+
snapshot.contextTokens < 0) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.maybePrune(snapshot.updatedAt || Date.now());
|
|
99
|
+
this.snapshots.set(key, snapshot);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Evaluate before forwarding the NEXT request on this session key.
|
|
103
|
+
*
|
|
104
|
+
* `requestModel` is the model that request will use, and it is what the
|
|
105
|
+
* window is resolved from -- NOT the model of the last recorded turn. The
|
|
106
|
+
* two differ whenever a session switches models, and then the snapshot's
|
|
107
|
+
* model is the wrong denominator: a session carrying 205k tokens is 79% of
|
|
108
|
+
* a 260k window and a fifth of a 1M one. Using the stale model also let
|
|
109
|
+
* the guard pause a request for a model it deliberately refuses to
|
|
110
|
+
* estimate, and print that stale model's name in the notice. Falls back to
|
|
111
|
+
* the snapshot's model only when the request declares none.
|
|
112
|
+
*/
|
|
113
|
+
getAdvisory(key, now = Date.now(), requestModel) {
|
|
114
|
+
this.maybePrune(now);
|
|
115
|
+
if (!this.enabled || !key)
|
|
116
|
+
return { level: "ok" };
|
|
117
|
+
const snapshot = this.snapshots.get(key);
|
|
118
|
+
if (!snapshot)
|
|
119
|
+
return { level: "ok" };
|
|
120
|
+
const trimmedRequestModel = requestModel?.trim();
|
|
121
|
+
const evaluatedModel = trimmedRequestModel && trimmedRequestModel.length > 0
|
|
122
|
+
? trimmedRequestModel
|
|
123
|
+
: snapshot.model;
|
|
124
|
+
const window = getEffectiveContextWindow(evaluatedModel, this.modelWindowOverrides);
|
|
125
|
+
if (!window || window.tokens <= 0)
|
|
126
|
+
return { level: "ok" };
|
|
127
|
+
const percent = (snapshot.contextTokens / window.tokens) * 100;
|
|
128
|
+
if (percent >= this.hardPercent) {
|
|
129
|
+
return {
|
|
130
|
+
level: "hard",
|
|
131
|
+
percent,
|
|
132
|
+
contextTokens: snapshot.contextTokens,
|
|
133
|
+
windowTokens: window.tokens,
|
|
134
|
+
windowSource: window.source,
|
|
135
|
+
model: evaluatedModel,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (percent >= this.softPercent) {
|
|
139
|
+
return {
|
|
140
|
+
level: "soft",
|
|
141
|
+
percent,
|
|
142
|
+
contextTokens: snapshot.contextTokens,
|
|
143
|
+
windowTokens: window.tokens,
|
|
144
|
+
windowSource: window.source,
|
|
145
|
+
model: evaluatedModel,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return { level: "ok" };
|
|
149
|
+
}
|
|
150
|
+
/** Drop sessions untouched for a while so the map does not grow unbounded. */
|
|
151
|
+
prune(now = Date.now()) {
|
|
152
|
+
let removed = 0;
|
|
153
|
+
for (const [key, snapshot] of this.snapshots.entries()) {
|
|
154
|
+
if (now - snapshot.updatedAt > SESSION_TTL_MS) {
|
|
155
|
+
this.snapshots.delete(key);
|
|
156
|
+
removed += 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return removed;
|
|
160
|
+
}
|
|
161
|
+
/** Forget a single session's tracked usage, e.g. once it has visibly compacted. */
|
|
162
|
+
forget(key) {
|
|
163
|
+
this.snapshots.delete(key);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Call immediately after emitting a hard pause for `key`.
|
|
167
|
+
*
|
|
168
|
+
* The pause is synthetic: it short-circuits before the request is forwarded,
|
|
169
|
+
* so `update()` never runs for it and the recorded usage can never fall on
|
|
170
|
+
* its own. Left in place, the first crossing of the hard threshold would
|
|
171
|
+
* block that session forever — including the `/compact` turn the pause
|
|
172
|
+
* message tells the user to run, which travels on the same session key.
|
|
173
|
+
*
|
|
174
|
+
* Dropping the snapshot makes the pause one-shot per measurement: the next
|
|
175
|
+
* request is forwarded and re-measures. A session that really did compact
|
|
176
|
+
* comes back under the threshold and stays quiet; one that did not gets
|
|
177
|
+
* paused again on the turn after, so the warning still repeats while the
|
|
178
|
+
* session is over budget, without ever becoming a dead end.
|
|
179
|
+
*/
|
|
180
|
+
noteHardPauseEmitted(key) {
|
|
181
|
+
this.snapshots.delete(key);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ContextBudgetAdvisory } from "./context-budget-guard.js";
|
|
2
|
+
/** Response header carrying the soft-threshold advisory, non-blocking. */
|
|
3
|
+
export declare const CONTEXT_BUDGET_PERCENT_HEADER = "x-codex-context-budget-percent";
|
|
4
|
+
/**
|
|
5
|
+
* Build the synthetic response returned in place of forwarding to upstream
|
|
6
|
+
* when the context budget guard's hard threshold has been reached.
|
|
7
|
+
*
|
|
8
|
+
* Same technique as `createContextOverflowResponse`: a 200 OK synthetic SSE
|
|
9
|
+
* reply so the host session never locks on an error for a notice the plugin
|
|
10
|
+
* itself generated — except this fires pre-flight, before the request that
|
|
11
|
+
* would overflow is ever sent, instead of reacting to the 400 after the
|
|
12
|
+
* fact.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createContextBudgetPauseResponse(advisory: Extract<ContextBudgetAdvisory, {
|
|
15
|
+
level: "hard";
|
|
16
|
+
}>): Response;
|
|
17
|
+
/**
|
|
18
|
+
* Non-blocking header for the soft threshold, added to the real forwarded
|
|
19
|
+
* response next to the existing `x-codex-*-used-percent` quota headers.
|
|
20
|
+
* Consumed by `codex-multi-auth status` / the statusline, not by the model.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildContextBudgetHeaders(advisory: Extract<ContextBudgetAdvisory, {
|
|
23
|
+
level: "soft" | "hard";
|
|
24
|
+
}>): Record<string, string>;
|
|
25
|
+
//# sourceMappingURL=context-budget-response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-budget-response.d.ts","sourceRoot":"","sources":["../../lib/context-budget-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAGvE,0EAA0E;AAC1E,eAAO,MAAM,6BAA6B,mCAAmC,CAAC;AAgC9E;;;;;;;;;GASG;AACH,wBAAgB,gCAAgC,CAC/C,QAAQ,EAAE,OAAO,CAAC,qBAAqB,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GACzD,QAAQ,CAOV;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACxC,QAAQ,EAAE,OAAO,CAAC,qBAAqB,EAAE;IAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC,GAClE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIxB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createSyntheticSseResponse } from "./synthetic-response.js";
|
|
2
|
+
/** Response header carrying the soft-threshold advisory, non-blocking. */
|
|
3
|
+
export const CONTEXT_BUDGET_PERCENT_HEADER = "x-codex-context-budget-percent";
|
|
4
|
+
function formatPercent(percent) {
|
|
5
|
+
return percent.toFixed(1);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The message shown to users when the context budget guard pauses a session
|
|
9
|
+
* at the hard threshold, before the request that would have pushed it over
|
|
10
|
+
* the model's window is even sent upstream.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately echoes `lib/context-overflow.ts`'s CONTEXT_OVERFLOW_MESSAGE
|
|
13
|
+
* copy so the two related notices read as one family in a transcript,
|
|
14
|
+
* rather than as two unexplained different warnings.
|
|
15
|
+
*/
|
|
16
|
+
function buildPauseMessage(advisory) {
|
|
17
|
+
const windowNote = advisory.windowSource === "override"
|
|
18
|
+
? "your configured window size"
|
|
19
|
+
: "an estimated window size (unverified — set `contextBudgetGuardModelWindowOverrides` if you know the real limit)";
|
|
20
|
+
return `[Plugin Notice] Context budget guard paused this session at ${formatPercent(advisory.percent)}% of ${windowNote} for \`${advisory.model}\`, before hitting the model's actual limit.
|
|
21
|
+
|
|
22
|
+
Please use one of these commands to reduce context size:
|
|
23
|
+
|
|
24
|
+
• **/compact** - Compress conversation history (recommended)
|
|
25
|
+
• **/clear** - Start fresh with empty context
|
|
26
|
+
• **/undo** - Remove recent messages
|
|
27
|
+
|
|
28
|
+
Then retry your request. This pause clears itself automatically once your context usage drops back below the threshold.`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build the synthetic response returned in place of forwarding to upstream
|
|
32
|
+
* when the context budget guard's hard threshold has been reached.
|
|
33
|
+
*
|
|
34
|
+
* Same technique as `createContextOverflowResponse`: a 200 OK synthetic SSE
|
|
35
|
+
* reply so the host session never locks on an error for a notice the plugin
|
|
36
|
+
* itself generated — except this fires pre-flight, before the request that
|
|
37
|
+
* would overflow is ever sent, instead of reacting to the 400 after the
|
|
38
|
+
* fact.
|
|
39
|
+
*/
|
|
40
|
+
export function createContextBudgetPauseResponse(advisory) {
|
|
41
|
+
return createSyntheticSseResponse({
|
|
42
|
+
model: advisory.model,
|
|
43
|
+
message: buildPauseMessage(advisory),
|
|
44
|
+
idPrefix: "context_budget_pause",
|
|
45
|
+
errorType: "context_budget_pause",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Non-blocking header for the soft threshold, added to the real forwarded
|
|
50
|
+
* response next to the existing `x-codex-*-used-percent` quota headers.
|
|
51
|
+
* Consumed by `codex-multi-auth status` / the statusline, not by the model.
|
|
52
|
+
*/
|
|
53
|
+
export function buildContextBudgetHeaders(advisory) {
|
|
54
|
+
return {
|
|
55
|
+
[CONTEXT_BUDGET_PERCENT_HEADER]: formatPercent(advisory.percent),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-overflow.d.ts","sourceRoot":"","sources":["../../lib/context-overflow.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"context-overflow.d.ts","sourceRoot":"","sources":["../../lib/context-overflow.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkBH;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMhF;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,GAAE,MAAkB,GAAG,QAAQ,CAOjF;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC,CAmBrE"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* This prevents the host session from getting locked on 400 errors.
|
|
7
7
|
*/
|
|
8
8
|
import { logDebug } from "./logger.js";
|
|
9
|
+
import { createSyntheticSseResponse } from "./synthetic-response.js";
|
|
9
10
|
/**
|
|
10
11
|
* Error patterns that indicate context overflow
|
|
11
12
|
*/
|
|
@@ -54,67 +55,11 @@ Alternatively, you can switch to a model with a larger context window.`;
|
|
|
54
55
|
* (recovery-01). Returns 200 OK so the host session does not lock on the 400.
|
|
55
56
|
*/
|
|
56
57
|
export function createContextOverflowResponse(model = "unknown") {
|
|
57
|
-
|
|
58
|
-
.toString(36)
|
|
59
|
-
.slice(2, 8)}`;
|
|
60
|
-
const responseId = `resp_synthetic_overflow_${Date.now()}_${Math.random()
|
|
61
|
-
.toString(36)
|
|
62
|
-
.slice(2, 8)}`;
|
|
63
|
-
const events = [];
|
|
64
|
-
const push = (type, payload) => {
|
|
65
|
-
events.push(`event: ${type}\ndata: ${JSON.stringify({ type, ...payload })}\n\n`);
|
|
66
|
-
};
|
|
67
|
-
const baseResponse = {
|
|
68
|
-
id: responseId,
|
|
69
|
-
object: "response",
|
|
58
|
+
return createSyntheticSseResponse({
|
|
70
59
|
model,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// output item (assistant message) added
|
|
75
|
-
push("response.output_item.added", {
|
|
76
|
-
output_index: 0,
|
|
77
|
-
item: {
|
|
78
|
-
id: messageId,
|
|
79
|
-
type: "message",
|
|
80
|
-
role: "assistant",
|
|
81
|
-
content: [],
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
// streamed text + its terminal "done" carrying the final canonical text
|
|
85
|
-
push("response.output_text.delta", {
|
|
86
|
-
output_index: 0,
|
|
87
|
-
content_index: 0,
|
|
88
|
-
delta: CONTEXT_OVERFLOW_MESSAGE,
|
|
89
|
-
});
|
|
90
|
-
push("response.output_text.done", {
|
|
91
|
-
output_index: 0,
|
|
92
|
-
content_index: 0,
|
|
93
|
-
text: CONTEXT_OVERFLOW_MESSAGE,
|
|
94
|
-
});
|
|
95
|
-
// terminal response.completed with the full output array
|
|
96
|
-
push("response.completed", {
|
|
97
|
-
response: {
|
|
98
|
-
...baseResponse,
|
|
99
|
-
status: "completed",
|
|
100
|
-
output: [
|
|
101
|
-
{
|
|
102
|
-
id: messageId,
|
|
103
|
-
type: "message",
|
|
104
|
-
role: "assistant",
|
|
105
|
-
content: [{ type: "output_text", text: CONTEXT_OVERFLOW_MESSAGE }],
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
usage: { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
return new Response(events.join(""), {
|
|
112
|
-
status: 200,
|
|
113
|
-
headers: {
|
|
114
|
-
"Content-Type": "text/event-stream",
|
|
115
|
-
"X-Codex-Plugin-Synthetic": "true",
|
|
116
|
-
"X-Codex-Plugin-Error-Type": "context_overflow",
|
|
117
|
-
},
|
|
60
|
+
message: CONTEXT_OVERFLOW_MESSAGE,
|
|
61
|
+
idPrefix: "synthetic_overflow",
|
|
62
|
+
errorType: "context_overflow",
|
|
118
63
|
});
|
|
119
64
|
}
|
|
120
65
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-capability-matrix.d.ts","sourceRoot":"","sources":["../../lib/model-capability-matrix.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAGN,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEN,KAAK,wBAAwB,EAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACX,wBAAwB,EACxB,qBAAqB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxE,MAAM,WAAW,0BAA0B;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,iBAAiB,CAAC;IAChC,sBAAsB,EAAE,oBAAoB,CAAC;IAC7C,yBAAyB,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC3D,YAAY,EAAE,iBAAiB,CAAC;IAChC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAClD,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,0BAA0B,EAAE,CAAC;CACtC;AAsBD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IACjD,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC/C,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,GAAG,qBAAqB,
|
|
1
|
+
{"version":3,"file":"model-capability-matrix.d.ts","sourceRoot":"","sources":["../../lib/model-capability-matrix.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAGN,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEN,KAAK,wBAAwB,EAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACX,wBAAwB,EACxB,qBAAqB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxE,MAAM,WAAW,0BAA0B;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,iBAAiB,CAAC;IAChC,sBAAsB,EAAE,oBAAoB,CAAC;IAC7C,yBAAyB,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC3D,YAAY,EAAE,iBAAiB,CAAC;IAChC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAClD,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,0BAA0B,EAAE,CAAC;CACtC;AAsBD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IACjD,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC/C,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,GAAG,qBAAqB,CAyFxB"}
|
|
@@ -35,7 +35,15 @@ export function buildModelCapabilityMatrix(input) {
|
|
|
35
35
|
});
|
|
36
36
|
const entitlementKeys = [accountKey, entitlementKey];
|
|
37
37
|
for (const model of models) {
|
|
38
|
-
|
|
38
|
+
// `Object.hasOwn` before the index: `models` comes from `--model` on
|
|
39
|
+
// the CLI, so `constructor` and friends reach this lookup and a bare
|
|
40
|
+
// index returns the matching `Object.prototype` member. It is truthy,
|
|
41
|
+
// so the `!profile` guard passes it through and the matrix emits a row
|
|
42
|
+
// whose normalizedModel, promptFamily and capabilities are all
|
|
43
|
+
// undefined.
|
|
44
|
+
const profile = Object.hasOwn(MODEL_PROFILES, model)
|
|
45
|
+
? MODEL_PROFILES[model]
|
|
46
|
+
: MODEL_PROFILES[resolveNormalizedModel(model)];
|
|
39
47
|
if (!profile)
|
|
40
48
|
continue;
|
|
41
49
|
const entitlement = getEntitlementBlock(input.entitlements, entitlementKeys, profile.normalizedModel, now);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-classification.d.ts","sourceRoot":"","sources":["../../../lib/request/error-classification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,gBAAgB;IACzB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACvB;AAED,sGAAsG;AACtG,eAAO,MAAM,oCAAoC,6CAA6C,CAAC;AAQ/F,eAAO,MAAM,wCAAwC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"error-classification.d.ts","sourceRoot":"","sources":["../../../lib/request/error-classification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,gBAAgB;IACzB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACvB;AAED,sGAAsG;AACtG,eAAO,MAAM,oCAAoC,6CAA6C,CAAC;AAQ/F,eAAO,MAAM,wCAAwC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAoD7E,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sCAAsC;IACtD,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,+BAA+B,EAAE,OAAO,CAAC;IACzC,iCAAiC,EAAE,OAAO,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAiDD,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgBzF;AAED,sGAAsG;AACtG,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ3F;AAED,wBAAgB,4BAA4B,CAC3C,SAAS,EAAE,OAAO,GAChB,yBAAyB,CAqD3B;AAED,wBAAgB,oCAAoC,CACnD,OAAO,EAAE,sCAAsC,GAC7C,MAAM,GAAG,SAAS,CAkCpB;AAED;;GAEG;AACH,wBAAgB,uCAAuC,CACtD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,OAAO,GAChB,OAAO,CAgBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,GACrB,OAAO,CAoDT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAmB1E"}
|