@tormentalabs/claude-code-wire-compat 0.4.0 → 0.6.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/CHANGELOG.md +217 -0
- package/README.md +51 -7
- package/dist/betas.d.ts +65 -7
- package/dist/betas.d.ts.map +1 -1
- package/dist/betas.js +143 -2
- package/dist/betas.js.map +1 -1
- package/dist/build-request.d.ts +12 -6
- package/dist/build-request.d.ts.map +1 -1
- package/dist/build-request.js +44 -15
- package/dist/build-request.js.map +1 -1
- package/dist/contracts.d.ts +7 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +5 -0
- package/dist/fingerprint.js.map +1 -1
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +5 -2
- package/dist/headers.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/model-capabilities.d.ts +3 -3
- package/dist/model-capabilities.d.ts.map +1 -1
- package/dist/model-capabilities.js +55 -19
- package/dist/model-capabilities.js.map +1 -1
- package/dist/model-identity.d.ts +9 -1
- package/dist/model-identity.d.ts.map +1 -1
- package/dist/model-identity.js +33 -2
- package/dist/model-identity.js.map +1 -1
- package/dist/model-queries.d.ts +89 -0
- package/dist/model-queries.d.ts.map +1 -0
- package/dist/model-queries.js +232 -0
- package/dist/model-queries.js.map +1 -0
- package/dist/models.js +1 -1
- package/dist/models.js.map +1 -1
- package/dist/profiles/beta-registry-2.1.280.d.ts +211 -0
- package/dist/profiles/beta-registry-2.1.280.d.ts.map +1 -0
- package/dist/profiles/beta-registry-2.1.280.js +292 -0
- package/dist/profiles/beta-registry-2.1.280.js.map +1 -0
- package/dist/profiles/claude-code-2.1.280.d.ts +3 -0
- package/dist/profiles/claude-code-2.1.280.d.ts.map +1 -0
- package/dist/profiles/claude-code-2.1.280.js +359 -0
- package/dist/profiles/claude-code-2.1.280.js.map +1 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +3 -0
- package/dist/redaction.js.map +1 -1
- package/dist/request-body.d.ts +1 -1
- package/dist/request-body.d.ts.map +1 -1
- package/dist/request-body.js +16 -5
- package/dist/request-body.js.map +1 -1
- package/dist/thinking.d.ts +53 -2
- package/dist/thinking.d.ts.map +1 -1
- package/dist/thinking.js +124 -26
- package/dist/thinking.js.map +1 -1
- package/package.json +6 -3
- package/src/betas.ts +208 -9
- package/src/build-request.ts +54 -21
- package/src/contracts.ts +7 -2
- package/src/fingerprint.ts +5 -0
- package/src/headers.ts +4 -2
- package/src/index.ts +28 -3
- package/src/model-capabilities.ts +55 -19
- package/src/model-identity.ts +29 -2
- package/src/model-queries.ts +273 -0
- package/src/models.ts +1 -1
- package/src/profiles/beta-registry-2.1.280.ts +309 -0
- package/src/profiles/claude-code-2.1.280.ts +364 -0
- package/src/redaction.ts +3 -0
- package/src/request-body.ts +25 -3
- package/src/thinking.ts +148 -26
package/dist/thinking.js
CHANGED
|
@@ -176,6 +176,72 @@ export function isThinkingDisplayActive(request, capabilities, betaPolicy) {
|
|
|
176
176
|
capabilities.interleavedThinking &&
|
|
177
177
|
betaPolicy.experimentalBetasEnabled);
|
|
178
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Narrows an unvalidated caller `thinking` value to its `type` literal.
|
|
181
|
+
*
|
|
182
|
+
* Deliberately tolerant of unvalidated input for the same reason
|
|
183
|
+
* `isThinkingDisplayActive` is: beta composition asks this question before
|
|
184
|
+
* `buildCanonicalBody` has validated the shape. Anything malformed answers
|
|
185
|
+
* `undefined` here and is rejected later by the body validator.
|
|
186
|
+
*/
|
|
187
|
+
function thinkingRequestType(request) {
|
|
188
|
+
if (request === null || typeof request !== "object")
|
|
189
|
+
return undefined;
|
|
190
|
+
const type = request["type"];
|
|
191
|
+
if (type === "enabled" || type === "adaptive" || type === "disabled") {
|
|
192
|
+
return type;
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Decides WHICH thinking object `resolveThinking` will emit, without building
|
|
198
|
+
* it.
|
|
199
|
+
*
|
|
200
|
+
* Split out of `resolveThinking` so that beta composition and body emission
|
|
201
|
+
* answer the same question from one place. A second, independently written
|
|
202
|
+
* copy of this predicate is exactly how a beta header and the body field it
|
|
203
|
+
* is coupled to drift apart.
|
|
204
|
+
*/
|
|
205
|
+
export function resolveThinkingType(requestType, capabilities) {
|
|
206
|
+
const requestActive = requestType !== undefined && requestType !== "disabled";
|
|
207
|
+
if (requestActive && capabilities.thinking) {
|
|
208
|
+
return capabilities.adaptiveThinking ? "adaptive" : "enabled";
|
|
209
|
+
}
|
|
210
|
+
if (requestType === "disabled" &&
|
|
211
|
+
capabilities.thinking &&
|
|
212
|
+
!capabilities.rejectsDisabledThinking) {
|
|
213
|
+
return "disabled";
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Upstream `ac = Kg && Fg() && iQt(model)`, the predicate the 2.1.280 thinking
|
|
219
|
+
* push sites gate on, with `Fg()` (`experimentalBetasEnabled`) factored OUT.
|
|
220
|
+
*
|
|
221
|
+
* Every push site conjoins the experimental gate itself, so folding it in here
|
|
222
|
+
* would count it twice and make the one site that legitimately does not want it
|
|
223
|
+
* impossible to express. `Kg`'s environment-disable term is not modelled: this
|
|
224
|
+
* package reads no environment.
|
|
225
|
+
*
|
|
226
|
+
* This is deliberately NOT `ResolvedThinking.requestActive`. That one is true
|
|
227
|
+
* whenever the caller asked for thinking at all, including for a model whose
|
|
228
|
+
* capabilities emit no thinking object — which would ship a thinking beta
|
|
229
|
+
* header for a body that carries no thinking block.
|
|
230
|
+
*
|
|
231
|
+
* Upstream `ac` conjoins only the interleaved predicate `iQt`; requiring a
|
|
232
|
+
* resolved `"adaptive"`/`"enabled"` type additionally requires
|
|
233
|
+
* `capabilities.thinking`, one conjunct more than upstream. That extra term is
|
|
234
|
+
* unobservable in practice: `supportsThinking` and `supportsInterleavedThinking`
|
|
235
|
+
* in `model-capabilities.ts` are the same expression, so no derived capability
|
|
236
|
+
* set separates them. Only an explicit caller capability override can, and then
|
|
237
|
+
* the package declines to announce a thinking beta for a request whose body
|
|
238
|
+
* will carry no thinking object.
|
|
239
|
+
*/
|
|
240
|
+
export function isThinkingActive(request, capabilities) {
|
|
241
|
+
const type = resolveThinkingType(thinkingRequestType(request), capabilities);
|
|
242
|
+
return ((type === "adaptive" || type === "enabled") &&
|
|
243
|
+
capabilities.interleavedThinking);
|
|
244
|
+
}
|
|
179
245
|
/**
|
|
180
246
|
* Resolves the caller's thinking request into the object the genuine client
|
|
181
247
|
* would put on the wire.
|
|
@@ -184,43 +250,75 @@ export function isThinkingDisplayActive(request, capabilities, betaPolicy) {
|
|
|
184
250
|
* for the enabled branch — `budget_tokens` FIRST — and `{type, display}` for
|
|
185
251
|
* adaptive. Serialised bodies are compared byte for byte, so the insertion
|
|
186
252
|
* order below must not be rearranged.
|
|
253
|
+
*
|
|
254
|
+
* `displayOverride` is the body-side half of beta push site 12b
|
|
255
|
+
* (`thinkingDisplayOverride` from `composeBetasWithAudit`); it is not
|
|
256
|
+
* caller-facing, which is why `ThinkingDisplay` stays unwidened. When both it
|
|
257
|
+
* and a caller display are present the override wins -- a combination the
|
|
258
|
+
* composition guard makes unreachable, since site 12b requires that the caller
|
|
259
|
+
* supplied no display.
|
|
187
260
|
*/
|
|
188
|
-
export function resolveThinking(request, normalizedId, capabilities, betaPolicy, maxTokens, profile = CLAUDE_CODE_2_1_195_PROFILE) {
|
|
261
|
+
export function resolveThinking(request, normalizedId, capabilities, betaPolicy, maxTokens, profile = CLAUDE_CODE_2_1_195_PROFILE, displayOverride) {
|
|
189
262
|
// Upstream `nr = n.type !== "disabled" && !CLAUDE_CODE_DISABLE_THINKING`.
|
|
190
263
|
const requestActive = request !== undefined && request.type !== "disabled";
|
|
191
264
|
const displayActive = isThinkingDisplayActive(request, capabilities, betaPolicy);
|
|
192
265
|
const display = displayActive ? request?.display : undefined;
|
|
266
|
+
const resolvedType = resolveThinkingType(request?.type, capabilities);
|
|
193
267
|
let emitted;
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// overridden by the caller's budget when supplied, then clamped by
|
|
203
|
-
// `Tr = Math.min(Fi - 1, Tr)` where `Fi` is the emitted `max_tokens`.
|
|
204
|
-
//
|
|
205
|
-
// This is the one wire-visible consumer of the request-derived bound: on
|
|
206
|
-
// a 2.1.222+ profile a caller asking for a `max_tokens` above the
|
|
207
|
-
// catalogue's upper limit seeds the default budget from THEIR number
|
|
208
|
-
// minus one, not from the catalogue's.
|
|
209
|
-
const requested = request.budgetTokens ??
|
|
210
|
-
modelOutputTokenLimits(normalizedId, profile, maxTokens).upperLimit - 1;
|
|
211
|
-
emitted = { budget_tokens: Math.min(maxTokens - 1, requested) };
|
|
212
|
-
emitted["type"] = "enabled";
|
|
213
|
-
if (display !== undefined)
|
|
214
|
-
emitted["display"] = display;
|
|
215
|
-
}
|
|
268
|
+
if (resolvedType === "adaptive") {
|
|
269
|
+
emitted = { type: "adaptive" };
|
|
270
|
+
if (display !== undefined)
|
|
271
|
+
emitted["display"] = display;
|
|
272
|
+
// Assignment, not reconstruction: an existing key keeps its position and a
|
|
273
|
+
// new one appends last, matching upstream `{...yc, display: "updates"}`.
|
|
274
|
+
if (displayOverride !== undefined)
|
|
275
|
+
emitted["display"] = displayOverride;
|
|
216
276
|
}
|
|
217
|
-
else if (
|
|
218
|
-
|
|
219
|
-
|
|
277
|
+
else if (resolvedType === "enabled") {
|
|
278
|
+
// Transcribed from the 2.1.280 analysis document:
|
|
279
|
+
// let hf = mlo(_e);
|
|
280
|
+
// if (r.type === "enabled" && r.budgetTokens !== void 0) hf = r.budgetTokens;
|
|
281
|
+
// hf = Math.max(1024, Math.min(Rv - 1, hf));
|
|
282
|
+
// The default budget is the model's upper limit minus one. The caller's
|
|
283
|
+
// `budgetTokens` replaces it only when the caller itself declared an
|
|
284
|
+
// `enabled` request -- the guard reads the caller's raw `type`, not the
|
|
285
|
+
// resolved one, so an `adaptive` request downgraded to `enabled` on a
|
|
286
|
+
// model without adaptive thinking ignores its budget. The result is
|
|
287
|
+
// clamped to the emitted `max_tokens` minus one, and the floor of 1024 is
|
|
288
|
+
// applied after that clamp, so the floor wins when the two conflict.
|
|
289
|
+
//
|
|
290
|
+
// No older analysis document in this repository transcribes this
|
|
291
|
+
// computation at all, so the floor and the type guard are evidenced for
|
|
292
|
+
// 2.1.280 only. They are applied to every profile as one shared
|
|
293
|
+
// behaviour -- the same deliberate choice already made for the model-id
|
|
294
|
+
// normalizer ladder -- and nothing here asserts whether older clients
|
|
295
|
+
// had them.
|
|
296
|
+
//
|
|
297
|
+
// This is the one wire-visible consumer of the request-derived bound: on
|
|
298
|
+
// a 2.1.222+ profile a caller asking for a `max_tokens` above the
|
|
299
|
+
// catalogue's upper limit seeds the default budget from THEIR number
|
|
300
|
+
// minus one, not from the catalogue's.
|
|
301
|
+
const callerBudget = request?.type === "enabled" ? request.budgetTokens : undefined;
|
|
302
|
+
const requested = callerBudget ??
|
|
303
|
+
modelOutputTokenLimits(normalizedId, profile, maxTokens).upperLimit - 1;
|
|
304
|
+
emitted = {
|
|
305
|
+
budget_tokens: Math.max(1024, Math.min(maxTokens - 1, requested)),
|
|
306
|
+
};
|
|
307
|
+
emitted["type"] = "enabled";
|
|
308
|
+
if (display !== undefined)
|
|
309
|
+
emitted["display"] = display;
|
|
310
|
+
// Same in-place assignment as the adaptive arm: order stays
|
|
311
|
+
// `budget_tokens, type, display`.
|
|
312
|
+
if (displayOverride !== undefined)
|
|
313
|
+
emitted["display"] = displayOverride;
|
|
314
|
+
}
|
|
315
|
+
else if (resolvedType === "disabled") {
|
|
220
316
|
emitted = { type: "disabled" };
|
|
221
317
|
}
|
|
222
318
|
// Upstream `Jr = Xn?.type === "enabled" || Xn?.type === "adaptive"
|
|
223
319
|
// || Xn === void 0 && U4e(u)`.
|
|
320
|
+
// Upstream uses this to demote `tool_choice` of type `tool`; the
|
|
321
|
+
// request body also demotes type `any` on it, a documented divergence.
|
|
224
322
|
const extendedThinkingActive = emitted?.["type"] === "enabled" ||
|
|
225
323
|
emitted?.["type"] === "adaptive" ||
|
|
226
324
|
(emitted === undefined && capabilities.rejectsDisabledThinking);
|
package/dist/thinking.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thinking.js","sourceRoot":"","sources":["../src/thinking.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAO5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"thinking.js","sourceRoot":"","sources":["../src/thinking.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAO5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AAyEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,UAAU,sBAAsB,CACpC,YAAoB,EACpB,UAAqC,2BAA2B,EAChE,kBAA2B;IAE3B,MAAM,QAAQ,GAAG,qBAAqB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAE9D;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,IACE,gBAAgB,CAAC,OAAO,CAAC,CAAC,0BAA0B;QACpD,kBAAkB,KAAK,SAAS;QAChC,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC;QACxC,kBAAkB,IAAI,IAAI,EAC1B,CAAC;QACD,MAAM,UAAU,GAAG,kBAAkB,CAAC;QACtC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IACzE,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,qBAAqB,CAC5B,YAAoB,EACpB,OAAkC;IAElC,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,YAAY,CAAC,EAAE,eAAe,CAAC;IACxE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,2EAA2E;QAC3E,4CAA4C;QAC5C,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;IACnE,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,IAAI,YAAY,KAAK,iBAAiB,EAAE,CAAC;QACvC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAChD,CAAC;IACD,IAAI,YAAY,KAAK,eAAe,EAAE,CAAC;QACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,YAAY,KAAK,iBAAiB,EAAE,CAAC;QACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,YAAY,KAAK,gBAAgB,EAAE,CAAC;QACtC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,cAAc,CAC5B,SAAiB,EACjB,YAAoB,EACpB,UAAqC,2BAA2B;IAEhE,OAAO,IAAI,CAAC,GAAG,CACb,SAAS,EACT,sBAAsB,CAAC,YAAY,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,OAAO,CACjE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAgB,EAChB,YAAoC,EACpC,UAAgC;IAEhC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClE,MAAM,MAAM,GAAG,OAAkC,CAAC;IAClD,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IAChD,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,OAAO,KAAK,YAAY,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACpE,OAAO,CACL,YAAY,CAAC,QAAQ;QACrB,YAAY,CAAC,mBAAmB;QAChC,UAAU,CAAC,wBAAwB,CACpC,CAAC;AACJ,CAAC;AAQD;;;;;;;GAOG;AACH,SAAS,mBAAmB,CAC1B,OAAgB;IAEhB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACtE,MAAM,IAAI,GAAI,OAAmC,CAAC,MAAM,CAAC,CAAC;IAC1D,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAgD,EAChD,YAAoC;IAEpC,MAAM,aAAa,GAAG,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,UAAU,CAAC;IAC9E,IAAI,aAAa,IAAI,YAAY,CAAC,QAAQ,EAAE,CAAC;QAC3C,OAAO,YAAY,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,CAAC;IACD,IACE,WAAW,KAAK,UAAU;QAC1B,YAAY,CAAC,QAAQ;QACrB,CAAC,YAAY,CAAC,uBAAuB,EACrC,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAgB,EAChB,YAAoC;IAEpC,MAAM,IAAI,GAAG,mBAAmB,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC;IAC7E,OAAO,CACL,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,SAAS,CAAC;QAC3C,YAAY,CAAC,mBAAmB,CACjC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAoC,EACpC,YAAoB,EACpB,YAAoC,EACpC,UAAgC,EAChC,SAAiB,EACjB,UAAqC,2BAA2B,EAChE,eAA2B;IAE3B,0EAA0E;IAC1E,MAAM,aAAa,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,CAAC;IAC3E,MAAM,aAAa,GAAG,uBAAuB,CAC3C,OAAO,EACP,YAAY,EACZ,UAAU,CACX,CAAC;IACF,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7D,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;IAEtE,IAAI,OAA4C,CAAC;IAEjD,IAAI,YAAY,KAAK,UAAU,EAAE,CAAC;QAChC,OAAO,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC;QACxD,2EAA2E;QAC3E,yEAAyE;QACzE,IAAI,eAAe,KAAK,SAAS;YAAE,OAAO,CAAC,SAAS,CAAC,GAAG,eAAe,CAAC;IAC1E,CAAC;SAAM,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QACtC,kDAAkD;QAClD,sBAAsB;QACtB,gFAAgF;QAChF,+CAA+C;QAC/C,wEAAwE;QACxE,qEAAqE;QACrE,wEAAwE;QACxE,sEAAsE;QACtE,oEAAoE;QACpE,0EAA0E;QAC1E,qEAAqE;QACrE,EAAE;QACF,iEAAiE;QACjE,wEAAwE;QACxE,gEAAgE;QAChE,wEAAwE;QACxE,sEAAsE;QACtE,YAAY;QACZ,EAAE;QACF,yEAAyE;QACzE,kEAAkE;QAClE,qEAAqE;QACrE,uCAAuC;QACvC,MAAM,YAAY,GAChB,OAAO,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;QACjE,MAAM,SAAS,GACb,YAAY;YACZ,sBAAsB,CAAC,YAAY,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC;QAC1E,OAAO,GAAG;YACR,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,EAAE,SAAS,CAAC,CAAC;SAClE,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;QAC5B,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC;QACxD,4DAA4D;QAC5D,kCAAkC;QAClC,IAAI,eAAe,KAAK,SAAS;YAAE,OAAO,CAAC,SAAS,CAAC,GAAG,eAAe,CAAC;IAC1E,CAAC;SAAM,IAAI,YAAY,KAAK,UAAU,EAAE,CAAC;QACvC,OAAO,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IACjC,CAAC;IAED,mEAAmE;IACnE,2CAA2C;IAC3C,iEAAiE;IACjE,uEAAuE;IACvE,MAAM,sBAAsB,GAC1B,OAAO,EAAE,CAAC,MAAM,CAAC,KAAK,SAAS;QAC/B,OAAO,EAAE,CAAC,MAAM,CAAC,KAAK,UAAU;QAChC,CAAC,OAAO,KAAK,SAAS,IAAI,YAAY,CAAC,uBAAuB,CAAC,CAAC;IAElE,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QACnE,aAAa;QACb,sBAAsB;KACvB,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tormentalabs/claude-code-wire-compat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Runtime-neutral Claude Code Messages wire compatibility primitives",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"./profiles/claude-code-2.1.233": {
|
|
38
38
|
"types": "./dist/profiles/claude-code-2.1.233.d.ts",
|
|
39
39
|
"import": "./dist/profiles/claude-code-2.1.233.js"
|
|
40
|
+
},
|
|
41
|
+
"./profiles/claude-code-2.1.280": {
|
|
42
|
+
"types": "./dist/profiles/claude-code-2.1.280.d.ts",
|
|
43
|
+
"import": "./dist/profiles/claude-code-2.1.280.js"
|
|
40
44
|
}
|
|
41
45
|
},
|
|
42
46
|
"types": "./dist/index.d.ts",
|
|
@@ -51,9 +55,8 @@
|
|
|
51
55
|
"format:check": "prettier --check .",
|
|
52
56
|
"test": "vitest run",
|
|
53
57
|
"test:coverage": "vitest run --coverage",
|
|
54
|
-
"test:pack": "node scripts/verify-packed-consumers.mjs",
|
|
58
|
+
"test:pack": "npm run build && node scripts/verify-packed-consumers.mjs",
|
|
55
59
|
"pack:check": "npm pack --dry-run && publint && attw --pack . --profile esm-only",
|
|
56
|
-
"drift:check": "node scripts/verify-drift.mjs",
|
|
57
60
|
"fixtures:check": "node scripts/seal-golden-fixtures.mjs --check",
|
|
58
61
|
"fixtures:seal": "node scripts/seal-golden-fixtures.mjs --write",
|
|
59
62
|
"extract:profile": "node scripts/extract-upstream-profile.mjs"
|
package/src/betas.ts
CHANGED
|
@@ -11,8 +11,10 @@ import {
|
|
|
11
11
|
supportsStructuredOutputs,
|
|
12
12
|
} from "./model-capabilities.js";
|
|
13
13
|
import { BETA_REGISTRY_2_1_233 } from "./profiles/beta-registry-2.1.233.js";
|
|
14
|
+
import { BETA_REGISTRY_2_1_280 } from "./profiles/beta-registry-2.1.280.js";
|
|
14
15
|
import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
|
|
15
16
|
import { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
|
|
17
|
+
import { CLAUDE_CODE_2_1_280_PROFILE } from "./profiles/claude-code-2.1.280.js";
|
|
16
18
|
|
|
17
19
|
/*
|
|
18
20
|
* Provenance. The emitted order is a port of the genuine client's base beta set
|
|
@@ -41,13 +43,22 @@ interface BetaRegistryEntry {
|
|
|
41
43
|
* The entries the push sites below require, as a structural contract rather
|
|
42
44
|
* than a reference to one concrete registry.
|
|
43
45
|
*
|
|
44
|
-
* Registry versions have different key sets. Every key here
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* optionality part of the TYPE is what forces every future registry
|
|
50
|
-
* checked against the push sites at compile time instead of at runtime.
|
|
46
|
+
* Registry versions have different key sets. Every REQUIRED key here is present
|
|
47
|
+
* in all of them, so those push sites index directly. The optional ones are
|
|
48
|
+
* optional because they exist in some registry versions and not others, and a
|
|
49
|
+
* push site whose key is absent becomes inert: it evaluates its gates, finds no
|
|
50
|
+
* entry, pushes nothing, and the surrounding order closes up with no gap.
|
|
51
|
+
* Making the optionality part of the TYPE is what forces every future registry
|
|
52
|
+
* to be checked against the push sites at compile time instead of at runtime.
|
|
53
|
+
*
|
|
54
|
+
* `NARRATION_SUMMARIES` is optional in the older direction -- upstream removed
|
|
55
|
+
* it after 2.1.195 (see `src/profiles/beta-registry-2.1.233.ts`). The keys
|
|
56
|
+
* added for 2.1.280 -- the remaining optional members declared below -- are
|
|
57
|
+
* optional in the newer direction: they are absent from the 2.1.195 registry,
|
|
58
|
+
* and all but `PER_MESSAGE_EFFORT` are absent from the 2.1.233 one. `PER_MESSAGE_EFFORT` IS declared by the 2.1.233 registry, so
|
|
59
|
+
* registry absence does not keep its site inert there; what does is the
|
|
60
|
+
* catalogue, because no 2.1.233 model declares the backing capability. The two
|
|
61
|
+
* mechanisms are not interchangeable and both are load-bearing.
|
|
51
62
|
*/
|
|
52
63
|
export interface ComposableBetaRegistry {
|
|
53
64
|
readonly CLAUDE_CODE: BetaRegistryEntry;
|
|
@@ -60,7 +71,12 @@ export interface ComposableBetaRegistry {
|
|
|
60
71
|
readonly STRUCTURED_OUTPUTS: BetaRegistryEntry;
|
|
61
72
|
readonly PROMPT_CACHING_SCOPE: BetaRegistryEntry;
|
|
62
73
|
readonly MID_CONVERSATION_SYSTEM: BetaRegistryEntry;
|
|
74
|
+
readonly PER_MESSAGE_EFFORT?: BetaRegistryEntry;
|
|
75
|
+
readonly MID_CONV_TOOL_CHANGE?: BetaRegistryEntry;
|
|
76
|
+
readonly MID_CONVERSATION_SYSTEM_CLEAR_AT?: BetaRegistryEntry;
|
|
63
77
|
readonly EFFORT: BetaRegistryEntry;
|
|
78
|
+
readonly THINKING_BINDING_CONTROLS?: BetaRegistryEntry;
|
|
79
|
+
readonly THINKING_DISPLAY_UPDATES?: BetaRegistryEntry;
|
|
64
80
|
readonly SPEED: BetaRegistryEntry;
|
|
65
81
|
readonly AFK_MODE: BetaRegistryEntry;
|
|
66
82
|
readonly EXTENDED_CACHE_TTL: BetaRegistryEntry;
|
|
@@ -73,6 +89,7 @@ const PROFILE_BETA_REGISTRIES: ReadonlyMap<string, ComposableBetaRegistry> =
|
|
|
73
89
|
new Map<string, ComposableBetaRegistry>([
|
|
74
90
|
[CLAUDE_CODE_2_1_195_PROFILE.id, BETA_REGISTRY],
|
|
75
91
|
[CLAUDE_CODE_2_1_233_PROFILE.id, BETA_REGISTRY_2_1_233],
|
|
92
|
+
[CLAUDE_CODE_2_1_280_PROFILE.id, BETA_REGISTRY_2_1_280],
|
|
76
93
|
]);
|
|
77
94
|
|
|
78
95
|
/**
|
|
@@ -84,8 +101,17 @@ const PROFILE_BETA_REGISTRIES: ReadonlyMap<string, ComposableBetaRegistry> =
|
|
|
84
101
|
* give `composeBetas` a second, differently-worded opinion about profile
|
|
85
102
|
* validity. Standalone callers keep the 2.1.195 behaviour they had before
|
|
86
103
|
* profiles were a parameter.
|
|
104
|
+
*
|
|
105
|
+
* Exported for tests only, and deliberately NOT re-exported from
|
|
106
|
+
* `src/index.ts`: the public runtime surface stays closed. That fallback is
|
|
107
|
+
* precisely why a test needs to reach this function. A profile bound to the
|
|
108
|
+
* wrong registry, or to none, silently composes against 2.1.195 instead of
|
|
109
|
+
* failing, and every required key of `ComposableBetaRegistry` currently
|
|
110
|
+
* carries an identical header in all three registries -- so a mis-binding
|
|
111
|
+
* changes no emitted byte and no behavioural suite can see it. Asking this
|
|
112
|
+
* function directly is the only way to observe the binding at all.
|
|
87
113
|
*/
|
|
88
|
-
function resolveBetaRegistry(
|
|
114
|
+
export function resolveBetaRegistry(
|
|
89
115
|
profile: ClaudeCodeProtocolProfile,
|
|
90
116
|
): ComposableBetaRegistry {
|
|
91
117
|
return PROFILE_BETA_REGISTRIES.get(profile.id) ?? BETA_REGISTRY;
|
|
@@ -96,6 +122,17 @@ export interface ComposeBetasInput {
|
|
|
96
122
|
readonly normalizedId: string;
|
|
97
123
|
readonly capabilities: ClaudeCodeCapabilities;
|
|
98
124
|
readonly thinkingDisplayActive: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Whether a `thinking` object of type `adaptive` or `enabled` will reach the
|
|
127
|
+
* wire for this request AND the model is interleaved-thinking capable, i.e.
|
|
128
|
+
* upstream `ac` minus its `Fg()` term. Compute it with `isThinkingActive`
|
|
129
|
+
* from `./thinking.js`; never re-derive it at a call site.
|
|
130
|
+
*
|
|
131
|
+
* Required rather than optional: it is a total function of inputs every call
|
|
132
|
+
* site already holds, and requiring it is what forces a new call site to
|
|
133
|
+
* decide rather than silently inherit a default.
|
|
134
|
+
*/
|
|
135
|
+
readonly thinkingActive: boolean;
|
|
99
136
|
readonly cacheTtl?: "5m" | "1h" | null;
|
|
100
137
|
readonly speed?: "standard" | "fast" | null;
|
|
101
138
|
/**
|
|
@@ -129,6 +166,19 @@ export interface ComposeBetasInput {
|
|
|
129
166
|
export interface ComposedBetas {
|
|
130
167
|
readonly betas: readonly string[];
|
|
131
168
|
readonly suppressedBetaNames: readonly string[];
|
|
169
|
+
/**
|
|
170
|
+
* Present only when push site 12b fired. This is the body-side half of a
|
|
171
|
+
* beta/body pair: the site pushes the display-updates beta AND decides that
|
|
172
|
+
* the body's `thinking` object carries `display: "updates"`. The consumer is
|
|
173
|
+
* the canonical body builder (`buildCanonicalBody`), which hands it to
|
|
174
|
+
* `resolveThinking`.
|
|
175
|
+
*
|
|
176
|
+
* `suppressBetas` cannot unset it. Suppressing the beta identifier removes
|
|
177
|
+
* the HEADER only; the body keeps `display: "updates"`. That matches the
|
|
178
|
+
* existing coupled pair where suppressing `effort-2025-11-24` leaves
|
|
179
|
+
* `output_config.effort` in the body.
|
|
180
|
+
*/
|
|
181
|
+
readonly thinkingDisplayOverride?: "updates";
|
|
132
182
|
}
|
|
133
183
|
|
|
134
184
|
/**
|
|
@@ -240,10 +290,151 @@ export function composeBetasWithAudit(
|
|
|
240
290
|
|
|
241
291
|
// No web-search beta: upstream pushes it only for vertex and foundry.
|
|
242
292
|
if (experimental) out.push(registry.PROMPT_CACHING_SCOPE.header);
|
|
243
|
-
|
|
293
|
+
|
|
294
|
+
/*
|
|
295
|
+
* Upstream `jR`. The local boolean below records that this site PUSHED, which
|
|
296
|
+
* is what sites 11b and 11c gate on -- upstream reads `Ee.includes(jR)`, the
|
|
297
|
+
* composed array, not the registry. Asking the registry instead would fire
|
|
298
|
+
* those two sites for a model this one skipped.
|
|
299
|
+
*/
|
|
300
|
+
const midConversationSystemFired = supportsMidConversationSystem(
|
|
301
|
+
input.normalizedId,
|
|
302
|
+
profile,
|
|
303
|
+
);
|
|
304
|
+
if (midConversationSystemFired) {
|
|
244
305
|
out.push(registry.MID_CONVERSATION_SYSTEM.header);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/*
|
|
309
|
+
* Site 11a. Upstream `wRt`, which reduces to
|
|
310
|
+
* `Fg() && mD(provider) && catalogue-declares-per_turn_effort`; the provider
|
|
311
|
+
* term is first-party here by construction.
|
|
312
|
+
*/
|
|
313
|
+
const perMessageEffort = registry.PER_MESSAGE_EFFORT;
|
|
314
|
+
if (
|
|
315
|
+
experimental &&
|
|
316
|
+
input.capabilities.perTurnEffort &&
|
|
317
|
+
perMessageEffort !== undefined
|
|
318
|
+
) {
|
|
319
|
+
out.push(perMessageEffort.header);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/*
|
|
323
|
+
* Site 11b. Upstream `oQt() && Tue(model)`. `Tue` opens with
|
|
324
|
+
* `if (!Fg() || !kue(e)) return false`, so the experimental gate is upstream's
|
|
325
|
+
* and not an addition. Step 3 of `Tue` -- which sends the beta for any model
|
|
326
|
+
* with NO catalogue entry -- is deliberately not ported; see the analysis
|
|
327
|
+
* document's divergence record.
|
|
328
|
+
*/
|
|
329
|
+
const midConvToolChange = registry.MID_CONV_TOOL_CHANGE;
|
|
330
|
+
if (
|
|
331
|
+
experimental &&
|
|
332
|
+
midConversationSystemFired &&
|
|
333
|
+
input.capabilities.midConvToolChange &&
|
|
334
|
+
midConvToolChange !== undefined
|
|
335
|
+
) {
|
|
336
|
+
out.push(midConvToolChange.header);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/*
|
|
340
|
+
* Site 11c. Upstream `Mo`, which is the one site whose experimental gate is
|
|
341
|
+
* invisible at the statement level -- `Mo` names no `Fg()` at all. The gate is
|
|
342
|
+
* reached three calls deep instead: `Mee` is `oRt(model) !== "off"`, `oRt`
|
|
343
|
+
* wraps `fur`, and `fur` opens by returning `"off"` when `Fg()` is false. So
|
|
344
|
+
* `experimental` is upstream's own conjunct here exactly as it is at 11a and
|
|
345
|
+
* 11b, and a reader who checks only the `Mo` expression will wrongly conclude
|
|
346
|
+
* it was invented. It was not.
|
|
347
|
+
*
|
|
348
|
+
* `Mo`'s remaining conjuncts are constants for this package. `!l7(querySource)`
|
|
349
|
+
* holds because `l7` is true only for the two auto-mode query sources. `!Vr`
|
|
350
|
+
* holds because `Vr` is a per-session latch, and a stateless package composes
|
|
351
|
+
* every request as a first request. `Mee`'s other terms are fixed on the
|
|
352
|
+
* first-party path. What is left is `Ee.includes(jR)`, which is precisely the
|
|
353
|
+
* `midConversationSystemFired` outcome recorded at site 11.
|
|
354
|
+
*/
|
|
355
|
+
const midConversationSystemClearAt =
|
|
356
|
+
registry.MID_CONVERSATION_SYSTEM_CLEAR_AT;
|
|
357
|
+
if (
|
|
358
|
+
experimental &&
|
|
359
|
+
midConversationSystemFired &&
|
|
360
|
+
midConversationSystemClearAt !== undefined
|
|
361
|
+
) {
|
|
362
|
+
out.push(midConversationSystemClearAt.header);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Site 12.
|
|
245
366
|
if (input.capabilities.effort) out.push(registry.EFFORT.header);
|
|
246
367
|
|
|
368
|
+
/*
|
|
369
|
+
* Site 12a. Upstream `er && Fg()` with the latch write, which on the pinned
|
|
370
|
+
* first-party path collapses to thinking-active AND experimental.
|
|
371
|
+
* The coupled body field `thinking.block_binding` is NOT emitted: it needs a
|
|
372
|
+
* host override this package cannot observe.
|
|
373
|
+
*/
|
|
374
|
+
const thinkingBindingControls = registry.THINKING_BINDING_CONTROLS;
|
|
375
|
+
if (
|
|
376
|
+
experimental &&
|
|
377
|
+
input.thinkingActive &&
|
|
378
|
+
thinkingBindingControls !== undefined
|
|
379
|
+
) {
|
|
380
|
+
out.push(thinkingBindingControls.header);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/*
|
|
384
|
+
* Site 12b. Upstream guards the push with
|
|
385
|
+
* `(yc?.type === "adaptive" || yc?.type === "enabled") && ac && firstParty
|
|
386
|
+
* && !callerSuppliedDisplay && ...` plus the simulate-proxy environment
|
|
387
|
+
* variable and a per-session latch. The reachable remainder of that guard is
|
|
388
|
+
* modelled below: `experimental` is upstream's `Fg()` term inside `ac`,
|
|
389
|
+
* `input.thinkingActive` is the thinking-type test together with the rest of
|
|
390
|
+
* `ac`, and `!input.thinkingDisplayActive` is the caller-supplied-display
|
|
391
|
+
* test. First-party is not modelled because this package only builds the
|
|
392
|
+
* first-party path, the simulate-proxy env var is not modelled because the
|
|
393
|
+
* package reads no environment, and the per-session latch is always empty in
|
|
394
|
+
* a stateless package that composes every request as a first request.
|
|
395
|
+
*
|
|
396
|
+
* The branch is additionally reached only when the display-mode resolver
|
|
397
|
+
* `Gxt` returns its connector-text result; its other results break out
|
|
398
|
+
* before the push. `!policy.thinkingSummariesShown` is that resolver's
|
|
399
|
+
* `sQt()` term, mapped onto the profile flag exactly as at the
|
|
400
|
+
* redact-thinking site above. See the 2.1.280 analysis document, §6.4, for
|
|
401
|
+
* the resolver itself.
|
|
402
|
+
*
|
|
403
|
+
* The resolver's other two diverting branches are already covered by
|
|
404
|
+
* `!thinkingDisplayActive`. A caller display of `"summarized"` takes the
|
|
405
|
+
* first branch, and `"omitted"` takes the second whenever the
|
|
406
|
+
* explicit-display flag is falsy, which is the only state this package can
|
|
407
|
+
* express -- it models no equivalent of that flag. Under a truthy
|
|
408
|
+
* explicit-display flag upstream would fall through and overwrite an
|
|
409
|
+
* explicit `"omitted"` with `"updates"`; that input is unreachable here, and
|
|
410
|
+
* is recorded rather than modelled.
|
|
411
|
+
*
|
|
412
|
+
* The site is coupled: it pushes the beta, records `display: "updates"` for
|
|
413
|
+
* the body builder, and removes the previously composed redact-thinking beta
|
|
414
|
+
* (upstream `qu()`).
|
|
415
|
+
*
|
|
416
|
+
* The splice sits INSIDE this block and is deliberately the LAST thing it
|
|
417
|
+
* does. At this point `out` holds only canonical pushes, because the caller
|
|
418
|
+
* `additionalBetas` merge runs later. So a caller who explicitly supplies
|
|
419
|
+
* `redact-thinking-2026-02-12` in `additionalBetas` still gets it, precisely
|
|
420
|
+
* because the merge's `if (!out.includes(beta))` test now succeeds. Moving
|
|
421
|
+
* the splice after the merge would silently eat that caller's entry.
|
|
422
|
+
*/
|
|
423
|
+
const thinkingDisplayUpdates = registry.THINKING_DISPLAY_UPDATES;
|
|
424
|
+
let thinkingDisplayOverride: "updates" | undefined;
|
|
425
|
+
if (
|
|
426
|
+
experimental &&
|
|
427
|
+
input.thinkingActive &&
|
|
428
|
+
!policy.thinkingSummariesShown &&
|
|
429
|
+
!input.thinkingDisplayActive &&
|
|
430
|
+
thinkingDisplayUpdates !== undefined
|
|
431
|
+
) {
|
|
432
|
+
out.push(thinkingDisplayUpdates.header);
|
|
433
|
+
thinkingDisplayOverride = "updates";
|
|
434
|
+
const redactIndex = out.indexOf(registry.REDACT_THINKING.header);
|
|
435
|
+
if (redactIndex !== -1) out.splice(redactIndex, 1);
|
|
436
|
+
}
|
|
437
|
+
|
|
247
438
|
if (input.speed === "fast" && !out.includes(registry.SPEED.header)) {
|
|
248
439
|
out.push(registry.SPEED.header);
|
|
249
440
|
}
|
|
@@ -286,6 +477,9 @@ export function composeBetasWithAudit(
|
|
|
286
477
|
return Object.freeze({
|
|
287
478
|
betas: Object.freeze(out),
|
|
288
479
|
suppressedBetaNames: NO_SUPPRESSED_BETAS,
|
|
480
|
+
...(thinkingDisplayOverride === undefined
|
|
481
|
+
? {}
|
|
482
|
+
: { thinkingDisplayOverride }),
|
|
289
483
|
});
|
|
290
484
|
}
|
|
291
485
|
const suppressed = new Set(validateAdditionalBetas(input.suppressBetas));
|
|
@@ -298,5 +492,10 @@ export function composeBetasWithAudit(
|
|
|
298
492
|
return Object.freeze({
|
|
299
493
|
betas: Object.freeze(kept),
|
|
300
494
|
suppressedBetaNames: Object.freeze(removed),
|
|
495
|
+
// The suppression filter above touches only the header list; the body
|
|
496
|
+
// half of the site-12b pair survives it by design.
|
|
497
|
+
...(thinkingDisplayOverride === undefined
|
|
498
|
+
? {}
|
|
499
|
+
: { thinkingDisplayOverride }),
|
|
301
500
|
});
|
|
302
501
|
}
|