@tormentalabs/claude-code-wire-compat 0.1.0-rc.15 → 0.1.0-rc.17

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 CHANGED
@@ -2,7 +2,143 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.1.0-rc.15] - Unreleased
5
+ ## [0.1.0-rc.17] - Unreleased
6
+
7
+ ### Added
8
+
9
+ - **Root `suppressIdentityBlock` (seam S8).** The canonical identity block was
10
+ composed unconditionally, so a consumer switch whose meaning is "send neither
11
+ canonical block" — `token_economy.lean_system_non_main` in the `opencode`
12
+ plugin, which removed billing AND identity before it migrated to this package
13
+ — was a silent no-op. `suppressIdentityBlock: true` omits the identity block
14
+ entirely, which with `suppressBillingBlock` makes four canonical prefixes
15
+ legitimate: `[billing, identity]`, `[identity]`, `[billing]` and `[]`. Only a
16
+ boolean is accepted; anything else is `INVALID_INPUT`.
17
+ `evidence.identityBlockSuppressed` is emitted only when the block was actually
18
+ removed, mirroring `billingBlockSuppressed` exactly, so evidence stays
19
+ byte-identical for every request that ignores the seam. Recorded in
20
+ `docs/source-trace.md` as governance ledger L16.
21
+
22
+ This is a DIFFERENT field from the L10 `cacheControl.suppressIdentityBlock`,
23
+ which keeps the block and drops only its `cache_control` marker. The name
24
+ collision is deliberate — symmetry with `suppressBillingBlock` at the root was
25
+ judged worth more than a novel name — and the JSDoc of each field states what
26
+ it does and names the other by its full path.
27
+
28
+ The caller-block drop stays UNCONDITIONAL: a caller block byte-equal to the
29
+ identity text is removed even when the canonical one was suppressed, matching
30
+ the genuine client and keeping the parser's absence check below sound.
31
+
32
+ - **`preserveThinkingBlockCacheControl` (seam S9).** `thinking` and
33
+ `redacted_thinking` blocks were pinned to a strict allowlist — `signature`,
34
+ `thinking`, `type` and `data`, `type` — so a request carrying `cache_control`
35
+ on a reasoning block was rejected outright with `INVALID_INPUT`. The consumer
36
+ had no legal way out, and this is a PRODUCTION failure rather than a test
37
+ artefact: the Anthropic API answers a mutated reasoning block with
38
+
39
+ > `400 ... thinking or redacted_thinking blocks in the latest assistant`
40
+ > `message cannot be modified. These blocks must remain as they were in the`
41
+ > `original response.`
42
+
43
+ so `delete block.cache_control` before the call is itself the modification
44
+ that triggers the 400. `preserveThinkingBlockCacheControl: true` accepts the
45
+ key and copies it to the body VERBATIM — caller key order intact, no TTL
46
+ applied, no breakpoint placed. Only a boolean is accepted; anything else is
47
+ `INVALID_INPUT`.
48
+
49
+ The allowlist grows by `cache_control` and by NOTHING else: an unknown key on
50
+ a reasoning block is still `INVALID_INPUT` with the seam active. The value
51
+ passes the same `cache_control` validator every other block uses —
52
+ `{ type: "ephemeral" }` with an optional `ttl` — so a malformed marker still
53
+ fails closed; the `scope` key that `text` blocks tolerate for legacy reasons
54
+ is deliberately NOT accepted, the API never returning it on a reasoning block.
55
+ The marker takes no part in this package's cache-control machinery:
56
+ `applySystemCacheControl` is untouched and `applyMessageCacheControl` already
57
+ exempts reasoning blocks from both the strip and the breakpoint pass.
58
+
59
+ `evidence.thinkingBlockCacheControlPreserved` is emitted only when the seam
60
+ was active AND at least one emitted block actually carried a marker — opting
61
+ in without using it records nothing — mirroring `billingBlockSuppressed`
62
+ exactly, so evidence stays byte-identical for every request that ignores the
63
+ seam. `parseBuiltClaudeCodeRequest` CONFIRMS that claim against the body
64
+ before it checks byte length or digest, so a forgery that is byte-length
65
+ preserving and evidence-self-consistent is refused by the structural check
66
+ rather than incidentally by arithmetic. Recorded in `docs/source-trace.md` as
67
+ governance ledger L17.
68
+
69
+ ### Changed
70
+
71
+ - **`parseBuiltClaudeCodeRequest` no longer INFERS the canonical prefix from the
72
+ identity block's position; it READS the length from evidence and VERIFIES it
73
+ structurally.** With two independent seams an empty prefix is
74
+ indistinguishable from a caller-only array, so position inference is no longer
75
+ decidable: the rc.16 discriminator would have hit its unconditional failure
76
+ path on every request built with both seams active. The parser now takes
77
+ `evidence.billingBlockSuppressed` and `evidence.identityBlockSuppressed` as
78
+ the claimed prefix length and confirms every block that claim implies —
79
+ billing by its fixed `x-anthropic-billing-header: cc_version=` head (the tail
80
+ is per-request), identity by the byte-exact identity text. This is strictly
81
+ stronger than what it replaces, which never inspected the billing slot at all:
82
+ an envelope built with `suppressBillingBlock` whose evidence hid that fact was
83
+ previously accepted.
84
+
85
+ Verification is **asymmetric, deliberately**. A claim that identity was
86
+ suppressed is refuted by finding the identity text ANYWHERE in the array,
87
+ which is sound because `buildCanonicalSystem` drops caller blocks equal to it
88
+ unconditionally and merges runs with `\n`, so the text cannot legitimately
89
+ survive. There is no mirror check for billing: a caller block may legitimately
90
+ begin with the billing header text, so its presence proves nothing.
91
+
92
+ The match is on TEXT, never on `cache_control` — the L10 seam can legitimately
93
+ emit the identity block with no marker — and the assertion remains an
94
+ EQUALITY.
95
+
96
+ - **`emittedSystemBlockCount` is computed from both seams instead of a
97
+ constant.** `CANONICAL_SYSTEM_BLOCKS` and `CANONICAL_SYSTEM_BLOCKS_WITHOUT_BILLING`
98
+ were removed: a constant cannot express the empty prefix the two seams produce
99
+ together. The build path now subtracts one slot per canonical block that
100
+ actually survived.
101
+
102
+ ## [0.1.0-rc.16] - Unreleased
103
+
104
+ ### Added
105
+
106
+ - **`suppressBillingBlock` (seam S7).** The canonical billing block at `system[0]`
107
+ was composed unconditionally, so a consumer switch whose meaning is "do not
108
+ send the billing block" — `CLAUDE_CODE_ATTRIBUTION_HEADER=0` in
109
+ `opencode-anthropic-fix` — was a silent no-op. `suppressBillingBlock: true`
110
+ emits `[identity]` as the canonical prefix instead of `[billing, identity]`.
111
+ Only a boolean is accepted; anything else is `INVALID_INPUT`.
112
+ `evidence.billingBlockSuppressed` is emitted only when the block was actually
113
+ removed, so evidence stays byte-identical for every request that ignores the
114
+ seam. Suppressing the block changes what Anthropic sees for attribution
115
+ purposes: that is a deliberate consumer decision, and the package does not
116
+ guard it. Recorded in `docs/source-trace.md` as governance ledger L15.
117
+
118
+ ### Changed
119
+
120
+ - **`parseBuiltClaudeCodeRequest` infers the canonical system prefix
121
+ structurally.** The flag never reaches the wire, so the parser can no longer
122
+ subtract a constant. It locates the byte-exact identity text — index 1 means a
123
+ two-block prefix, index 0 means a one-block prefix, neither means the envelope
124
+ was not produced by this package — matching on TEXT and never on
125
+ `cache_control`, because `cacheControl.suppressIdentityBlock` can legitimately
126
+ emit the identity block with no cache marker. The assertion remains an
127
+ equality.
128
+
129
+ ### Fixed
130
+
131
+ - **`evidence.systemBlockCount` counted the caller's blocks, not the emitted
132
+ ones.** `buildCanonicalSystem` merges adjacent caller blocks that share a
133
+ `cache_control` and drops any block equal to the identity text, so the emitted
134
+ array was routinely shorter than the caller's while the parser asserted
135
+ equality against it. Any request with two or more mergeable system blocks was
136
+ rejected by `parseBuiltClaudeCodeRequest` with an opaque `INVALID_INPUT` and
137
+ `safeDetails: {}` — a 403 in production with no diagnosable cause, since the
138
+ parser's only consumer is a proxy validating envelopes from a Worker. Evidence
139
+ now records the length of the array actually serialized.
140
+
141
+ ## [0.1.0-rc.15] - 2026-07-28
6
142
 
