@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 +137 -1
- package/dist/betas.d.ts +19 -0
- package/dist/betas.d.ts.map +1 -1
- package/dist/betas.js +31 -2
- package/dist/betas.js.map +1 -1
- package/dist/build-request.d.ts.map +1 -1
- package/dist/build-request.js +224 -5
- package/dist/build-request.js.map +1 -1
- package/dist/contracts.d.ts +160 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/redaction.d.ts +27 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +36 -4
- package/dist/redaction.js.map +1 -1
- package/dist/request-body.d.ts.map +1 -1
- package/dist/request-body.js +81 -21
- package/dist/request-body.js.map +1 -1
- package/dist/system-prompt.d.ts +17 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +32 -7
- package/dist/system-prompt.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
package/dist/betas.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
-
|
|
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":"AAAA,4CAA4C;AAE5C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAKnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EACL,6BAA6B,EAC7B,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;
|
|
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;
|
|
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"}
|
package/dist/build-request.js
CHANGED
|
@@ -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
|
|
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 -
|
|
1326
|
+
? parsedBody["system"].length -
|
|
1327
|
+
canonicalSystemPrefixLength(parsedBody["system"], evidence.billingBlockSuppressed === true, evidence.identityBlockSuppressed === true)
|
|
1109
1328
|
: -1)) {
|
|
1110
1329
|
fail();
|
|
1111
1330
|
}
|