@tormentalabs/claude-code-wire-compat 0.5.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 +167 -0
- package/README.md +19 -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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +19 -1
- package/dist/model-identity.js.map +1 -1
- package/dist/model-queries.d.ts +17 -5
- package/dist/model-queries.d.ts.map +1 -1
- package/dist/model-queries.js +33 -6
- package/dist/model-queries.js.map +1 -1
- 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 -2
- 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 +2 -0
- package/src/model-capabilities.ts +55 -19
- package/src/model-identity.ts +14 -1
- package/src/model-queries.ts +38 -6
- 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/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
|
}
|
package/src/build-request.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { resolveModel } from "./models.js";
|
|
31
31
|
import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
|
|
32
32
|
import { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
|
|
33
|
+
import { CLAUDE_CODE_2_1_280_PROFILE } from "./profiles/claude-code-2.1.280.js";
|
|
33
34
|
import type { NormalizedRequestInput } from "./redaction.js";
|
|
34
35
|
import { buildRedactedEvidence, toSafeErrorDetails } from "./redaction.js";
|
|
35
36
|
import {
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
} from "./request-body.js";
|
|
39
40
|
import { sha256Hex } from "./sha256.js";
|
|
40
41
|
import { buildCanonicalSystem, IDENTITY_TEXT } from "./system-prompt.js";
|
|
41
|
-
import { isThinkingDisplayActive } from "./thinking.js";
|
|
42
|
+
import { isThinkingActive, isThinkingDisplayActive } from "./thinking.js";
|
|
42
43
|
import { classifySurrogateAt } from "./unicode.js";
|
|
43
44
|
|
|
44
45
|
const METHOD = "POST";
|
|
@@ -152,6 +153,8 @@ const CAPABILITY_KEYS = [
|
|
|
152
153
|
"contextManagement",
|
|
153
154
|
"temperature",
|
|
154
155
|
"rejectsDisabledThinking",
|
|
156
|
+
"midConvToolChange",
|
|
157
|
+
"perTurnEffort",
|
|
155
158
|
] as const;
|
|
156
159
|
const CAPABILITY_KEY_SET = new Set(CAPABILITY_KEYS);
|
|
157
160
|
/** Adds the optional package-extension override keys carried by evidence. */
|
|
@@ -319,10 +322,14 @@ function containsString(value: unknown, target: string): boolean {
|
|
|
319
322
|
}
|
|
320
323
|
|
|
321
324
|
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
325
|
+
* Every pinned profile singleton, and nothing else. The set is not enumerated
|
|
326
|
+
* in prose here: the literal below IS the enumeration and cannot go stale,
|
|
327
|
+
* whereas a count in a comment goes stale on the next port -- which is exactly
|
|
328
|
+
* what happened to the text this replaced. Which profile a caller gets when it
|
|
329
|
+
* passes none is `DEFAULT_PROFILE` below and is deliberately not restated
|
|
330
|
+
* here, so that a default switch touches one line rather than two. Admitting a
|
|
331
|
+
* profile is exactly this list -- `validateProfile` never changed to accept a
|
|
332
|
+
* new one.
|
|
326
333
|
*
|
|
327
334
|
* Membership is by REFERENCE, deliberately. A structural check would accept a
|
|
328
335
|
* caller-built object that merely looks like a pinned profile, and every wire
|
|
@@ -339,11 +346,16 @@ function containsString(value: unknown, target: string): boolean {
|
|
|
339
346
|
const ACCEPTED_PROFILES: ReadonlySet<ClaudeCodeProtocolProfile> = new Set([
|
|
340
347
|
CLAUDE_CODE_2_1_195_PROFILE,
|
|
341
348
|
CLAUDE_CODE_2_1_233_PROFILE,
|
|
349
|
+
CLAUDE_CODE_2_1_280_PROFILE,
|
|
342
350
|
]);
|
|
343
351
|
|
|
344
352
|
/**
|
|
345
|
-
* The profile
|
|
346
|
-
*
|
|
353
|
+
* The profile the request-building entry points (`buildClaudeCodeRequest`,
|
|
354
|
+
* `buildClaudeCodeCountTokensRequest` and `parseBuiltClaudeCodeRequest`)
|
|
355
|
+
* resolve to when the caller supplies none. It is not a global default: the
|
|
356
|
+
* model-query and anti-verbosity helpers deliberately keep their own,
|
|
357
|
+
* separately declared default and are NOT governed by this constant. Declared
|
|
358
|
+
* once so that the default is a single, greppable seam: a test
|
|
347
359
|
* that means "whatever the default is" reads THIS instead of naming a
|
|
348
360
|
* version, which keeps a default switch to a one-line diff and keeps tests
|
|
349
361
|
* that genuinely mean 2.1.195 honest about saying so.
|
|
@@ -352,7 +364,7 @@ const ACCEPTED_PROFILES: ReadonlySet<ClaudeCodeProtocolProfile> = new Set([
|
|
|
352
364
|
* from `src/index.ts`: the public runtime surface stays closed.
|
|
353
365
|
*/
|
|
354
366
|
export const DEFAULT_PROFILE: ClaudeCodeProtocolProfile =
|
|
355
|
-
|
|
367
|
+
CLAUDE_CODE_2_1_280_PROFILE;
|
|
356
368
|
|
|
357
369
|
function validateProfile(
|
|
358
370
|
profile: ClaudeCodeProtocolProfile,
|
|
@@ -950,6 +962,8 @@ function requestedCapabilities(
|
|
|
950
962
|
temperature: raw?.temperature ?? supported.temperature,
|
|
951
963
|
rejectsDisabledThinking:
|
|
952
964
|
raw?.rejectsDisabledThinking ?? supported.rejectsDisabledThinking,
|
|
965
|
+
midConvToolChange: raw?.midConvToolChange ?? supported.midConvToolChange,
|
|
966
|
+
perTurnEffort: raw?.perTurnEffort ?? supported.perTurnEffort,
|
|
953
967
|
};
|
|
954
968
|
for (const key of CAPABILITY_KEYS) {
|
|
955
969
|
if (typeof result[key] !== "boolean") fail("UNSUPPORTED_CAPABILITY");
|
|
@@ -1038,7 +1052,7 @@ function parseCapabilityDecisions(
|
|
|
1038
1052
|
value: unknown,
|
|
1039
1053
|
): ClaudeCodeCapabilityDecisions {
|
|
1040
1054
|
if (!isRecord(value)) fail();
|
|
1041
|
-
//
|
|
1055
|
+
// Every capability key is mandatory; the package-extension override keys
|
|
1042
1056
|
// are optional and must survive the round-trip untouched, so they are allowed
|
|
1043
1057
|
// here but never synthesized.
|
|
1044
1058
|
assertExactKeys(value, CAPABILITY_DECISION_KEY_SET);
|
|
@@ -1064,6 +1078,8 @@ function parseCapabilityDecisions(
|
|
|
1064
1078
|
contextManagement: readBoolean("contextManagement"),
|
|
1065
1079
|
temperature: readBoolean("temperature"),
|
|
1066
1080
|
rejectsDisabledThinking: readBoolean("rejectsDisabledThinking"),
|
|
1081
|
+
midConvToolChange: readBoolean("midConvToolChange"),
|
|
1082
|
+
perTurnEffort: readBoolean("perTurnEffort"),
|
|
1067
1083
|
};
|
|
1068
1084
|
}
|
|
1069
1085
|
|
|
@@ -1071,7 +1087,7 @@ function parseCapabilityDecisions(
|
|
|
1071
1087
|
* Validates evidence against the profile the request was parsed under, not
|
|
1072
1088
|
* against a hardcoded singleton. `parseBuiltClaudeCodeRequest` already
|
|
1073
1089
|
* validates `url` against `pinnedProfile.endpoint`; the profile id is the one
|
|
1074
|
-
* remaining field where
|
|
1090
|
+
* remaining field where pinned profiles differ, so it has to follow
|
|
1075
1091
|
* the same source or a request built with a non-default profile could never
|
|
1076
1092
|
* be re-parsed. Still fail-closed: the profile reaching here has already
|
|
1077
1093
|
* passed `validateProfile`.
|
|
@@ -1442,6 +1458,10 @@ export async function buildClaudeCodeCountTokensRequest(
|
|
|
1442
1458
|
normalizedId: resolvedModel.id,
|
|
1443
1459
|
capabilities: resolvedModel.capabilities,
|
|
1444
1460
|
thinkingDisplayActive: false,
|
|
1461
|
+
// A constant, not a re-derivation: this path carries no thinking
|
|
1462
|
+
// request, so `isThinkingActive(undefined, capabilities)` is false
|
|
1463
|
+
// by construction.
|
|
1464
|
+
thinkingActive: false,
|
|
1445
1465
|
},
|
|
1446
1466
|
effectiveProfile,
|
|
1447
1467
|
),
|
|
@@ -1516,8 +1536,9 @@ export async function buildClaudeCodeCountTokensRequest(
|
|
|
1516
1536
|
/**
|
|
1517
1537
|
* Builds one canonical request for the pinned Claude Code wire profile.
|
|
1518
1538
|
*
|
|
1519
|
-
* @param profile - The
|
|
1520
|
-
* `
|
|
1539
|
+
* @param profile - The accepted values are the exported pinned profile
|
|
1540
|
+
* singletons held in `ACCEPTED_PROFILES`; omitting the argument resolves to
|
|
1541
|
+
* `DEFAULT_PROFILE`. Any other object, even a
|
|
1521
1542
|
* structurally identical clone, is rejected with `ClaudeCodeWireError` code
|
|
1522
1543
|
* `INVALID_INPUT`. This deliberate fail-closed behaviour prevents callers from
|
|
1523
1544
|
* substituting an unpinned protocol profile.
|
|
@@ -1581,13 +1602,12 @@ export async function buildClaudeCodeRequest(
|
|
|
1581
1602
|
validated.suppressBillingBlock,
|
|
1582
1603
|
validated.suppressIdentityBlock,
|
|
1583
1604
|
);
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
);
|
|
1605
|
+
/*
|
|
1606
|
+
* Composition runs BEFORE body construction so that a beta site can hand a
|
|
1607
|
+
* decision to the body emitter. Both calls are pure and neither feeds the
|
|
1608
|
+
* other's arguments today, so the order itself changes no serialised byte;
|
|
1609
|
+
* the swap lands on its own so the packed-consumer digests prove that.
|
|
1610
|
+
*/
|
|
1591
1611
|
const composedBetas = composeBetasWithAudit(
|
|
1592
1612
|
{
|
|
1593
1613
|
rawModel: validated.source.model,
|
|
@@ -1598,6 +1618,10 @@ export async function buildClaudeCodeRequest(
|
|
|
1598
1618
|
capabilities,
|
|
1599
1619
|
effectiveProfile.betaPolicy,
|
|
1600
1620
|
),
|
|
1621
|
+
thinkingActive: isThinkingActive(
|
|
1622
|
+
validated.source.thinking,
|
|
1623
|
+
capabilities,
|
|
1624
|
+
),
|
|
1601
1625
|
...(validated.source.cacheControl?.ttl === undefined
|
|
1602
1626
|
? {}
|
|
1603
1627
|
: { cacheTtl: validated.source.cacheControl.ttl }),
|
|
@@ -1616,6 +1640,14 @@ export async function buildClaudeCodeRequest(
|
|
|
1616
1640
|
},
|
|
1617
1641
|
effectiveProfile,
|
|
1618
1642
|
);
|
|
1643
|
+
const canonicalBody = buildCanonicalBody(
|
|
1644
|
+
evidenceRequest(validated.source, validated.source.model),
|
|
1645
|
+
effectiveModel,
|
|
1646
|
+
system,
|
|
1647
|
+
metadata,
|
|
1648
|
+
effectiveProfile,
|
|
1649
|
+
composedBetas.thinkingDisplayOverride,
|
|
1650
|
+
);
|
|
1619
1651
|
const betas = composedBetas.betas;
|
|
1620
1652
|
const headerPlan = buildOrderedHeaderPlan({
|
|
1621
1653
|
accessToken: validated.source.accessToken,
|
|
@@ -1706,8 +1738,9 @@ export async function buildClaudeCodeRequest(
|
|
|
1706
1738
|
/**
|
|
1707
1739
|
* Validates and clones a previously built request into a deeply frozen value.
|
|
1708
1740
|
*
|
|
1709
|
-
* @param profile - The
|
|
1710
|
-
* `
|
|
1741
|
+
* @param profile - The accepted values are the exported pinned profile
|
|
1742
|
+
* singletons held in `ACCEPTED_PROFILES`; omitting the argument resolves to
|
|
1743
|
+
* `DEFAULT_PROFILE`. Any other object, even a
|
|
1711
1744
|
* structurally identical clone, is rejected with `ClaudeCodeWireError` code
|
|
1712
1745
|
* `INVALID_INPUT`. This deliberate fail-closed behaviour prevents callers from
|
|
1713
1746
|
* substituting an unpinned protocol profile.
|
package/src/contracts.ts
CHANGED
|
@@ -669,6 +669,10 @@ export interface ClaudeCodeCapabilities {
|
|
|
669
669
|
readonly contextManagement: boolean;
|
|
670
670
|
readonly temperature: boolean;
|
|
671
671
|
readonly rejectsDisabledThinking: boolean;
|
|
672
|
+
/** Catalogue string `mid_conv_tool_change`. */
|
|
673
|
+
readonly midConvToolChange: boolean;
|
|
674
|
+
/** Catalogue string `per_turn_effort`. */
|
|
675
|
+
readonly perTurnEffort: boolean;
|
|
672
676
|
}
|
|
673
677
|
|
|
674
678
|
/** Host-state beta gates pinned for a default first-party environment. */
|
|
@@ -1221,8 +1225,9 @@ export interface RedactedRequestEvidence {
|
|
|
1221
1225
|
}
|
|
1222
1226
|
|
|
1223
1227
|
/**
|
|
1224
|
-
* Records
|
|
1225
|
-
*
|
|
1228
|
+
* Records every model capability decision -- the key set is exactly
|
|
1229
|
+
* `keyof ClaudeCodeCapabilities`, so it widens whenever that interface does --
|
|
1230
|
+
* plus any package-extension beta override the caller supplied.
|
|
1226
1231
|
*
|
|
1227
1232
|
* The override keys are OPTIONAL and are emitted only when the corresponding
|
|
1228
1233
|
* member of `betaOverrides` is present, so evidence for a request that omits
|
package/src/fingerprint.ts
CHANGED
|
@@ -56,6 +56,11 @@ export async function createBillingFingerprint(
|
|
|
56
56
|
crypto?: Pick<Crypto, "subtle">,
|
|
57
57
|
): Promise<string> {
|
|
58
58
|
const cryptoProvider = crypto ?? getDefaultCrypto();
|
|
59
|
+
// The upstream transcription in the analysis doc writes `||` here; `??` is a
|
|
60
|
+
// deliberate, safe difference, not a divergence to "fix" in either
|
|
61
|
+
// direction. A string subscript yields either a one-unit string, which is
|
|
62
|
+
// always truthy, or `undefined`, so no value makes the two operators
|
|
63
|
+
// disagree (and no test can tell them apart).
|
|
59
64
|
const material = `${FINGERPRINT_PREFIX}${firstUserText[4] ?? "0"}${firstUserText[7] ?? "0"}${firstUserText[20] ?? "0"}${cliVersion}`;
|
|
60
65
|
const bytes = new TextEncoder().encode(material);
|
|
61
66
|
|
package/src/headers.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import { ClaudeCodeWireError } from "./contracts.js";
|
|
9
9
|
import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
|
|
10
10
|
import { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
|
|
11
|
+
import { CLAUDE_CODE_2_1_280_PROFILE } from "./profiles/claude-code-2.1.280.js";
|
|
11
12
|
|
|
12
13
|
const HEADER_NAMES = Object.freeze({
|
|
13
14
|
anthropicBeta: "anthropic-beta",
|
|
@@ -163,13 +164,14 @@ function parseExtraHeaderPolicy(value: unknown): ClaudeCodeExtraHeaderPolicy {
|
|
|
163
164
|
|
|
164
165
|
/**
|
|
165
166
|
* Accepts a pinned profile by REFERENCE, never by shape, and returns the
|
|
166
|
-
* singleton itself so nothing downstream can be handed a look-alike.
|
|
167
|
-
*
|
|
167
|
+
* singleton itself so nothing downstream can be handed a look-alike. Each
|
|
168
|
+
* pinned profile widens the accepted set by exactly one object;
|
|
168
169
|
* anything else, including a structural clone, still fails closed.
|
|
169
170
|
*/
|
|
170
171
|
function parseProfile(value: unknown): ClaudeCodeProtocolProfile {
|
|
171
172
|
if (value === CLAUDE_CODE_2_1_195_PROFILE) return CLAUDE_CODE_2_1_195_PROFILE;
|
|
172
173
|
if (value === CLAUDE_CODE_2_1_233_PROFILE) return CLAUDE_CODE_2_1_233_PROFILE;
|
|
174
|
+
if (value === CLAUDE_CODE_2_1_280_PROFILE) return CLAUDE_CODE_2_1_280_PROFILE;
|
|
173
175
|
throw new ClaudeCodeWireError("INVALID_INPUT");
|
|
174
176
|
}
|
|
175
177
|
|
package/src/index.ts
CHANGED
|
@@ -82,5 +82,7 @@ export {
|
|
|
82
82
|
} from "./model-queries.js";
|
|
83
83
|
|
|
84
84
|
export { BETA_REGISTRY_2_1_233 } from "./profiles/beta-registry-2.1.233.js";
|
|
85
|
+
export { BETA_REGISTRY_2_1_280 } from "./profiles/beta-registry-2.1.280.js";
|
|
85
86
|
export { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
|
|
86
87
|
export { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
|
|
88
|
+
export { CLAUDE_CODE_2_1_280_PROFILE } from "./profiles/claude-code-2.1.280.js";
|