7
143
  ### Fixed
8
144
 
package/dist/betas.d.ts CHANGED
@@ -12,6 +12,14 @@ export interface ComposeBetasInput {
12
12
  * `docs/source-trace.md`, governance ledger L10.
13
13
  */
14
14
  readonly additionalBetas?: readonly string[];
15
+ /**
16
+ * Package extension, not observed upstream behaviour. Beta identifiers
17
+ * removed from the emitted set AFTER composition and AFTER the
18
+ * `additionalBetas` merge, so suppression beats addition. An identifier that
19
+ * is not in the composed set is a silent no-op. See `docs/source-trace.md`,
20
+ * governance ledger L14.
21
+ */
22
+ readonly suppressBetas?: readonly string[];
15
23
  /**
16
24
  * Package extension, not observed upstream behaviour. Forces (`true`) or
17
25
  * suppresses (`false`) the 1M-context beta for this request, overriding the
@@ -19,5 +27,16 @@ export interface ComposeBetasInput {
19
27
  */
20
28
  readonly use1MContextOverride?: boolean;
21
29
  }
30
+ /**
31
+ * Reports the emitted beta set together with the identifiers `suppressBetas`
32
+ * actually removed. `suppressedBetaNames` is empty when the seam is unused or
33
+ * matched nothing, which is what keeps the evidence key absent for every
34
+ * request built before the seam existed.
35
+ */
36
+ export interface ComposedBetas {
37
+ readonly betas: readonly string[];
38
+ readonly suppressedBetaNames: readonly string[];
39
+ }
22
40
  export declare function composeBetas(input: ComposeBetasInput, profile?: ClaudeCodeProtocolProfile): readonly string[];
41
+ export declare function composeBetasWithAudit(input: ComposeBetasInput, profile?: ClaudeCodeProtocolProfile): ComposedBetas;
23
42
  //# sourceMappingURL=betas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"betas.d.ts","sourceRoot":"","sources":["../src/betas.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,gBAAgB,CAAC;AAmBxB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,sBAAsB,CAAC;IAC9C,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CACzC;AA+BD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE,yBAAuD,GAC/D,SAAS,MAAM,EAAE,CAyFnB"}
1
+ {"version":3,"file":"betas.d.ts","sourceRoot":"","sources":["../src/betas.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,gBAAgB,CAAC;AAmBxB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,sBAAsB,CAAC;IAC9C,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CACzC;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CACjD;AAkCD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE,yBAAuD,GAC/D,SAAS,MAAM,EAAE,CAEnB;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE,yBAAuD,GAC/D,aAAa,CA+Gf"}
package/dist/betas.js CHANGED
@@ -4,7 +4,8 @@ import { ClaudeCodeWireError } from "./contracts.js";
4
4
  import { supportsMidConversationSystem, supportsStructuredOutputs, } from "./model-capabilities.js";
5
5
  import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
6
6
  /**
7
- * Bounds the caller-supplied beta list. The header is a comma-joined single
7
+ * Bounds a caller-supplied beta list (`additionalBetas` and `suppressBetas`
8
+ * share these rules verbatim). The header is a comma-joined single
8
9
  * field, so a comma, control character, or whitespace in an entry would let a
9
10
  * caller synthesize extra beta values (or, with CR/LF, an entirely separate
10
11
  * header). The allowlist below is deliberately narrower than the observed
@@ -28,7 +29,11 @@ function validateAdditionalBetas(value) {
28
29
  return entry;
29
30
  });
30
31
  }
32
+ const NO_SUPPRESSED_BETAS = Object.freeze([]);
31
33
  export function composeBetas(input, profile = CLAUDE_CODE_2_1_195_PROFILE) {
34
+ return composeBetasWithAudit(input, profile).betas;
35
+ }
36
+ export function composeBetasWithAudit(input, profile = CLAUDE_CODE_2_1_195_PROFILE) {
32
37
  const out = [];
33
38
  const policy = profile.betaPolicy;
34
39
  const experimental = policy.experimentalBetasEnabled;
@@ -102,6 +107,30 @@ export function composeBetas(input, profile = CLAUDE_CODE_2_1_195_PROFILE) {
102
107
  out.push(beta);
103
108
  }
104
109
  }
105
- return Object.freeze(out);
110
+ // Package extension. The suppression filter is deliberately LAST: it runs
111
+ // after the canonical composition and after the `additionalBetas` merge, so
112
+ // an identifier named by both seams does not reach the wire. Removal is
113
+ // reported in composed order, never in caller order, and an identifier that
114
+ // was never composed is a silent no-op because a consumer cannot know which
115
+ // betas this package derives for a given model.
116
+ if (input.suppressBetas === undefined) {
117
+ return Object.freeze({
118
+ betas: Object.freeze(out),
119
+ suppressedBetaNames: NO_SUPPRESSED_BETAS,
120
+ });
121
+ }
122
+ const suppressed = new Set(validateAdditionalBetas(input.suppressBetas));
123
+ const kept = [];
124
+ const removed = [];
125
+ for (const beta of out) {
126
+ if (suppressed.has(beta))
127
+ removed.push(beta);
128
+ else
129
+ kept.push(beta);
130
+ }
131
+ return Object.freeze({
132
+ betas: Object.freeze(kept),
133
+ suppressedBetaNames: Object.freeze(removed),
134
+ });
106
135
  }
107
136
  //# sourceMappingURL=betas.js.map
package/dist/betas.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"betas.js","sourceRoot":"","sources":["../src/betas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"betas.js","sourceRoot":"","sources":["../src/betas.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"build-request.d.ts","sourceRoot":"","sources":["../src/build-request.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,iCAAiC,EACjC,sBAAsB,EAMtB,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EAGvB,MAAM,gBAAgB,CAAC;AA6iCxB,2DAA2D;AAC3D,wBAAsB,iCAAiC,CACrD,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAAC,iCAAiC,CAAC,CAiF5C;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,sBAAsB,EAC7B,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAAC,sBAAsB,CAAC,CAqHjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,yBAAuD,GAC/D,sBAAsB,CA+ExB"}
1
+ {"version":3,"file":"build-request.d.ts","sourceRoot":"","sources":["../src/build-request.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,iCAAiC,EACjC,sBAAsB,EAMtB,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EAGvB,MAAM,gBAAgB,CAAC;AAwvCxB,2DAA2D;AAC3D,wBAAsB,iCAAiC,CACrD,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAAC,iCAAiC,CAAC,CAiF5C;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,sBAAsB,EAC7B,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAAC,sBAAsB,CAAC,CA8JjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,yBAAuD,GAC/D,sBAAsB,CA+FxB"}
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-or-later
2
- import { composeBetas } from "./betas.js";
2
+ import { composeBetas, composeBetasWithAudit } from "./betas.js";
3
3
  import { ClaudeCodeWireError } from "./contracts.js";
4
4
  import { buildCountTokensBody, filterCountTokensBetas, TOKEN_COUNTING_BETA, } from "./count-tokens.js";
5
5
  import { createBillingBlock } from "./fingerprint.js";
@@ -10,7 +10,7 @@ import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
10
10
  import { buildRedactedEvidence, toSafeErrorDetails } from "./redaction.js";
11
11
  import { buildCanonicalBody, canonicalCountTokensLists, } from "./request-body.js";
12
12
  import { sha256Hex } from "./sha256.js";
13
- import { buildCanonicalSystem } from "./system-prompt.js";
13
+ import { buildCanonicalSystem, IDENTITY_TEXT } from "./system-prompt.js";
14
14
  import { isThinkingDisplayActive } from "./thinking.js";
15
15
  import { classifySurrogateAt } from "./unicode.js";
16
16
  const METHOD = "POST";
@@ -52,6 +52,10 @@ const INPUT_KEYS = new Set([
52
52
  "clientApp",
53
53
  "anthropicAdditionalProtection",
54
54
  "additionalBetas",
55
+ "suppressBetas",
56
+ "suppressBillingBlock",
57
+ "suppressIdentityBlock",
58
+ "preserveThinkingBlockCacheControl",
55
59
  "betaOverrides",
56
60
  "metadataOverrides",
57
61
  "extraHeaders",
@@ -79,6 +83,16 @@ const COUNT_TOKENS_INPUT_KEYS = new Set([
79
83
  "extraHeaders",
80
84
  ]);
81
85
  const BUILT_KEYS = new Set(["url", "method", "headers", "body", "evidence"]);
86
+ /**
87
+ * The self-describing prefix of the canonical billing block's text.
88
+ *
89
+ * `buildBillingBlock` emits
90
+ * `x-anthropic-billing-header: cc_version=<version>.<fingerprint>; ...`, whose
91
+ * tail varies per request, so only this fixed head can anchor a structural
92
+ * check. It is what lets the parser CONFIRM that a request which claims to
93
+ * carry the billing block actually carries it.
94
+ */
95
+ const BILLING_BLOCK_TEXT_PREFIX = "x-anthropic-billing-header: cc_version=";
82
96
  const EVIDENCE_KEYS = new Set([
83
97
  "profileId",
84
98
  "url",
@@ -92,6 +106,10 @@ const EVIDENCE_KEYS = new Set([
92
106
  "systemBlockCount",
93
107
  "capabilityDecisions",
94
108
  "droppedExtraHeaderNames",
109
+ "suppressedBetaNames",
110
+ "billingBlockSuppressed",
111
+ "identityBlockSuppressed",
112
+ "thinkingBlockCacheControlPreserved",
95
113
  ]);
96
114
  const CAPABILITY_KEYS = [
97
115
  "thinking",
@@ -493,6 +511,36 @@ function validateExtraHeaderPolicy(value) {
493
511
  fail();
494
512
  return value;
495
513
  }
514
+ /**
515
+ * Validates the package-extension billing-block suppression flag.
516
+ *
517
+ * Only a boolean states a decision. A truthy string or `0` is a caller mistake
518
+ * rather than a coercion target, because coercing would silently change what
519
+ * Anthropic receives for attribution purposes.
520
+ */
521
+ function validateSuppressBillingBlock(value) {
522
+ return parseBoolean(value);
523
+ }
524
+ /**
525
+ * Validates the package-extension identity-block suppression flag.
526
+ *
527
+ * Same contract as `validateSuppressBillingBlock`: only a boolean states a
528
+ * decision, because coercing a truthy string would silently drop a canonical
529
+ * block the genuine client always sends.
530
+ */
531
+ function validateSuppressIdentityBlock(value) {
532
+ return parseBoolean(value);
533
+ }
534
+ /**
535
+ * Validates the package-extension thinking-block `cache_control` seam flag.
536
+ *
537
+ * Same contract as the suppression flags: only a boolean states a decision,
538
+ * because coercing a truthy string would silently widen the thinking-block
539
+ * allowlist this package pins against the genuine client.
540
+ */
541
+ function validatePreserveThinkingBlockCacheControl(value) {
542
+ return parseBoolean(value);
543
+ }
496
544
  function createEffectiveProfile(pinnedProfile, override) {
497
545
  if (override === undefined)
498
546
  return pinnedProfile;
@@ -552,6 +600,15 @@ function validateInput(input) {
552
600
  const extraHeaderPolicy = Object.hasOwn(input, "extraHeaderPolicy")
553
601
  ? validateExtraHeaderPolicy(ownValue(input, "extraHeaderPolicy"))
554
602
  : undefined;
603
+ const suppressBillingBlock = Object.hasOwn(input, "suppressBillingBlock")
604
+ ? validateSuppressBillingBlock(ownValue(input, "suppressBillingBlock"))
605
+ : false;
606
+ const suppressIdentityBlock = Object.hasOwn(input, "suppressIdentityBlock")
607
+ ? validateSuppressIdentityBlock(ownValue(input, "suppressIdentityBlock"))
608
+ : false;
609
+ const preserveThinkingBlockCacheControl = Object.hasOwn(input, "preserveThinkingBlockCacheControl")
610
+ ? validatePreserveThinkingBlockCacheControl(ownValue(input, "preserveThinkingBlockCacheControl"))
611
+ : false;
555
612
  return {
556
613
  source: input,
557
614
  clientRequestId,
@@ -559,6 +616,9 @@ function validateInput(input) {
559
616
  profileOverride,
560
617
  betaOverrides,
561
618
  extraHeaderPolicy,
619
+ suppressBillingBlock,
620
+ suppressIdentityBlock,
621
+ preserveThinkingBlockCacheControl,
562
622
  };
563
623
  }
564
624
  function validateCountTokensInput(input) {
@@ -786,8 +846,113 @@ function parseEvidence(value) {
786
846
  droppedExtraHeaderNames: parseStringArray(ownValue(value, "droppedExtraHeaderNames")),
787
847
  }
788
848
  : {}),
849
+ ...(Object.hasOwn(value, "suppressedBetaNames")
850
+ ? {
851
+ suppressedBetaNames: parseStringArray(ownValue(value, "suppressedBetaNames")),
852
+ }
853
+ : {}),
854
+ ...(Object.hasOwn(value, "billingBlockSuppressed")
855
+ ? {
856
+ billingBlockSuppressed: parseBoolean(ownValue(value, "billingBlockSuppressed")),
857
+ }
858
+ : {}),
859
+ ...(Object.hasOwn(value, "identityBlockSuppressed")
860
+ ? {
861
+ identityBlockSuppressed: parseBoolean(ownValue(value, "identityBlockSuppressed")),
862
+ }
863
+ : {}),
864
+ // Rehydrated here because `assertExactKeys` above already accepts the key:
865
+ // omitting this branch would silently DROP it, and the round-trip equality
866
+ // every seam test asserts would fail on an envelope that is entirely legal.
867
+ ...(Object.hasOwn(value, "thinkingBlockCacheControlPreserved")
868
+ ? {
869
+ thinkingBlockCacheControlPreserved: parseBoolean(ownValue(value, "thinkingBlockCacheControlPreserved")),
870
+ }
871
+ : {}),
789
872
  };
790
873
  }
874
+ /** Reads an own property of a value that is not asserted to be a record. */
875
+ function ownProperty(value, key) {
876
+ return isRecord(value) && Object.hasOwn(value, key)
877
+ ? ownValue(value, key)
878
+ : undefined;
879
+ }
880
+ /**
881
+ * Reports whether the emitted `messages` carry a reasoning block that actually
882
+ * kept a `cache_control` key.
883
+ *
884
+ * This is what makes `evidence.thinkingBlockCacheControlPreserved` a record of
885
+ * what the seam DID rather than of what it was allowed to do, and it is the
886
+ * check that refutes an envelope claiming the seam over a body carrying no such
887
+ * block. Presence of the KEY is the test, not truthiness: `cache_control: null`
888
+ * is a preserved marker too, exactly as it is on every other block type.
889
+ */
890
+ function hasThinkingBlockCacheControl(messages) {
891
+ if (!Array.isArray(messages))
892
+ return false;
893
+ return messages.some((message) => {
894
+ const content = ownProperty(message, "content");
895
+ return (Array.isArray(content) &&
896
+ content.some((block) => {
897
+ const type = ownProperty(block, "type");
898
+ return ((type === "thinking" || type === "redacted_thinking") &&
899
+ ownProperty(block, "cache_control") !== undefined);
900
+ }));
901
+ });
902
+ }
903
+ /** Reads a system block's `text` without asserting the block's shape. */
904
+ function systemBlockText(block) {
905
+ return ownProperty(block, "text");
906
+ }
907
+ /** Recognises the canonical billing block by its fixed, self-describing head. */
908
+ function isBillingBlockText(text) {
909
+ return typeof text === "string" && text.startsWith(BILLING_BLOCK_TEXT_PREFIX);
910
+ }
911
+ /**
912
+ * VERIFIES how many canonical blocks the emitted `system` array carries.
913
+ *
914
+ * The root seams `suppressBillingBlock` and `suppressIdentityBlock` make four
915
+ * prefixes legitimate — `[billing, identity]`, `[identity]`, `[billing]` and
916
+ * `[]` — so no probe over the array alone can tell them apart: an empty prefix
917
+ * is indistinguishable from a caller-only array. The prefix length is therefore
918
+ * READ from the evidence flags, which the builder emits only when suppression
919
+ * actually removed a block.
920
+ *
921
+ * Evidence is not trusted blindly. Each block the flags claim is present is
922
+ * confirmed in place: billing by its `x-anthropic-billing-header: cc_version=`
923
+ * head (its tail is per-request), identity by the byte-exact `IDENTITY_TEXT`;
924
+ * a claim that identity was suppressed is confirmed by that text being absent
925
+ * from the whole array. This is strictly stronger than the position probe it
926
+ * replaces, which never checked the billing slot at all. Never `cache_control`:
927
+ * the
928
+ * `cacheControl.suppressIdentityBlock` seam can emit the identity block with no
929
+ * marker, so a marker probe would misread a legitimate request.
930
+ */
931
+ function canonicalSystemPrefixLength(system, billingSuppressed, identitySuppressed) {
932
+ let index = 0;
933
+ if (!billingSuppressed) {
934
+ if (!isBillingBlockText(systemBlockText(system[index])))
935
+ fail();
936
+ index += 1;
937
+ }
938
+ if (identitySuppressed) {
939
+ // The identity text cannot appear ANYWHERE in a body built with the seam
940
+ // active: `buildCanonicalSystem` drops a caller block equal to it
941
+ // unconditionally, and merging joins with `\n`, so no merged run can equal
942
+ // it either. Absence is therefore checkable, which is what refutes a claim
943
+ // of suppression made over a body that still carries the block.
944
+ //
945
+ // No mirror check exists for billing: a caller block may legitimately begin
946
+ // with the billing header text, so its presence proves nothing.
947
+ if (system.some((block) => systemBlockText(block) === IDENTITY_TEXT)) {
948
+ fail();
949
+ }
950
+ return index;
951
+ }
952
+ if (systemBlockText(system[index]) !== IDENTITY_TEXT)
953
+ fail();
954
+ return index + 1;
955
+ }
791
956
  function parseBody(value) {
792
957
  let parsed;
793
958
  try {
@@ -858,6 +1023,12 @@ function evidenceRequest(input, callerModel) {
858
1023
  request.capabilities = input.capabilities;
859
1024
  if (input.betaOverrides !== undefined)
860
1025
  request.betaOverrides = input.betaOverrides;
1026
+ // The body builder owns the thinking-block allowlist, so the seam flag has to
1027
+ // reach it. It is forwarded only when the caller stated it, keeping the
1028
+ // normalized request shape identical for every request that ignores the seam.
1029
+ if (input.preserveThinkingBlockCacheControl !== undefined)
1030
+ request.preserveThinkingBlockCacheControl =
1031
+ input.preserveThinkingBlockCacheControl;
861
1032
  if (input.thinking !== undefined)
862
1033
  request.thinking = input.thinking;
863
1034
  if (input.effort !== undefined)
@@ -978,9 +1149,9 @@ export async function buildClaudeCodeRequest(input, profile = CLAUDE_CODE_2_1_19
978
1149
  });
979
1150
  const billing = await createBillingBlock(fingerprintText(validated.source), effectiveProfile.cliVersion, validated.crypto);
980
1151
  const metadata = buildCorrelatedMetadata(identity, validated.source.metadata, validated.source.metadataOverrides);
981
- const system = buildCanonicalSystem(validated.source.system, billing, identity);
1152
+ const system = buildCanonicalSystem(validated.source.system, billing, identity, validated.suppressBillingBlock, validated.suppressIdentityBlock);
982
1153
  const canonicalBody = buildCanonicalBody(evidenceRequest(validated.source, validated.source.model), effectiveModel, system, metadata, effectiveProfile);
983
- const betas = composeBetas({
1154
+ const composedBetas = composeBetasWithAudit({
984
1155
  rawModel: validated.source.model,
985
1156
  normalizedId: resolvedModel.id,
986
1157
  capabilities,
@@ -994,10 +1165,14 @@ export async function buildClaudeCodeRequest(input, profile = CLAUDE_CODE_2_1_19
994
1165
  ...(validated.source.additionalBetas === undefined
995
1166
  ? {}
996
1167
  : { additionalBetas: validated.source.additionalBetas }),
1168
+ ...(validated.source.suppressBetas === undefined
1169
+ ? {}
1170
+ : { suppressBetas: validated.source.suppressBetas }),
997
1171
  ...(validated.betaOverrides?.use1MContext === undefined
998
1172
  ? {}
999
1173
  : { use1MContextOverride: validated.betaOverrides.use1MContext }),
1000
1174
  }, effectiveProfile);
1175
+ const betas = composedBetas.betas;
1001
1176
  const headerPlan = buildOrderedHeaderPlan({
1002
1177
  accessToken: validated.source.accessToken,
1003
1178
  runtime: identity,
@@ -1024,11 +1199,45 @@ export async function buildClaudeCodeRequest(input, profile = CLAUDE_CODE_2_1_19
1024
1199
  logicalHeaders: headers,
1025
1200
  betaFeatures: betas,
1026
1201
  body,
1202
+ // The canonical system merges adjacent caller blocks and drops any
1203
+ // block equal to the identity text, so only the emitted count keeps
1204
+ // `systemBlockCount === body.system.length - <canonical>` true.
1205
+ // Symmetric arithmetic over the two suppression seams: each canonical
1206
+ // block that survived costs one slot. A single constant cannot express
1207
+ // the empty prefix both seams together produce.
1208
+ emittedSystemBlockCount: system.length -
1209
+ (validated.suppressBillingBlock ? 0 : 1) -
1210
+ (validated.suppressIdentityBlock ? 0 : 1),
1027
1211
  // Emitted only for the opted-in policy, so evidence for every other
1028
1212
  // request keeps the shape it had before the seam existed.
1029
1213
  ...(validated.extraHeaderPolicy === "dropConflicting"
1030
1214
  ? { droppedExtraHeaderNames: headerPlan.droppedExtraHeaderNames }
1031
1215
  : {}),
1216
+ // Emitted only when suppression actually removed something, so
1217
+ // evidence for every request that ignores the seam is unchanged.
1218
+ ...(composedBetas.suppressedBetaNames.length === 0
1219
+ ? {}
1220
+ : { suppressedBetaNames: composedBetas.suppressedBetaNames }),
1221
+ // Emitted only when the billing block was actually removed, so the
1222
+ // audit records the attribution decision without inventing a key for
1223
+ // every request that leaves the canonical prefix intact.
1224
+ ...(validated.suppressBillingBlock
1225
+ ? { billingBlockSuppressed: true }
1226
+ : {}),
1227
+ // Emitted only when the identity block was actually removed, on the
1228
+ // same terms as `billingBlockSuppressed`: the parser reads both flags
1229
+ // to know the length of the canonical prefix it must verify.
1230
+ ...(validated.suppressIdentityBlock
1231
+ ? { identityBlockSuppressed: true }
1232
+ : {}),
1233
+ // Emitted only when the seam was active AND a reasoning block actually
1234
+ // carried the marker. Opting in without using it records nothing, so
1235
+ // the audit states what happened on the wire rather than what the
1236
+ // caller was permitted to do.
1237
+ ...(validated.preserveThinkingBlockCacheControl &&
1238
+ hasThinkingBlockCacheControl(canonicalBody["messages"])
1239
+ ? { thinkingBlockCacheControlPreserved: true }
1240
+ : {}),
1032
1241
  }, validated.crypto);
1033
1242
  return deepFreeze({
1034
1243
  url: effectiveProfile.endpoint,
@@ -1068,6 +1277,15 @@ export function parseBuiltClaudeCodeRequest(value, profile = CLAUDE_CODE_2_1_195
1068
1277
  const parsedBody = parseBody(body);
1069
1278
  const headers = parseHeaders(ownValue(value, "headers"));
1070
1279
  const evidence = parseEvidence(ownValue(value, "evidence"));
1280
+ // Reading evidence is not trusting evidence. A claim that the seam
1281
+ // preserved a marker is confirmed against the body, and it is confirmed
1282
+ // HERE — before the byte-length and digest checks — so that a forgery which
1283
+ // is byte-length preserving and evidence-self-consistent is refused by the
1284
+ // structural check rather than incidentally by arithmetic.
1285
+ if (evidence.thinkingBlockCacheControlPreserved === true &&
1286
+ !hasThinkingBlockCacheControl(parsedBody["messages"])) {
1287
+ fail();
1288
+ }
1071
1289
  const sessionId = headerValue(headers, "x-claude-code-session-id");
1072
1290
  const additionalHeaders = splitDynamicAndExtraHeaders(headers);
1073
1291
  const expectedHeaders = buildOrderedHeaders({
@@ -1105,7 +1323,8 @@ export function parseBuiltClaudeCodeRequest(value, profile = CLAUDE_CODE_2_1_195
1105
1323
  : -1) ||
1106
1324
  evidence.systemBlockCount !==
1107
1325
  (Array.isArray(parsedBody["system"])
1108
- ? parsedBody["system"].length - 2
1326
+ ? parsedBody["system"].length -
1327
+ canonicalSystemPrefixLength(parsedBody["system"], evidence.billingBlockSuppressed === true, evidence.identityBlockSuppressed === true)
1109
1328
  : -1)) {
1110
1329
  fail();
1111
1330
  }