@tormentalabs/claude-code-wire-compat 0.1.0-rc.17 → 0.2.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +101 -2
  2. package/README.md +67 -2
  3. package/dist/betas.d.ts +37 -0
  4. package/dist/betas.d.ts.map +1 -1
  5. package/dist/betas.js +55 -23
  6. package/dist/betas.js.map +1 -1
  7. package/dist/build-request.d.ts +11 -0
  8. package/dist/build-request.d.ts.map +1 -1
  9. package/dist/build-request.js +133 -11
  10. package/dist/build-request.js.map +1 -1
  11. package/dist/contracts.d.ts +53 -0
  12. package/dist/contracts.d.ts.map +1 -1
  13. package/dist/contracts.js.map +1 -1
  14. package/dist/fingerprint.d.ts +29 -2
  15. package/dist/fingerprint.d.ts.map +1 -1
  16. package/dist/fingerprint.js +60 -7
  17. package/dist/fingerprint.js.map +1 -1
  18. package/dist/headers.d.ts.map +1 -1
  19. package/dist/headers.js +12 -4
  20. package/dist/headers.js.map +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/model-capabilities.d.ts +31 -3
  26. package/dist/model-capabilities.d.ts.map +1 -1
  27. package/dist/model-capabilities.js +145 -12
  28. package/dist/model-capabilities.js.map +1 -1
  29. package/dist/models.d.ts.map +1 -1
  30. package/dist/models.js +8 -4
  31. package/dist/models.js.map +1 -1
  32. package/dist/profile-behaviors.d.ts +61 -0
  33. package/dist/profile-behaviors.d.ts.map +1 -0
  34. package/dist/profile-behaviors.js +53 -0
  35. package/dist/profile-behaviors.js.map +1 -0
  36. package/dist/profiles/beta-registry-2.1.233.d.ts +140 -0
  37. package/dist/profiles/beta-registry-2.1.233.d.ts.map +1 -0
  38. package/dist/profiles/beta-registry-2.1.233.js +183 -0
  39. package/dist/profiles/beta-registry-2.1.233.js.map +1 -0
  40. package/dist/profiles/claude-code-2.1.195.d.ts.map +1 -1
  41. package/dist/profiles/claude-code-2.1.195.js +14 -0
  42. package/dist/profiles/claude-code-2.1.195.js.map +1 -1
  43. package/dist/profiles/claude-code-2.1.233.d.ts +3 -0
  44. package/dist/profiles/claude-code-2.1.233.d.ts.map +1 -0
  45. package/dist/profiles/claude-code-2.1.233.js +235 -0
  46. package/dist/profiles/claude-code-2.1.233.js.map +1 -0
  47. package/dist/redaction.d.ts.map +1 -1
  48. package/dist/redaction.js +14 -1
  49. package/dist/redaction.js.map +1 -1
  50. package/dist/request-body.d.ts.map +1 -1
  51. package/dist/request-body.js +12 -10
  52. package/dist/request-body.js.map +1 -1
  53. package/dist/thinking.d.ts +33 -7
  54. package/dist/thinking.d.ts.map +1 -1
  55. package/dist/thinking.js +105 -36
  56. package/dist/thinking.js.map +1 -1
  57. package/package.json +10 -2
  58. package/src/anti-verbosity.ts +219 -0
  59. package/src/beta-registry.ts +140 -0
  60. package/src/betas.ts +302 -0
  61. package/src/build-request.ts +1799 -0
  62. package/src/contracts.ts +1286 -0
  63. package/src/count-tokens.ts +84 -0
  64. package/src/fingerprint.ts +155 -0
  65. package/src/headers.ts +448 -0
  66. package/src/index.ts +63 -0
  67. package/src/metadata.ts +331 -0
  68. package/src/model-capabilities.ts +453 -0
  69. package/src/model-identity.ts +45 -0
  70. package/src/models.ts +50 -0
  71. package/src/profile-behaviors.ts +114 -0
  72. package/src/profiles/beta-registry-2.1.233.ts +200 -0
  73. package/src/profiles/claude-code-2.1.195.ts +168 -0
  74. package/src/profiles/claude-code-2.1.233.ts +240 -0
  75. package/src/redaction.ts +536 -0
  76. package/src/request-body.ts +1931 -0
  77. package/src/sha256.ts +114 -0
  78. package/src/system-prompt.ts +222 -0
  79. package/src/thinking.ts +346 -0
  80. package/src/unicode.ts +24 -0
@@ -0,0 +1,1799 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+ import { composeBetas, composeBetasWithAudit } from "./betas.js";
4
+ import type {
5
+ BuiltClaudeCodeCountTokensRequest,
6
+ BuiltClaudeCodeRequest,
7
+ ClaudeCodeBetaOverrides,
8
+ ClaudeCodeCapabilities,
9
+ ClaudeCodeCapabilityDecisions,
10
+ ClaudeCodeExtraHeaderPolicy,
11
+ ClaudeCodeProfileOverride,
12
+ ClaudeCodeProtocolProfile,
13
+ ClaudeCodeCountTokensInput,
14
+ ClaudeCodeRequestInput,
15
+ HeaderPair,
16
+ RedactedRequestEvidence,
17
+ } from "./contracts.js";
18
+ import { ClaudeCodeWireError } from "./contracts.js";
19
+ import {
20
+ buildCountTokensBody,
21
+ filterCountTokensBetas,
22
+ TOKEN_COUNTING_BETA,
23
+ } from "./count-tokens.js";
24
+ import { createBillingBlock } from "./fingerprint.js";
25
+ import { buildOrderedHeaderPlan, buildOrderedHeaders } from "./headers.js";
26
+ import {
27
+ buildCorrelatedMetadata,
28
+ validateRuntimeIdentity,
29
+ } from "./metadata.js";
30
+ import { resolveModel } from "./models.js";
31
+ import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
32
+ import { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
33
+ import type { NormalizedRequestInput } from "./redaction.js";
34
+ import { buildRedactedEvidence, toSafeErrorDetails } from "./redaction.js";
35
+ import {
36
+ buildCanonicalBody,
37
+ canonicalCountTokensLists,
38
+ } from "./request-body.js";
39
+ import { sha256Hex } from "./sha256.js";
40
+ import { buildCanonicalSystem, IDENTITY_TEXT } from "./system-prompt.js";
41
+ import { isThinkingDisplayActive } from "./thinking.js";
42
+ import { classifySurrogateAt } from "./unicode.js";
43
+
44
+ const METHOD = "POST";
45
+ const MAX_INPUT_DEPTH = 100;
46
+ const MAX_INPUT_SIZE = 1_000_000;
47
+ const FORBIDDEN_KEYS = new Set(["__proto__", "prototype", "constructor"]);
48
+ const INPUT_KEYS = new Set([
49
+ "accessToken",
50
+ "model",
51
+ "maxTokens",
52
+ "messages",
53
+ "system",
54
+ "tools",
55
+ "cacheControl",
56
+ "runtime",
57
+ "capabilities",
58
+ "profileOverride",
59
+ "thinking",
60
+ "effort",
61
+ "metadata",
62
+ "experimentalBodyFields",
63
+ "contextManagement",
64
+ "outputConfig",
65
+ "speed",
66
+ "serviceTier",
67
+ "outputFormat",
68
+ "toolChoice",
69
+ "topP",
70
+ "topK",
71
+ "stopSequences",
72
+ "stream",
73
+ "temperature",
74
+ "clientRequestId",
75
+ "app",
76
+ "stainlessRetryCount",
77
+ "stainlessHelper",
78
+ "claudeRemoteContainerId",
79
+ "claudeRemoteSessionId",
80
+ "clientApp",
81
+ "anthropicAdditionalProtection",
82
+ "additionalBetas",
83
+ "suppressBetas",
84
+ "suppressBillingBlock",
85
+ "suppressIdentityBlock",
86
+ "preserveThinkingBlockCacheControl",
87
+ "betaOverrides",
88
+ "metadataOverrides",
89
+ "extraHeaders",
90
+ "extraHeaderPolicy",
91
+ "previousRequestId",
92
+ "promptId",
93
+ "crypto",
94
+ ]);
95
+ const BETA_OVERRIDE_KEYS = new Set(["use1MContext"]);
96
+ const METADATA_OVERRIDE_KEYS = new Set(["userId", "userIdFields"]);
97
+ const COUNT_TOKENS_INPUT_KEYS = new Set([
98
+ "accessToken",
99
+ "model",
100
+ "messages",
101
+ "tools",
102
+ "runtime",
103
+ "clientRequestId",
104
+ "profileOverride",
105
+ "crypto",
106
+ "app",
107
+ "stainlessRetryCount",
108
+ "stainlessHelper",
109
+ "claudeRemoteContainerId",
110
+ "claudeRemoteSessionId",
111
+ "clientApp",
112
+ "anthropicAdditionalProtection",
113
+ "extraHeaders",
114
+ ]);
115
+ const BUILT_KEYS = new Set(["url", "method", "headers", "body", "evidence"]);
116
+ /**
117
+ * The self-describing prefix of the canonical billing block's text.
118
+ *
119
+ * `buildBillingBlock` emits
120
+ * `x-anthropic-billing-header: cc_version=<version>.<fingerprint>; ...`, whose
121
+ * tail varies per request, so only this fixed head can anchor a structural
122
+ * check. It is what lets the parser CONFIRM that a request which claims to
123
+ * carry the billing block actually carries it.
124
+ */
125
+ const BILLING_BLOCK_TEXT_PREFIX = "x-anthropic-billing-header: cc_version=";
126
+ const EVIDENCE_KEYS = new Set([
127
+ "profileId",
128
+ "url",
129
+ "method",
130
+ "modelFamily",
131
+ "logicalHeaderNames",
132
+ "betaFeatures",
133
+ "bodySha256",
134
+ "bodyByteLength",
135
+ "messageCount",
136
+ "systemBlockCount",
137
+ "capabilityDecisions",
138
+ "droppedExtraHeaderNames",
139
+ "suppressedBetaNames",
140
+ "billingBlockSuppressed",
141
+ "identityBlockSuppressed",
142
+ "thinkingBlockCacheControlPreserved",
143
+ ]);
144
+ const CAPABILITY_KEYS = [
145
+ "thinking",
146
+ "adaptiveThinking",
147
+ "interleavedThinking",
148
+ "effort",
149
+ "maxEffort",
150
+ "xhighEffort",
151
+ "contextManagement",
152
+ "temperature",
153
+ "rejectsDisabledThinking",
154
+ ] as const;
155
+ const CAPABILITY_KEY_SET = new Set(CAPABILITY_KEYS);
156
+ /** Adds the optional package-extension override keys carried by evidence. */
157
+ const CAPABILITY_DECISION_KEY_SET = new Set([
158
+ ...CAPABILITY_KEYS,
159
+ "use1MContext",
160
+ ]);
161
+ const OVERRIDE_KEYS = new Set([
162
+ "id",
163
+ "cliVersion",
164
+ "sdkVersion",
165
+ "entrypoint",
166
+ "userAgent",
167
+ "buildTime",
168
+ "gitSha",
169
+ "attributionHeaderEnabled",
170
+ "contextHintEnabled",
171
+ "betaPolicy",
172
+ "supportedModels",
173
+ ]);
174
+ const MODEL_KEYS = new Set([
175
+ "family",
176
+ "context",
177
+ "capabilities",
178
+ "maxOutputTokens",
179
+ "defaultEffort",
180
+ ]);
181
+ const BETA_POLICY_KEYS = new Set([
182
+ "oauthAuthenticated",
183
+ "experimentalBetasEnabled",
184
+ "oneMillionContextEnabled",
185
+ "interleavedThinkingEnabled",
186
+ "interactive",
187
+ "thinkingSummariesShown",
188
+ "thinkingTokenCountEnabled",
189
+ "narrationSummariesEnabled",
190
+ "structuredOutputsEnabled",
191
+ "afkModeEnabled",
192
+ "cacheDiagnosisEnabled",
193
+ ]);
194
+
195
+ type UnknownRecord = Readonly<Record<string, unknown>>;
196
+
197
+ function fail(
198
+ code: ConstructorParameters<typeof ClaudeCodeWireError>[0] = "INVALID_INPUT",
199
+ ): never {
200
+ throw new ClaudeCodeWireError(code);
201
+ }
202
+
203
+ function isRecord(value: unknown): value is UnknownRecord {
204
+ return typeof value === "object" && value !== null && !Array.isArray(value);
205
+ }
206
+
207
+ function ownValue(value: object, key: string): unknown {
208
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
209
+ if (descriptor === undefined || !("value" in descriptor)) fail();
210
+ return descriptor.value;
211
+ }
212
+
213
+ function present<T>(value: T | undefined): T {
214
+ if (value === undefined) fail();
215
+ return value;
216
+ }
217
+
218
+ function assertExactKeys(
219
+ value: Readonly<Record<string, unknown>>,
220
+ allowed: ReadonlySet<string>,
221
+ ): void {
222
+ for (const key of Reflect.ownKeys(value)) {
223
+ if (typeof key !== "string" || !allowed.has(key)) fail("INVALID_INPUT");
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Screens one string from the caller's input graph.
229
+ *
230
+ * TAB (0x09), LF (0x0A) and CR (0x0D) are ALLOWED. This function walks the
231
+ * whole input graph, which is overwhelmingly BODY content — message text,
232
+ * system blocks, tool descriptions — where a line break is ordinary prose that
233
+ * `JSON.stringify` escapes on the way out. Rejecting them here made the package
234
+ * unusable for real traffic: no genuine prompt is a single line.
235
+ *
236
+ * The strict rule those three characters used to be caught by is a HEADER rule,
237
+ * and it still lives where it belongs and still applies in full:
238
+ * `assertHeaderText` in `src/headers.ts` rejects every control character,
239
+ * including these three, because a bare LF in a header is request smuggling.
240
+ * `src/metadata.ts` is likewise unchanged: `user_id` and metadata keys are
241
+ * identifiers that travel as JSON inside a header, not prose.
242
+ *
243
+ * Every other C0 control (0x00-0x08, 0x0B, 0x0C, 0x0E-0x1F) and DEL (0x7F)
244
+ * stay rejected: they have no meaning in prompt text and are a reliable signal
245
+ * of a corrupted or hostile input.
246
+ *
247
+ * LONE SURROGATES stay rejected in every context, deliberately. `TextEncoder`
248
+ * silently replaces them with U+FFFD, so an unpaired surrogate would corrupt
249
+ * the body — and the body hash recorded in evidence — with no error anywhere.
250
+ */
251
+ function inspectString(value: string): number {
252
+ for (let index = 0; index < value.length; index += 1) {
253
+ const unit = value.charCodeAt(index);
254
+ if (
255
+ (unit <= 0x1f && unit !== 0x09 && unit !== 0x0a && unit !== 0x0d) ||
256
+ unit === 0x7f
257
+ ) {
258
+ fail("INVALID_UNICODE");
259
+ }
260
+ const classification = classifySurrogateAt(value, index);
261
+ if (classification === "loneSurrogate") fail("INVALID_UNICODE");
262
+ if (classification === "surrogatePair") index += 1;
263
+ }
264
+ return new TextEncoder().encode(value).byteLength;
265
+ }
266
+
267
+ function inspectGraph(value: unknown): void {
268
+ const active = new WeakSet();
269
+ let size = 0;
270
+
271
+ function visit(current: unknown, depth: number): void {
272
+ if (depth > MAX_INPUT_DEPTH) fail("INPUT_TOO_DEEP");
273
+ if (typeof current === "string") {
274
+ size += inspectString(current);
275
+ } else if (
276
+ current === null ||
277
+ typeof current === "boolean" ||
278
+ typeof current === "number"
279
+ ) {
280
+ if (typeof current === "number" && !Number.isFinite(current)) fail();
281
+ size += 1;
282
+ } else if (typeof current !== "object") {
283
+ fail();
284
+ } else {
285
+ const prototype = Reflect.getPrototypeOf(current);
286
+ if (
287
+ prototype !== null &&
288
+ prototype !== Object.prototype &&
289
+ prototype !== Array.prototype
290
+ ) {
291
+ fail();
292
+ }
293
+ if (active.has(current)) fail("CYCLIC_INPUT");
294
+ active.add(current);
295
+ const keys = Reflect.ownKeys(current);
296
+ size += keys.length;
297
+ for (const key of keys) {
298
+ if (typeof key !== "string" || FORBIDDEN_KEYS.has(key)) fail();
299
+ size += inspectString(key);
300
+ visit(ownValue(current, key), depth + 1);
301
+ }
302
+ active.delete(current);
303
+ }
304
+ if (size > MAX_INPUT_SIZE) fail("INPUT_TOO_LARGE");
305
+ }
306
+
307
+ visit(value, 0);
308
+ }
309
+
310
+ function containsString(value: unknown, target: string): boolean {
311
+ if (typeof value === "string") return value === target;
312
+ if (value === null || typeof value !== "object") return false;
313
+ return Reflect.ownKeys(value).some((key) =>
314
+ typeof key === "string"
315
+ ? containsString(ownValue(value, key), target)
316
+ : false,
317
+ );
318
+ }
319
+
320
+ /**
321
+ * The profiles this package will assemble a request for. Two entries: the
322
+ * 2.1.195 default and the 2.1.233 profile, which callers must pass
323
+ * explicitly. Admitting a profile is exactly this list -- `validateProfile`
324
+ * did not change to accept the second one.
325
+ *
326
+ * Membership is by REFERENCE, deliberately. A structural check would accept a
327
+ * caller-built object that merely looks like a pinned profile, and every wire
328
+ * guarantee this package makes -- the sealed golden fixtures, the packed
329
+ * consumer digests -- is a statement about the exact frozen singletons, not
330
+ * about anything shaped like them. `Set.prototype.has` uses SameValueZero, so
331
+ * `{ ...CLAUDE_CODE_2_1_195_PROFILE }` is rejected exactly as it was by the
332
+ * `!==` this replaced.
333
+ *
334
+ * Not exported and not frozen-with-teeth: `Object.freeze` on a `Set` blocks
335
+ * property assignment but NOT `add`, so freezing it would advertise a
336
+ * guarantee it cannot keep. Module scope is the real protection.
337
+ */
338
+ const ACCEPTED_PROFILES: ReadonlySet<ClaudeCodeProtocolProfile> = new Set([
339
+ CLAUDE_CODE_2_1_195_PROFILE,
340
+ CLAUDE_CODE_2_1_233_PROFILE,
341
+ ]);
342
+
343
+ /**
344
+ * The profile every public entry point resolves to when the caller supplies
345
+ * none. Declared once so that the default is a single, greppable seam: a test
346
+ * that means "whatever the default is" reads THIS instead of naming a
347
+ * version, which keeps a default switch to a one-line diff and keeps tests
348
+ * that genuinely mean 2.1.195 honest about saying so.
349
+ *
350
+ * Exported for tests, which deep-import it. It is deliberately NOT re-exported
351
+ * from `src/index.ts`: the public runtime surface stays closed.
352
+ */
353
+ export const DEFAULT_PROFILE: ClaudeCodeProtocolProfile =
354
+ CLAUDE_CODE_2_1_233_PROFILE;
355
+
356
+ function validateProfile(
357
+ profile: ClaudeCodeProtocolProfile,
358
+ ): ClaudeCodeProtocolProfile {
359
+ if (!ACCEPTED_PROFILES.has(profile)) fail();
360
+ return profile;
361
+ }
362
+
363
+ function isCryptoProvider(value: unknown): value is Pick<Crypto, "subtle"> {
364
+ if (!isRecord(value)) return false;
365
+ const subtleDescriptor = Object.getOwnPropertyDescriptor(value, "subtle");
366
+ if (
367
+ subtleDescriptor === undefined ||
368
+ !("value" in subtleDescriptor) ||
369
+ !isRecord(subtleDescriptor.value)
370
+ ) {
371
+ return false;
372
+ }
373
+ const digestDescriptor = Object.getOwnPropertyDescriptor(
374
+ subtleDescriptor.value,
375
+ "digest",
376
+ );
377
+ return (
378
+ digestDescriptor !== undefined &&
379
+ "value" in digestDescriptor &&
380
+ typeof digestDescriptor.value === "function"
381
+ );
382
+ }
383
+
384
+ function validateCrypto(value: unknown): Pick<Crypto, "subtle"> | undefined {
385
+ if (value === undefined) return undefined;
386
+ if (!isCryptoProvider(value)) fail("CRYPTO_UNAVAILABLE");
387
+ return value;
388
+ }
389
+
390
+ function requireNonEmptyString(value: unknown): string {
391
+ if (typeof value !== "string" || value.length === 0) fail();
392
+ return value;
393
+ }
394
+
395
+ function parseCatalogueCapabilities(value: unknown): readonly string[] {
396
+ if (
397
+ !Array.isArray(value) ||
398
+ !value.every((item) => typeof item === "string")
399
+ ) {
400
+ throw new ClaudeCodeWireError("INVALID_INPUT");
401
+ }
402
+ return Object.freeze([...value]);
403
+ }
404
+
405
+ /**
406
+ * Validates a catalogue entry's `maxOutputTokens`. Both fields are required
407
+ * when the object is present: a half-populated entry would silently fall back
408
+ * to the legacy limit table for the missing half, which is exactly the drift
409
+ * `modelOutputTokenLimits` is structured to prevent.
410
+ */
411
+ function parseCatalogueMaxOutputTokens(value: unknown): Readonly<{
412
+ readonly default: number;
413
+ readonly upper: number;
414
+ }> {
415
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
416
+ throw new ClaudeCodeWireError("INVALID_INPUT");
417
+ }
418
+ const keys = Reflect.ownKeys(value);
419
+ if (
420
+ keys.some(
421
+ (key) =>
422
+ typeof key !== "string" || (key !== "default" && key !== "upper"),
423
+ )
424
+ ) {
425
+ throw new ClaudeCodeWireError("INVALID_INPUT");
426
+ }
427
+ const defaultLimit: unknown = Reflect.get(value, "default");
428
+ const upper: unknown = Reflect.get(value, "upper");
429
+ if (
430
+ typeof defaultLimit !== "number" ||
431
+ !Number.isSafeInteger(defaultLimit) ||
432
+ defaultLimit <= 0 ||
433
+ typeof upper !== "number" ||
434
+ !Number.isSafeInteger(upper) ||
435
+ upper <= 0
436
+ ) {
437
+ throw new ClaudeCodeWireError("INVALID_INPUT");
438
+ }
439
+ return Object.freeze({ default: defaultLimit, upper });
440
+ }
441
+
442
+ function parseCatalogueContext(value: unknown): Readonly<{
443
+ readonly window: number;
444
+ readonly native1m?: boolean;
445
+ readonly supports1mBeta?: boolean;
446
+ }> {
447
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
448
+ throw new ClaudeCodeWireError("INVALID_INPUT");
449
+ }
450
+ const keys = Reflect.ownKeys(value);
451
+ if (
452
+ keys.some(
453
+ (key) =>
454
+ typeof key !== "string" ||
455
+ (key !== "window" && key !== "native1m" && key !== "supports1mBeta"),
456
+ )
457
+ ) {
458
+ throw new ClaudeCodeWireError("INVALID_INPUT");
459
+ }
460
+ const window: unknown = Reflect.get(value, "window");
461
+ const native1m: unknown = Reflect.get(value, "native1m");
462
+ const supports1mBeta: unknown = Reflect.get(value, "supports1mBeta");
463
+ if (
464
+ typeof window !== "number" ||
465
+ !Number.isFinite(window) ||
466
+ window <= 0 ||
467
+ (native1m !== undefined && typeof native1m !== "boolean") ||
468
+ (supports1mBeta !== undefined && typeof supports1mBeta !== "boolean")
469
+ ) {
470
+ throw new ClaudeCodeWireError("INVALID_INPUT");
471
+ }
472
+ return Object.freeze({
473
+ window,
474
+ ...(native1m === undefined ? {} : { native1m }),
475
+ ...(supports1mBeta === undefined ? {} : { supports1mBeta }),
476
+ });
477
+ }
478
+
479
+ function parseDefaultEffort(
480
+ value: unknown,
481
+ ): "low" | "medium" | "high" | "xhigh" | "max" {
482
+ if (
483
+ value !== "low" &&
484
+ value !== "medium" &&
485
+ value !== "high" &&
486
+ value !== "xhigh" &&
487
+ value !== "max"
488
+ ) {
489
+ throw new ClaudeCodeWireError("INVALID_INPUT");
490
+ }
491
+ return value;
492
+ }
493
+
494
+ function parseBoolean(value: unknown): boolean {
495
+ if (typeof value !== "boolean") {
496
+ throw new ClaudeCodeWireError("INVALID_INPUT");
497
+ }
498
+ return value;
499
+ }
500
+
501
+ function parseBetaPolicy(
502
+ value: unknown,
503
+ ): ClaudeCodeProtocolProfile["betaPolicy"] {
504
+ if (!isRecord(value)) fail();
505
+ assertExactKeys(value, BETA_POLICY_KEYS);
506
+ if (Reflect.ownKeys(value).length !== BETA_POLICY_KEYS.size) fail();
507
+ return Object.freeze({
508
+ oauthAuthenticated: parseBoolean(ownValue(value, "oauthAuthenticated")),
509
+ experimentalBetasEnabled: parseBoolean(
510
+ ownValue(value, "experimentalBetasEnabled"),
511
+ ),
512
+ oneMillionContextEnabled: parseBoolean(
513
+ ownValue(value, "oneMillionContextEnabled"),
514
+ ),
515
+ interleavedThinkingEnabled: parseBoolean(
516
+ ownValue(value, "interleavedThinkingEnabled"),
517
+ ),
518
+ interactive: parseBoolean(ownValue(value, "interactive")),
519
+ thinkingSummariesShown: parseBoolean(
520
+ ownValue(value, "thinkingSummariesShown"),
521
+ ),
522
+ thinkingTokenCountEnabled: parseBoolean(
523
+ ownValue(value, "thinkingTokenCountEnabled"),
524
+ ),
525
+ narrationSummariesEnabled: parseBoolean(
526
+ ownValue(value, "narrationSummariesEnabled"),
527
+ ),
528
+ structuredOutputsEnabled: parseBoolean(
529
+ ownValue(value, "structuredOutputsEnabled"),
530
+ ),
531
+ afkModeEnabled: parseBoolean(ownValue(value, "afkModeEnabled")),
532
+ cacheDiagnosisEnabled: parseBoolean(
533
+ ownValue(value, "cacheDiagnosisEnabled"),
534
+ ),
535
+ });
536
+ }
537
+
538
+ function parseSupportedModels(
539
+ value: unknown,
540
+ ): ClaudeCodeProtocolProfile["supportedModels"] {
541
+ if (!isRecord(value) || Reflect.ownKeys(value).length === 0) fail();
542
+ const result: Record<
543
+ string,
544
+ ClaudeCodeProtocolProfile["supportedModels"][string]
545
+ > = {};
546
+ for (const key of Reflect.ownKeys(value)) {
547
+ if (typeof key !== "string" || key.length === 0) fail();
548
+ const model = ownValue(value, key);
549
+ if (!isRecord(model)) fail();
550
+ assertExactKeys(model, MODEL_KEYS);
551
+ const family = ownValue(model, "family");
552
+ if (
553
+ family !== "haiku" &&
554
+ family !== "sonnet" &&
555
+ family !== "opus" &&
556
+ family !== "fable" &&
557
+ family !== "mythos" &&
558
+ family !== "unknown"
559
+ )
560
+ fail();
561
+ result[key] = Object.freeze({
562
+ family,
563
+ ...(Object.hasOwn(model, "context")
564
+ ? { context: parseCatalogueContext(ownValue(model, "context")) }
565
+ : {}),
566
+ capabilities: parseCatalogueCapabilities(ownValue(model, "capabilities")),
567
+ ...(Object.hasOwn(model, "maxOutputTokens")
568
+ ? {
569
+ maxOutputTokens: parseCatalogueMaxOutputTokens(
570
+ ownValue(model, "maxOutputTokens"),
571
+ ),
572
+ }
573
+ : {}),
574
+ ...(Object.hasOwn(model, "defaultEffort")
575
+ ? {
576
+ defaultEffort: parseDefaultEffort(ownValue(model, "defaultEffort")),
577
+ }
578
+ : {}),
579
+ });
580
+ }
581
+ return Object.freeze(result);
582
+ }
583
+
584
+ function validateProfileOverride(value: unknown): ClaudeCodeProfileOverride {
585
+ if (!isRecord(value)) fail();
586
+ assertExactKeys(value, OVERRIDE_KEYS);
587
+ if (Reflect.ownKeys(value).length === 0) fail();
588
+
589
+ const cliVersion = Object.hasOwn(value, "cliVersion")
590
+ ? requireNonEmptyString(ownValue(value, "cliVersion"))
591
+ : undefined;
592
+ const userAgent = Object.hasOwn(value, "userAgent")
593
+ ? requireNonEmptyString(ownValue(value, "userAgent"))
594
+ : undefined;
595
+ // `cc_version` in the billing header derives from `cliVersion` while the
596
+ // user agent is separate. Overriding one alone would emit a self-inconsistent
597
+ // client signature, precisely the fingerprint mismatch this package prevents.
598
+ if (cliVersion !== undefined && !userAgent?.includes(cliVersion)) {
599
+ fail();
600
+ }
601
+
602
+ const attributionHeaderEnabled = Object.hasOwn(
603
+ value,
604
+ "attributionHeaderEnabled",
605
+ )
606
+ ? ownValue(value, "attributionHeaderEnabled")
607
+ : undefined;
608
+ if (
609
+ attributionHeaderEnabled !== undefined &&
610
+ typeof attributionHeaderEnabled !== "boolean"
611
+ ) {
612
+ fail();
613
+ }
614
+
615
+ return Object.freeze({
616
+ ...(Object.hasOwn(value, "id")
617
+ ? { id: requireNonEmptyString(ownValue(value, "id")) }
618
+ : {}),
619
+ ...(cliVersion === undefined ? {} : { cliVersion }),
620
+ ...(Object.hasOwn(value, "sdkVersion")
621
+ ? { sdkVersion: requireNonEmptyString(ownValue(value, "sdkVersion")) }
622
+ : {}),
623
+ ...(Object.hasOwn(value, "entrypoint")
624
+ ? { entrypoint: requireNonEmptyString(ownValue(value, "entrypoint")) }
625
+ : {}),
626
+ ...(userAgent === undefined ? {} : { userAgent }),
627
+ ...(Object.hasOwn(value, "buildTime")
628
+ ? { buildTime: requireNonEmptyString(ownValue(value, "buildTime")) }
629
+ : {}),
630
+ ...(Object.hasOwn(value, "gitSha")
631
+ ? { gitSha: requireNonEmptyString(ownValue(value, "gitSha")) }
632
+ : {}),
633
+ ...(attributionHeaderEnabled === undefined
634
+ ? {}
635
+ : { attributionHeaderEnabled }),
636
+ ...(Object.hasOwn(value, "contextHintEnabled")
637
+ ? {
638
+ contextHintEnabled: parseBoolean(
639
+ ownValue(value, "contextHintEnabled"),
640
+ ),
641
+ }
642
+ : {}),
643
+ ...(Object.hasOwn(value, "betaPolicy")
644
+ ? { betaPolicy: parseBetaPolicy(ownValue(value, "betaPolicy")) }
645
+ : {}),
646
+ ...(Object.hasOwn(value, "supportedModels")
647
+ ? {
648
+ supportedModels: parseSupportedModels(
649
+ ownValue(value, "supportedModels"),
650
+ ),
651
+ }
652
+ : {}),
653
+ });
654
+ }
655
+
656
+ /**
657
+ * Validates the package-extension beta overrides.
658
+ *
659
+ * An explicitly present key with an `undefined` value is rejected rather than
660
+ * silently treated as absent, so the tri-state stays observable: the caller
661
+ * either states a decision or omits the key.
662
+ */
663
+ function validateBetaOverrides(value: unknown): ClaudeCodeBetaOverrides {
664
+ if (!isRecord(value)) fail();
665
+ assertExactKeys(value, BETA_OVERRIDE_KEYS);
666
+ return Object.freeze({
667
+ ...(Object.hasOwn(value, "use1MContext")
668
+ ? { use1MContext: parseBoolean(ownValue(value, "use1MContext")) }
669
+ : {}),
670
+ });
671
+ }
672
+
673
+ /**
674
+ * Validates the shape of the package-extension metadata overrides.
675
+ *
676
+ * Only the key set is decided here, exactly as `validateBetaOverrides` does.
677
+ * Member values, and the mutual exclusion between them, are decided by
678
+ * `buildCorrelatedMetadata`, which owns the correlation rules.
679
+ */
680
+ function validateMetadataOverrides(value: unknown): void {
681
+ if (!isRecord(value)) fail();
682
+ assertExactKeys(value, METADATA_OVERRIDE_KEYS);
683
+ }
684
+
685
+ /**
686
+ * Validates the package-extension extra-header policy.
687
+ *
688
+ * The two literals are the whole domain; anything else, including a casing
689
+ * variant or a padded string, is a caller mistake rather than a silent fallback
690
+ * to `strict`, because falling back would emit a request the caller did not ask
691
+ * for.
692
+ */
693
+ function validateExtraHeaderPolicy(
694
+ value: unknown,
695
+ ): ClaudeCodeExtraHeaderPolicy {
696
+ if (value !== "strict" && value !== "dropConflicting") fail();
697
+ return value;
698
+ }
699
+
700
+ /**
701
+ * Validates the package-extension billing-block suppression flag.
702
+ *
703
+ * Only a boolean states a decision. A truthy string or `0` is a caller mistake
704
+ * rather than a coercion target, because coercing would silently change what
705
+ * Anthropic receives for attribution purposes.
706
+ */
707
+ function validateSuppressBillingBlock(value: unknown): boolean {
708
+ return parseBoolean(value);
709
+ }
710
+
711
+ /**
712
+ * Validates the package-extension identity-block suppression flag.
713
+ *
714
+ * Same contract as `validateSuppressBillingBlock`: only a boolean states a
715
+ * decision, because coercing a truthy string would silently drop a canonical
716
+ * block the genuine client always sends.
717
+ */
718
+ function validateSuppressIdentityBlock(value: unknown): boolean {
719
+ return parseBoolean(value);
720
+ }
721
+
722
+ /**
723
+ * Validates the package-extension thinking-block `cache_control` seam flag.
724
+ *
725
+ * Same contract as the suppression flags: only a boolean states a decision,
726
+ * because coercing a truthy string would silently widen the thinking-block
727
+ * allowlist this package pins against the genuine client.
728
+ */
729
+ function validatePreserveThinkingBlockCacheControl(value: unknown): boolean {
730
+ return parseBoolean(value);
731
+ }
732
+
733
+ function createEffectiveProfile(
734
+ pinnedProfile: ClaudeCodeProtocolProfile,
735
+ override: ClaudeCodeProfileOverride | undefined,
736
+ ): ClaudeCodeProtocolProfile {
737
+ if (override === undefined) return pinnedProfile;
738
+ return deepFreeze({ ...pinnedProfile, ...override });
739
+ }
740
+
741
+ function applyEffectiveProfileHeaders(
742
+ headers: readonly HeaderPair[],
743
+ profile: ClaudeCodeProtocolProfile,
744
+ ): readonly HeaderPair[] {
745
+ return Object.freeze(
746
+ headers.map(([name, value]): HeaderPair => {
747
+ if (name === "user-agent") {
748
+ return Object.freeze([name, profile.userAgent]);
749
+ }
750
+ if (name === "x-stainless-package-version") {
751
+ return Object.freeze([name, profile.sdkVersion]);
752
+ }
753
+ return Object.freeze([name, value]);
754
+ }),
755
+ );
756
+ }
757
+
758
+ function validateInput(input: ClaudeCodeRequestInput): {
759
+ readonly source: ClaudeCodeRequestInput;
760
+ readonly clientRequestId: string;
761
+ readonly crypto: Pick<Crypto, "subtle"> | undefined;
762
+ readonly profileOverride: ClaudeCodeProfileOverride | undefined;
763
+ readonly betaOverrides: ClaudeCodeBetaOverrides | undefined;
764
+ readonly extraHeaderPolicy: ClaudeCodeExtraHeaderPolicy | undefined;
765
+ readonly suppressBillingBlock: boolean;
766
+ readonly suppressIdentityBlock: boolean;
767
+ readonly preserveThinkingBlockCacheControl: boolean;
768
+ readonly previousRequestId: string | undefined;
769
+ readonly promptId: string | undefined;
770
+ } {
771
+ if (!isRecord(input)) fail();
772
+ assertExactKeys(input, INPUT_KEYS);
773
+ const graph: Record<string, unknown> = {};
774
+ for (const key of Reflect.ownKeys(input)) {
775
+ if (key !== "crypto" && typeof key === "string") {
776
+ graph[key] = ownValue(input, key);
777
+ }
778
+ }
779
+ inspectGraph(graph);
780
+ const accessToken = ownValue(input, "accessToken");
781
+ if (
782
+ typeof accessToken !== "string" ||
783
+ typeof ownValue(input, "model") !== "string" ||
784
+ !Array.isArray(ownValue(input, "messages"))
785
+ ) {
786
+ fail();
787
+ }
788
+ for (const key of Reflect.ownKeys(input)) {
789
+ if (
790
+ typeof key === "string" &&
791
+ key !== "accessToken" &&
792
+ key !== "crypto" &&
793
+ containsString(ownValue(input, key), accessToken)
794
+ ) {
795
+ fail();
796
+ }
797
+ }
798
+ const clientRequestId = ownValue(input, "clientRequestId");
799
+ if (typeof clientRequestId !== "string" || clientRequestId.length === 0)
800
+ fail();
801
+ const cryptoValue = Object.hasOwn(input, "crypto")
802
+ ? validateCrypto(ownValue(input, "crypto"))
803
+ : undefined;
804
+ const profileOverride = Object.hasOwn(input, "profileOverride")
805
+ ? validateProfileOverride(ownValue(input, "profileOverride"))
806
+ : undefined;
807
+ const betaOverrides = Object.hasOwn(input, "betaOverrides")
808
+ ? validateBetaOverrides(ownValue(input, "betaOverrides"))
809
+ : undefined;
810
+ if (Object.hasOwn(input, "metadataOverrides")) {
811
+ validateMetadataOverrides(ownValue(input, "metadataOverrides"));
812
+ }
813
+ const extraHeaderPolicy = Object.hasOwn(input, "extraHeaderPolicy")
814
+ ? validateExtraHeaderPolicy(ownValue(input, "extraHeaderPolicy"))
815
+ : undefined;
816
+ const suppressBillingBlock = Object.hasOwn(input, "suppressBillingBlock")
817
+ ? validateSuppressBillingBlock(ownValue(input, "suppressBillingBlock"))
818
+ : false;
819
+ const suppressIdentityBlock = Object.hasOwn(input, "suppressIdentityBlock")
820
+ ? validateSuppressIdentityBlock(ownValue(input, "suppressIdentityBlock"))
821
+ : false;
822
+ const preserveThinkingBlockCacheControl = Object.hasOwn(
823
+ input,
824
+ "preserveThinkingBlockCacheControl",
825
+ )
826
+ ? validatePreserveThinkingBlockCacheControl(
827
+ ownValue(input, "preserveThinkingBlockCacheControl"),
828
+ )
829
+ : false;
830
+ const previousRequestId = Object.hasOwn(input, "previousRequestId")
831
+ ? validateBillingChainId(ownValue(input, "previousRequestId"))
832
+ : undefined;
833
+ const promptId = Object.hasOwn(input, "promptId")
834
+ ? validateBillingChainId(ownValue(input, "promptId"))
835
+ : undefined;
836
+ return {
837
+ source: input,
838
+ clientRequestId,
839
+ crypto: cryptoValue,
840
+ profileOverride,
841
+ betaOverrides,
842
+ extraHeaderPolicy,
843
+ suppressBillingBlock,
844
+ suppressIdentityBlock,
845
+ preserveThinkingBlockCacheControl,
846
+ previousRequestId,
847
+ promptId,
848
+ };
849
+ }
850
+
851
+ /**
852
+ * Type check only. The FORMAT of these two ids is deliberately not checked
853
+ * here: upstream guards them at the point of emission and drops a value it
854
+ * cannot vouch for, silently, so rejecting one here would make this package
855
+ * fail where the genuine client succeeds. `createBillingBlock` owns the
856
+ * patterns. A non-string is still a caller bug and fails like every other
857
+ * mistyped field.
858
+ *
859
+ * There is deliberately no `undefined` arm: `inspectGraph` has already rejected
860
+ * an explicitly-undefined value for every key but `crypto` by the time this
861
+ * runs, so such an arm would be unreachable. An explicitly-undefined id is
862
+ * therefore `INVALID_INPUT` here, as it is for every other field, and is NOT
863
+ * equivalent to omitting the key — unlike at the `createBillingBlock` seam,
864
+ * which does treat the two alike. `billing-prev-req.test.ts` pins both halves.
865
+ */
866
+ function validateBillingChainId(value: unknown): string {
867
+ if (typeof value !== "string") fail();
868
+ return value;
869
+ }
870
+
871
+ function validateCountTokensInput(input: ClaudeCodeCountTokensInput): {
872
+ readonly source: ClaudeCodeCountTokensInput;
873
+ readonly clientRequestId: string;
874
+ readonly crypto: Pick<Crypto, "subtle"> | undefined;
875
+ readonly profileOverride: ClaudeCodeProfileOverride | undefined;
876
+ } {
877
+ if (!isRecord(input)) fail();
878
+ assertExactKeys(input, COUNT_TOKENS_INPUT_KEYS);
879
+ const graph: Record<string, unknown> = {};
880
+ for (const key of Reflect.ownKeys(input)) {
881
+ if (key !== "crypto" && typeof key === "string") {
882
+ graph[key] = ownValue(input, key);
883
+ }
884
+ }
885
+ inspectGraph(graph);
886
+ const accessToken = ownValue(input, "accessToken");
887
+ const tools = Object.hasOwn(input, "tools")
888
+ ? ownValue(input, "tools")
889
+ : undefined;
890
+ if (
891
+ typeof accessToken !== "string" ||
892
+ typeof ownValue(input, "model") !== "string" ||
893
+ !Array.isArray(ownValue(input, "messages")) ||
894
+ (tools !== undefined && !Array.isArray(tools))
895
+ ) {
896
+ fail();
897
+ }
898
+ for (const key of Reflect.ownKeys(input)) {
899
+ if (
900
+ typeof key === "string" &&
901
+ key !== "accessToken" &&
902
+ key !== "crypto" &&
903
+ containsString(ownValue(input, key), accessToken)
904
+ ) {
905
+ fail();
906
+ }
907
+ }
908
+ const clientRequestId = ownValue(input, "clientRequestId");
909
+ if (typeof clientRequestId !== "string" || clientRequestId.length === 0) {
910
+ fail();
911
+ }
912
+ const cryptoValue = Object.hasOwn(input, "crypto")
913
+ ? validateCrypto(ownValue(input, "crypto"))
914
+ : undefined;
915
+ const profileOverride = Object.hasOwn(input, "profileOverride")
916
+ ? validateProfileOverride(ownValue(input, "profileOverride"))
917
+ : undefined;
918
+ return {
919
+ source: input,
920
+ clientRequestId,
921
+ crypto: cryptoValue,
922
+ profileOverride,
923
+ };
924
+ }
925
+
926
+ function requestedCapabilities(
927
+ input: ClaudeCodeRequestInput,
928
+ supported: ClaudeCodeCapabilities,
929
+ ): ClaudeCodeCapabilities {
930
+ const raw = input.capabilities;
931
+ if (raw !== undefined) {
932
+ if (!isRecord(raw)) fail("UNSUPPORTED_CAPABILITY");
933
+ assertExactKeys(raw, CAPABILITY_KEY_SET);
934
+ }
935
+ const result: ClaudeCodeCapabilities = {
936
+ thinking: raw?.thinking ?? supported.thinking,
937
+ adaptiveThinking: raw?.adaptiveThinking ?? supported.adaptiveThinking,
938
+ interleavedThinking:
939
+ raw?.interleavedThinking ?? supported.interleavedThinking,
940
+ effort: raw?.effort ?? supported.effort,
941
+ maxEffort: raw?.maxEffort ?? supported.maxEffort,
942
+ xhighEffort: raw?.xhighEffort ?? supported.xhighEffort,
943
+ contextManagement: raw?.contextManagement ?? supported.contextManagement,
944
+ temperature: raw?.temperature ?? supported.temperature,
945
+ rejectsDisabledThinking:
946
+ raw?.rejectsDisabledThinking ?? supported.rejectsDisabledThinking,
947
+ };
948
+ for (const key of CAPABILITY_KEYS) {
949
+ if (typeof result[key] !== "boolean") fail("UNSUPPORTED_CAPABILITY");
950
+ if (result[key] && !supported[key]) {
951
+ throw new ClaudeCodeWireError("UNSUPPORTED_CAPABILITY", {
952
+ capability: key,
953
+ });
954
+ }
955
+ }
956
+ return Object.freeze(result);
957
+ }
958
+
959
+ /**
960
+ * Selects the text that seeds the billing fingerprint.
961
+ *
962
+ * The source is the FIRST USER MESSAGE, and nothing else. Upstream
963
+ * `lib/mimicry/system-prompt.mjs:134,143` calls
964
+ * `buildAnthropicBillingHeader(version, firstUserMessage, ...)`, which forwards
965
+ * that message to `computeBillingCacheHash(firstUserMessage || "", version)`.
966
+ * The system prompt never contributes.
967
+ *
968
+ * This is protocol-critical: seeding the fingerprint from system text instead
969
+ * would emit a `cc_version` suffix that does not match the pinned wire profile,
970
+ * silently breaking parity while every local test still looked plausible.
971
+ * The committed goldens are the ground truth here — `outgoing-foreground.json`
972
+ * pairs first user text `hello wire compat` with `cc_version=2.1.195.0f6`.
973
+ */
974
+ function fingerprintText(input: ClaudeCodeRequestInput): string {
975
+ for (const message of input.messages) {
976
+ if (message.role !== "user") continue;
977
+ if (typeof message.content === "string") return message.content;
978
+ for (const block of message.content) {
979
+ if (block.type === "text") return block.text;
980
+ }
981
+ // The first user message exists but carries no text block. Upstream's
982
+ // `firstUserMessage || ""` fallback applies; later messages never substitute.
983
+ return "";
984
+ }
985
+ return "";
986
+ }
987
+
988
+ function sanitizeError(error: unknown): never {
989
+ if (error instanceof ClaudeCodeWireError) {
990
+ const details = toSafeErrorDetails(error);
991
+ const safeDetails = Object.fromEntries(
992
+ Object.entries(details).filter(([key]) => key !== "code"),
993
+ );
994
+ throw new ClaudeCodeWireError(error.code, safeDetails);
995
+ }
996
+ throw new ClaudeCodeWireError("INVALID_INPUT");
997
+ }
998
+
999
+ function deepFreeze<T>(value: T): T {
1000
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
1001
+ for (const key of Reflect.ownKeys(value))
1002
+ deepFreeze(Reflect.get(value, key));
1003
+ Object.freeze(value);
1004
+ }
1005
+ return value;
1006
+ }
1007
+
1008
+ function parseStringArray(value: unknown): readonly string[] {
1009
+ if (!Array.isArray(value)) fail();
1010
+ return value.map((entry) => {
1011
+ if (typeof entry !== "string") fail();
1012
+ return entry;
1013
+ });
1014
+ }
1015
+
1016
+ function parseHeaders(value: unknown): readonly HeaderPair[] {
1017
+ if (!Array.isArray(value)) fail();
1018
+ return value.map((entry): HeaderPair => {
1019
+ if (
1020
+ !Array.isArray(entry) ||
1021
+ entry.length !== 2 ||
1022
+ typeof entry[0] !== "string" ||
1023
+ typeof entry[1] !== "string"
1024
+ ) {
1025
+ fail();
1026
+ }
1027
+ return [entry[0], entry[1]];
1028
+ });
1029
+ }
1030
+
1031
+ function parseCapabilityDecisions(
1032
+ value: unknown,
1033
+ ): ClaudeCodeCapabilityDecisions {
1034
+ if (!isRecord(value)) fail();
1035
+ // The nine capability keys are mandatory; the package-extension override keys
1036
+ // are optional and must survive the round-trip untouched, so they are allowed
1037
+ // here but never synthesized.
1038
+ assertExactKeys(value, CAPABILITY_DECISION_KEY_SET);
1039
+ // Read each key through a narrowing helper rather than building a record and
1040
+ // re-checking it afterwards. The re-check was unreachable -- every value was
1041
+ // already proven boolean -- which cost coverage and produced mutants that
1042
+ // could not be killed.
1043
+ const readBoolean = (key: string): boolean => {
1044
+ const entry = ownValue(value, key);
1045
+ if (typeof entry !== "boolean") fail();
1046
+ return entry;
1047
+ };
1048
+ return {
1049
+ ...(Object.hasOwn(value, "use1MContext")
1050
+ ? { use1MContext: readBoolean("use1MContext") }
1051
+ : {}),
1052
+ thinking: readBoolean("thinking"),
1053
+ adaptiveThinking: readBoolean("adaptiveThinking"),
1054
+ interleavedThinking: readBoolean("interleavedThinking"),
1055
+ effort: readBoolean("effort"),
1056
+ maxEffort: readBoolean("maxEffort"),
1057
+ xhighEffort: readBoolean("xhighEffort"),
1058
+ contextManagement: readBoolean("contextManagement"),
1059
+ temperature: readBoolean("temperature"),
1060
+ rejectsDisabledThinking: readBoolean("rejectsDisabledThinking"),
1061
+ };
1062
+ }
1063
+
1064
+ /**
1065
+ * Validates evidence against the profile the request was parsed under, not
1066
+ * against a hardcoded singleton. `parseBuiltClaudeCodeRequest` already
1067
+ * validates `url` against `pinnedProfile.endpoint`; the profile id is the one
1068
+ * remaining field where the two pinned profiles differ, so it has to follow
1069
+ * the same source or a request built with a non-default profile could never
1070
+ * be re-parsed. Still fail-closed: the profile reaching here has already
1071
+ * passed `validateProfile`.
1072
+ */
1073
+ function parseEvidence(
1074
+ value: unknown,
1075
+ pinnedProfile: ClaudeCodeProtocolProfile,
1076
+ ): RedactedRequestEvidence {
1077
+ if (!isRecord(value)) fail();
1078
+ assertExactKeys(value, EVIDENCE_KEYS);
1079
+ const modelFamily = ownValue(value, "modelFamily");
1080
+ if (
1081
+ modelFamily !== "haiku" &&
1082
+ modelFamily !== "sonnet" &&
1083
+ modelFamily !== "opus" &&
1084
+ modelFamily !== "fable" &&
1085
+ modelFamily !== "mythos" &&
1086
+ modelFamily !== "unknown"
1087
+ ) {
1088
+ fail();
1089
+ }
1090
+ const bodySha256 = ownValue(value, "bodySha256");
1091
+ const bodyByteLength = ownValue(value, "bodyByteLength");
1092
+ const messageCount = ownValue(value, "messageCount");
1093
+ const systemBlockCount = ownValue(value, "systemBlockCount");
1094
+ if (
1095
+ ownValue(value, "profileId") !== pinnedProfile.id ||
1096
+ ownValue(value, "url") !== pinnedProfile.endpoint ||
1097
+ ownValue(value, "method") !== METHOD ||
1098
+ typeof bodySha256 !== "string" ||
1099
+ !/^[0-9a-f]{64}$/u.test(bodySha256) ||
1100
+ typeof bodyByteLength !== "number" ||
1101
+ !Number.isSafeInteger(bodyByteLength) ||
1102
+ typeof messageCount !== "number" ||
1103
+ !Number.isSafeInteger(messageCount) ||
1104
+ typeof systemBlockCount !== "number" ||
1105
+ !Number.isSafeInteger(systemBlockCount)
1106
+ ) {
1107
+ fail();
1108
+ }
1109
+ return {
1110
+ profileId: pinnedProfile.id,
1111
+ url: pinnedProfile.endpoint,
1112
+ method: METHOD,
1113
+ modelFamily,
1114
+ logicalHeaderNames: parseStringArray(ownValue(value, "logicalHeaderNames")),
1115
+ betaFeatures: parseStringArray(ownValue(value, "betaFeatures")),
1116
+ bodySha256,
1117
+ bodyByteLength,
1118
+ messageCount,
1119
+ systemBlockCount,
1120
+ capabilityDecisions: parseCapabilityDecisions(
1121
+ ownValue(value, "capabilityDecisions"),
1122
+ ),
1123
+ // Optional package-extension audit: preserved verbatim when present and
1124
+ // never synthesized, exactly like the `use1MContext` decision key.
1125
+ ...(Object.hasOwn(value, "droppedExtraHeaderNames")
1126
+ ? {
1127
+ droppedExtraHeaderNames: parseStringArray(
1128
+ ownValue(value, "droppedExtraHeaderNames"),
1129
+ ),
1130
+ }
1131
+ : {}),
1132
+ ...(Object.hasOwn(value, "suppressedBetaNames")
1133
+ ? {
1134
+ suppressedBetaNames: parseStringArray(
1135
+ ownValue(value, "suppressedBetaNames"),
1136
+ ),
1137
+ }
1138
+ : {}),
1139
+ ...(Object.hasOwn(value, "billingBlockSuppressed")
1140
+ ? {
1141
+ billingBlockSuppressed: parseBoolean(
1142
+ ownValue(value, "billingBlockSuppressed"),
1143
+ ),
1144
+ }
1145
+ : {}),
1146
+ ...(Object.hasOwn(value, "identityBlockSuppressed")
1147
+ ? {
1148
+ identityBlockSuppressed: parseBoolean(
1149
+ ownValue(value, "identityBlockSuppressed"),
1150
+ ),
1151
+ }
1152
+ : {}),
1153
+ // Rehydrated here because `assertExactKeys` above already accepts the key:
1154
+ // omitting this branch would silently DROP it, and the round-trip equality
1155
+ // every seam test asserts would fail on an envelope that is entirely legal.
1156
+ ...(Object.hasOwn(value, "thinkingBlockCacheControlPreserved")
1157
+ ? {
1158
+ thinkingBlockCacheControlPreserved: parseBoolean(
1159
+ ownValue(value, "thinkingBlockCacheControlPreserved"),
1160
+ ),
1161
+ }
1162
+ : {}),
1163
+ };
1164
+ }
1165
+
1166
+ /** Reads an own property of a value that is not asserted to be a record. */
1167
+ function ownProperty(value: unknown, key: string): unknown {
1168
+ return isRecord(value) && Object.hasOwn(value, key)
1169
+ ? ownValue(value, key)
1170
+ : undefined;
1171
+ }
1172
+
1173
+ /**
1174
+ * Reports whether the emitted `messages` carry a reasoning block that actually
1175
+ * kept a `cache_control` key.
1176
+ *
1177
+ * This is what makes `evidence.thinkingBlockCacheControlPreserved` a record of
1178
+ * what the seam DID rather than of what it was allowed to do, and it is the
1179
+ * check that refutes an envelope claiming the seam over a body carrying no such
1180
+ * block. Presence of the KEY is the test, not truthiness: `cache_control: null`
1181
+ * is a preserved marker too, exactly as it is on every other block type.
1182
+ */
1183
+ function hasThinkingBlockCacheControl(messages: unknown): boolean {
1184
+ if (!Array.isArray(messages)) return false;
1185
+ return messages.some((message) => {
1186
+ const content = ownProperty(message, "content");
1187
+ return (
1188
+ Array.isArray(content) &&
1189
+ content.some((block) => {
1190
+ const type = ownProperty(block, "type");
1191
+ return (
1192
+ (type === "thinking" || type === "redacted_thinking") &&
1193
+ ownProperty(block, "cache_control") !== undefined
1194
+ );
1195
+ })
1196
+ );
1197
+ });
1198
+ }
1199
+
1200
+ /** Reads a system block's `text` without asserting the block's shape. */
1201
+ function systemBlockText(block: unknown): unknown {
1202
+ return ownProperty(block, "text");
1203
+ }
1204
+
1205
+ /** Recognises the canonical billing block by its fixed, self-describing head. */
1206
+ function isBillingBlockText(text: unknown): boolean {
1207
+ return typeof text === "string" && text.startsWith(BILLING_BLOCK_TEXT_PREFIX);
1208
+ }
1209
+
1210
+ /**
1211
+ * VERIFIES how many canonical blocks the emitted `system` array carries.
1212
+ *
1213
+ * The root seams `suppressBillingBlock` and `suppressIdentityBlock` make four
1214
+ * prefixes legitimate — `[billing, identity]`, `[identity]`, `[billing]` and
1215
+ * `[]` — so no probe over the array alone can tell them apart: an empty prefix
1216
+ * is indistinguishable from a caller-only array. The prefix length is therefore
1217
+ * READ from the evidence flags, which the builder emits only when suppression
1218
+ * actually removed a block.
1219
+ *
1220
+ * Evidence is not trusted blindly. Each block the flags claim is present is
1221
+ * confirmed in place: billing by its `x-anthropic-billing-header: cc_version=`
1222
+ * head (its tail is per-request), identity by the byte-exact `IDENTITY_TEXT`;
1223
+ * a claim that identity was suppressed is confirmed by that text being absent
1224
+ * from the whole array. This is strictly stronger than the position probe it
1225
+ * replaces, which never checked the billing slot at all. Never `cache_control`:
1226
+ * the
1227
+ * `cacheControl.suppressIdentityBlock` seam can emit the identity block with no
1228
+ * marker, so a marker probe would misread a legitimate request.
1229
+ */
1230
+ function canonicalSystemPrefixLength(
1231
+ system: readonly unknown[],
1232
+ billingSuppressed: boolean,
1233
+ identitySuppressed: boolean,
1234
+ ): number {
1235
+ let index = 0;
1236
+ if (!billingSuppressed) {
1237
+ if (!isBillingBlockText(systemBlockText(system[index]))) fail();
1238
+ index += 1;
1239
+ }
1240
+ if (identitySuppressed) {
1241
+ // The identity text cannot appear ANYWHERE in a body built with the seam
1242
+ // active: `buildCanonicalSystem` drops a caller block equal to it
1243
+ // unconditionally, and merging joins with `\n`, so no merged run can equal
1244
+ // it either. Absence is therefore checkable, which is what refutes a claim
1245
+ // of suppression made over a body that still carries the block.
1246
+ //
1247
+ // No mirror check exists for billing: a caller block may legitimately begin
1248
+ // with the billing header text, so its presence proves nothing.
1249
+ if (system.some((block) => systemBlockText(block) === IDENTITY_TEXT)) {
1250
+ fail();
1251
+ }
1252
+ return index;
1253
+ }
1254
+ if (systemBlockText(system[index]) !== IDENTITY_TEXT) fail();
1255
+ return index + 1;
1256
+ }
1257
+
1258
+ function parseBody(value: string): UnknownRecord {
1259
+ let parsed: unknown;
1260
+ try {
1261
+ parsed = JSON.parse(value);
1262
+ } catch {
1263
+ fail();
1264
+ }
1265
+ if (!isRecord(parsed)) fail();
1266
+ return parsed;
1267
+ }
1268
+
1269
+ function parsedBodySessionId(body: UnknownRecord): string {
1270
+ const metadata = body["metadata"];
1271
+ if (!isRecord(metadata) || typeof metadata["user_id"] !== "string") fail();
1272
+ const identity = parseBody(metadata["user_id"]);
1273
+ const sessionId = identity["session_id"];
1274
+ if (typeof sessionId !== "string") fail();
1275
+ return sessionId;
1276
+ }
1277
+
1278
+ function headerValue(headers: readonly HeaderPair[], name: string): string {
1279
+ const matches = headers.filter(([candidate]) => candidate === name);
1280
+ if (matches.length !== 1) fail();
1281
+ const match = matches[0];
1282
+ if (match === undefined) fail();
1283
+ return match[1];
1284
+ }
1285
+
1286
+ function splitDynamicAndExtraHeaders(headers: readonly HeaderPair[]): {
1287
+ readonly stainlessHelper: string | undefined;
1288
+ readonly claudeRemoteContainerId: string | undefined;
1289
+ readonly claudeRemoteSessionId: string | undefined;
1290
+ readonly clientApp: string | undefined;
1291
+ readonly anthropicAdditionalProtection: string | undefined;
1292
+ readonly extraHeaders: readonly HeaderPair[];
1293
+ } {
1294
+ const timeoutIndex = headers.findIndex(
1295
+ ([name]) => name === "x-stainless-timeout",
1296
+ );
1297
+ if (timeoutIndex < 0) fail();
1298
+ let cursor = timeoutIndex + 1;
1299
+ function consume(name: string): string | undefined {
1300
+ const pair = headers[cursor];
1301
+ if (pair?.[0] !== name) return undefined;
1302
+ cursor += 1;
1303
+ return pair[1];
1304
+ }
1305
+ return {
1306
+ stainlessHelper: consume("x-stainless-helper"),
1307
+ claudeRemoteContainerId: consume("x-claude-remote-container-id"),
1308
+ claudeRemoteSessionId: consume("x-claude-remote-session-id"),
1309
+ clientApp: consume("x-client-app"),
1310
+ anthropicAdditionalProtection: consume("x-anthropic-additional-protection"),
1311
+ extraHeaders: headers.slice(cursor),
1312
+ };
1313
+ }
1314
+
1315
+ function evidenceRequest(
1316
+ input: ClaudeCodeRequestInput,
1317
+ callerModel: string,
1318
+ ): NormalizedRequestInput {
1319
+ const request: {
1320
+ accessToken: string;
1321
+ model: string;
1322
+ maxTokens: number;
1323
+ messages: ClaudeCodeRequestInput["messages"];
1324
+ runtime: ClaudeCodeRequestInput["runtime"];
1325
+ system?: NonNullable<ClaudeCodeRequestInput["system"]>;
1326
+ tools?: NonNullable<ClaudeCodeRequestInput["tools"]>;
1327
+ cacheControl?: Exclude<ClaudeCodeRequestInput["cacheControl"], undefined>;
1328
+ capabilities?: NonNullable<ClaudeCodeRequestInput["capabilities"]>;
1329
+ betaOverrides?: NonNullable<ClaudeCodeRequestInput["betaOverrides"]>;
1330
+ preserveThinkingBlockCacheControl?: NonNullable<
1331
+ ClaudeCodeRequestInput["preserveThinkingBlockCacheControl"]
1332
+ >;
1333
+ thinking?: NonNullable<ClaudeCodeRequestInput["thinking"]>;
1334
+ effort?: NonNullable<ClaudeCodeRequestInput["effort"]>;
1335
+ metadata?: NonNullable<ClaudeCodeRequestInput["metadata"]>;
1336
+ experimentalBodyFields?: NonNullable<
1337
+ ClaudeCodeRequestInput["experimentalBodyFields"]
1338
+ >;
1339
+ contextManagement?: Exclude<
1340
+ ClaudeCodeRequestInput["contextManagement"],
1341
+ undefined
1342
+ >;
1343
+ outputConfig?: Exclude<ClaudeCodeRequestInput["outputConfig"], undefined>;
1344
+ speed?: Exclude<ClaudeCodeRequestInput["speed"], undefined>;
1345
+ serviceTier?: Exclude<ClaudeCodeRequestInput["serviceTier"], undefined>;
1346
+ outputFormat?: Exclude<ClaudeCodeRequestInput["outputFormat"], undefined>;
1347
+ toolChoice?: Exclude<ClaudeCodeRequestInput["toolChoice"], undefined>;
1348
+ topP?: Exclude<ClaudeCodeRequestInput["topP"], undefined>;
1349
+ topK?: Exclude<ClaudeCodeRequestInput["topK"], undefined>;
1350
+ stopSequences?: Exclude<ClaudeCodeRequestInput["stopSequences"], undefined>;
1351
+ stream?: Exclude<ClaudeCodeRequestInput["stream"], undefined>;
1352
+ temperature?: Exclude<ClaudeCodeRequestInput["temperature"], undefined>;
1353
+ } = {
1354
+ accessToken: input.accessToken,
1355
+ model: callerModel,
1356
+ maxTokens: input.maxTokens,
1357
+ messages: input.messages,
1358
+ runtime: input.runtime,
1359
+ };
1360
+ if (input.system !== undefined) request.system = input.system;
1361
+ if (input.tools !== undefined) request.tools = input.tools;
1362
+ if (Object.hasOwn(input, "cacheControl"))
1363
+ request.cacheControl = present(input.cacheControl);
1364
+ if (input.capabilities !== undefined)
1365
+ request.capabilities = input.capabilities;
1366
+ if (input.betaOverrides !== undefined)
1367
+ request.betaOverrides = input.betaOverrides;
1368
+ // The body builder owns the thinking-block allowlist, so the seam flag has to
1369
+ // reach it. It is forwarded only when the caller stated it, keeping the
1370
+ // normalized request shape identical for every request that ignores the seam.
1371
+ if (input.preserveThinkingBlockCacheControl !== undefined)
1372
+ request.preserveThinkingBlockCacheControl =
1373
+ input.preserveThinkingBlockCacheControl;
1374
+ if (input.thinking !== undefined) request.thinking = input.thinking;
1375
+ if (input.effort !== undefined) request.effort = input.effort;
1376
+ if (input.metadata !== undefined) request.metadata = input.metadata;
1377
+ if (input.experimentalBodyFields !== undefined)
1378
+ request.experimentalBodyFields = input.experimentalBodyFields;
1379
+ if (Object.hasOwn(input, "contextManagement"))
1380
+ request.contextManagement = present(input.contextManagement);
1381
+ if (Object.hasOwn(input, "outputConfig"))
1382
+ request.outputConfig = present(input.outputConfig);
1383
+ if (Object.hasOwn(input, "speed")) request.speed = present(input.speed);
1384
+ if (Object.hasOwn(input, "serviceTier"))
1385
+ request.serviceTier = present(input.serviceTier);
1386
+ if (Object.hasOwn(input, "outputFormat"))
1387
+ request.outputFormat = present(input.outputFormat);
1388
+ if (Object.hasOwn(input, "toolChoice"))
1389
+ request.toolChoice = present(input.toolChoice);
1390
+ if (Object.hasOwn(input, "topP")) request.topP = present(input.topP);
1391
+ if (Object.hasOwn(input, "topK")) request.topK = present(input.topK);
1392
+ if (Object.hasOwn(input, "stopSequences"))
1393
+ request.stopSequences = present(input.stopSequences);
1394
+ if (Object.hasOwn(input, "stream")) request.stream = present(input.stream);
1395
+ if (Object.hasOwn(input, "temperature"))
1396
+ request.temperature = present(input.temperature);
1397
+ return request;
1398
+ }
1399
+
1400
+ function countTokensEvidenceRequest(
1401
+ input: ClaudeCodeCountTokensInput,
1402
+ capabilities: ClaudeCodeCapabilities,
1403
+ ): NormalizedRequestInput {
1404
+ return {
1405
+ accessToken: input.accessToken,
1406
+ model: input.model,
1407
+ maxTokens: 1,
1408
+ messages: input.messages,
1409
+ ...(input.tools === undefined ? {} : { tools: input.tools }),
1410
+ runtime: input.runtime,
1411
+ capabilities,
1412
+ };
1413
+ }
1414
+
1415
+ /** Builds a canonical Claude Code count-tokens request. */
1416
+ export async function buildClaudeCodeCountTokensRequest(
1417
+ input: ClaudeCodeCountTokensInput,
1418
+ profile: ClaudeCodeProtocolProfile = DEFAULT_PROFILE,
1419
+ ): Promise<BuiltClaudeCodeCountTokensRequest> {
1420
+ try {
1421
+ const pinnedProfile = validateProfile(profile);
1422
+ const validated = validateCountTokensInput(input);
1423
+ const effectiveProfile = createEffectiveProfile(
1424
+ pinnedProfile,
1425
+ validated.profileOverride,
1426
+ );
1427
+ const identity = validateRuntimeIdentity(validated.source.runtime);
1428
+ const resolvedModel = resolveModel(
1429
+ validated.source.model,
1430
+ effectiveProfile,
1431
+ );
1432
+ const countTokensBetas = filterCountTokensBetas(
1433
+ composeBetas(
1434
+ {
1435
+ rawModel: validated.source.model,
1436
+ normalizedId: resolvedModel.id,
1437
+ capabilities: resolvedModel.capabilities,
1438
+ thinkingDisplayActive: false,
1439
+ },
1440
+ effectiveProfile,
1441
+ ),
1442
+ );
1443
+ const betas = Object.freeze([...countTokensBetas, TOKEN_COUNTING_BETA]);
1444
+ const headers = applyEffectiveProfileHeaders(
1445
+ buildOrderedHeaders({
1446
+ accessToken: validated.source.accessToken,
1447
+ runtime: identity,
1448
+ clientRequestId: validated.clientRequestId,
1449
+ betaFeatures: betas,
1450
+ app: validated.source.app ?? effectiveProfile.entrypoint,
1451
+ stainlessRetryCount: validated.source.stainlessRetryCount ?? 0,
1452
+ stainlessHelper: validated.source.stainlessHelper,
1453
+ claudeRemoteContainerId: validated.source.claudeRemoteContainerId,
1454
+ claudeRemoteSessionId: validated.source.claudeRemoteSessionId,
1455
+ clientApp: validated.source.clientApp,
1456
+ anthropicAdditionalProtection:
1457
+ validated.source.anthropicAdditionalProtection,
1458
+ extraHeaders: validated.source.extraHeaders ?? [],
1459
+ profile: pinnedProfile,
1460
+ }),
1461
+ effectiveProfile,
1462
+ );
1463
+ const canonical = canonicalCountTokensLists(
1464
+ validated.source.messages,
1465
+ validated.source.tools,
1466
+ );
1467
+ const body = JSON.stringify(
1468
+ buildCountTokensBody(
1469
+ resolvedModel.wireId,
1470
+ canonical.messages,
1471
+ canonical.tools,
1472
+ ),
1473
+ );
1474
+ const evidenceRequestInput = countTokensEvidenceRequest(
1475
+ validated.source,
1476
+ resolvedModel.capabilities,
1477
+ );
1478
+ const evidence = await buildRedactedEvidence(
1479
+ {
1480
+ profile: pinnedProfile,
1481
+ effectiveProfile,
1482
+ request: evidenceRequestInput,
1483
+ modelFamily: resolvedModel.family,
1484
+ logicalHeaders: headers,
1485
+ betaFeatures: betas,
1486
+ body,
1487
+ },
1488
+ validated.crypto,
1489
+ );
1490
+ return deepFreeze({
1491
+ url: effectiveProfile.countTokensEndpoint,
1492
+ method: METHOD,
1493
+ headers,
1494
+ body,
1495
+ evidence,
1496
+ });
1497
+ } catch (error: unknown) {
1498
+ return sanitizeError(error);
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * Builds one canonical request for the pinned Claude Code wire profile.
1504
+ *
1505
+ * @param profile - The only accepted value is the exported
1506
+ * `CLAUDE_CODE_2_1_195_PROFILE` singleton. Any other object, even a
1507
+ * structurally identical clone, is rejected with `ClaudeCodeWireError` code
1508
+ * `INVALID_INPUT`. This deliberate fail-closed behaviour prevents callers from
1509
+ * substituting an unpinned protocol profile.
1510
+ */
1511
+ export async function buildClaudeCodeRequest(
1512
+ input: ClaudeCodeRequestInput,
1513
+ profile: ClaudeCodeProtocolProfile = DEFAULT_PROFILE,
1514
+ ): Promise<BuiltClaudeCodeRequest> {
1515
+ try {
1516
+ const pinnedProfile = validateProfile(profile);
1517
+ const validated = validateInput(input);
1518
+ const effectiveProfile = createEffectiveProfile(
1519
+ pinnedProfile,
1520
+ validated.profileOverride,
1521
+ );
1522
+ const identity = validateRuntimeIdentity(validated.source.runtime);
1523
+ const resolvedModel = resolveModel(
1524
+ validated.source.model,
1525
+ effectiveProfile,
1526
+ );
1527
+ const capabilities = requestedCapabilities(
1528
+ validated.source,
1529
+ resolvedModel.capabilities,
1530
+ );
1531
+ const effectiveModel = Object.freeze({
1532
+ ...resolvedModel,
1533
+ capabilities,
1534
+ });
1535
+ /*
1536
+ * The previous turn's request id is the CALLER's to supply. Upstream
1537
+ * derives it by scanning the conversation for the last assistant message
1538
+ * and reading a `requestId` it stored alongside it — a field of the
1539
+ * client's own transcript, not of the Messages API wire format. Modelling
1540
+ * that would mean adding a non-wire property to `Message` and having this
1541
+ * package infer conversation state it does not own. A documented
1542
+ * divergence of convenience: the value is the same, the plumbing is the
1543
+ * consumer's.
1544
+ */
1545
+ const billing = await createBillingBlock(
1546
+ fingerprintText(validated.source),
1547
+ effectiveProfile,
1548
+ validated.crypto,
1549
+ {
1550
+ ...(validated.previousRequestId !== undefined && {
1551
+ previousRequestId: validated.previousRequestId,
1552
+ }),
1553
+ ...(validated.promptId !== undefined && {
1554
+ promptId: validated.promptId,
1555
+ }),
1556
+ },
1557
+ );
1558
+ const metadata = buildCorrelatedMetadata(
1559
+ identity,
1560
+ validated.source.metadata,
1561
+ validated.source.metadataOverrides,
1562
+ );
1563
+ const system = buildCanonicalSystem(
1564
+ validated.source.system,
1565
+ billing,
1566
+ identity,
1567
+ validated.suppressBillingBlock,
1568
+ validated.suppressIdentityBlock,
1569
+ );
1570
+ const canonicalBody = buildCanonicalBody(
1571
+ evidenceRequest(validated.source, validated.source.model),
1572
+ effectiveModel,
1573
+ system,
1574
+ metadata,
1575
+ effectiveProfile,
1576
+ );
1577
+ const composedBetas = composeBetasWithAudit(
1578
+ {
1579
+ rawModel: validated.source.model,
1580
+ normalizedId: resolvedModel.id,
1581
+ capabilities,
1582
+ thinkingDisplayActive: isThinkingDisplayActive(
1583
+ validated.source.thinking,
1584
+ capabilities,
1585
+ effectiveProfile.betaPolicy,
1586
+ ),
1587
+ ...(validated.source.cacheControl?.ttl === undefined
1588
+ ? {}
1589
+ : { cacheTtl: validated.source.cacheControl.ttl }),
1590
+ ...(validated.source.speed === undefined
1591
+ ? {}
1592
+ : { speed: validated.source.speed }),
1593
+ ...(validated.source.additionalBetas === undefined
1594
+ ? {}
1595
+ : { additionalBetas: validated.source.additionalBetas }),
1596
+ ...(validated.source.suppressBetas === undefined
1597
+ ? {}
1598
+ : { suppressBetas: validated.source.suppressBetas }),
1599
+ ...(validated.betaOverrides?.use1MContext === undefined
1600
+ ? {}
1601
+ : { use1MContextOverride: validated.betaOverrides.use1MContext }),
1602
+ },
1603
+ effectiveProfile,
1604
+ );
1605
+ const betas = composedBetas.betas;
1606
+ const headerPlan = buildOrderedHeaderPlan({
1607
+ accessToken: validated.source.accessToken,
1608
+ runtime: identity,
1609
+ clientRequestId: validated.clientRequestId,
1610
+ betaFeatures: betas,
1611
+ app: validated.source.app ?? effectiveProfile.entrypoint,
1612
+ stainlessRetryCount: validated.source.stainlessRetryCount ?? 0,
1613
+ stainlessHelper: validated.source.stainlessHelper,
1614
+ claudeRemoteContainerId: validated.source.claudeRemoteContainerId,
1615
+ claudeRemoteSessionId: validated.source.claudeRemoteSessionId,
1616
+ clientApp: validated.source.clientApp,
1617
+ anthropicAdditionalProtection:
1618
+ validated.source.anthropicAdditionalProtection,
1619
+ extraHeaders: validated.source.extraHeaders ?? [],
1620
+ extraHeaderPolicy: validated.extraHeaderPolicy ?? "strict",
1621
+ profile: pinnedProfile,
1622
+ });
1623
+ const headers = applyEffectiveProfileHeaders(
1624
+ headerPlan.headers,
1625
+ effectiveProfile,
1626
+ );
1627
+ const body = JSON.stringify(canonicalBody);
1628
+ const evidence = await buildRedactedEvidence(
1629
+ {
1630
+ profile: pinnedProfile,
1631
+ effectiveProfile,
1632
+ request: evidenceRequest(validated.source, validated.source.model),
1633
+ modelFamily: resolvedModel.family,
1634
+ logicalHeaders: headers,
1635
+ betaFeatures: betas,
1636
+ body,
1637
+ // The canonical system merges adjacent caller blocks and drops any
1638
+ // block equal to the identity text, so only the emitted count keeps
1639
+ // `systemBlockCount === body.system.length - <canonical>` true.
1640
+ // Symmetric arithmetic over the two suppression seams: each canonical
1641
+ // block that survived costs one slot. A single constant cannot express
1642
+ // the empty prefix both seams together produce.
1643
+ emittedSystemBlockCount:
1644
+ system.length -
1645
+ (validated.suppressBillingBlock ? 0 : 1) -
1646
+ (validated.suppressIdentityBlock ? 0 : 1),
1647
+ // Emitted only for the opted-in policy, so evidence for every other
1648
+ // request keeps the shape it had before the seam existed.
1649
+ ...(validated.extraHeaderPolicy === "dropConflicting"
1650
+ ? { droppedExtraHeaderNames: headerPlan.droppedExtraHeaderNames }
1651
+ : {}),
1652
+ // Emitted only when suppression actually removed something, so
1653
+ // evidence for every request that ignores the seam is unchanged.
1654
+ ...(composedBetas.suppressedBetaNames.length === 0
1655
+ ? {}
1656
+ : { suppressedBetaNames: composedBetas.suppressedBetaNames }),
1657
+ // Emitted only when the billing block was actually removed, so the
1658
+ // audit records the attribution decision without inventing a key for
1659
+ // every request that leaves the canonical prefix intact.
1660
+ ...(validated.suppressBillingBlock
1661
+ ? { billingBlockSuppressed: true }
1662
+ : {}),
1663
+ // Emitted only when the identity block was actually removed, on the
1664
+ // same terms as `billingBlockSuppressed`: the parser reads both flags
1665
+ // to know the length of the canonical prefix it must verify.
1666
+ ...(validated.suppressIdentityBlock
1667
+ ? { identityBlockSuppressed: true }
1668
+ : {}),
1669
+ // Emitted only when the seam was active AND a reasoning block actually
1670
+ // carried the marker. Opting in without using it records nothing, so
1671
+ // the audit states what happened on the wire rather than what the
1672
+ // caller was permitted to do.
1673
+ ...(validated.preserveThinkingBlockCacheControl &&
1674
+ hasThinkingBlockCacheControl(canonicalBody["messages"])
1675
+ ? { thinkingBlockCacheControlPreserved: true }
1676
+ : {}),
1677
+ },
1678
+ validated.crypto,
1679
+ );
1680
+ return deepFreeze({
1681
+ url: effectiveProfile.endpoint,
1682
+ method: METHOD,
1683
+ headers,
1684
+ body,
1685
+ evidence,
1686
+ });
1687
+ } catch (error: unknown) {
1688
+ return sanitizeError(error);
1689
+ }
1690
+ }
1691
+
1692
+ /**
1693
+ * Validates and clones a previously built request into a deeply frozen value.
1694
+ *
1695
+ * @param profile - The only accepted value is the exported
1696
+ * `CLAUDE_CODE_2_1_195_PROFILE` singleton. Any other object, even a
1697
+ * structurally identical clone, is rejected with `ClaudeCodeWireError` code
1698
+ * `INVALID_INPUT`. This deliberate fail-closed behaviour prevents callers from
1699
+ * substituting an unpinned protocol profile.
1700
+ */
1701
+ export function parseBuiltClaudeCodeRequest(
1702
+ value: unknown,
1703
+ profile: ClaudeCodeProtocolProfile = DEFAULT_PROFILE,
1704
+ ): BuiltClaudeCodeRequest {
1705
+ try {
1706
+ const pinnedProfile = validateProfile(profile);
1707
+ inspectGraph(value);
1708
+ if (!isRecord(value)) fail();
1709
+ assertExactKeys(value, BUILT_KEYS);
1710
+ if (
1711
+ ownValue(value, "url") !== pinnedProfile.endpoint ||
1712
+ ownValue(value, "method") !== METHOD
1713
+ ) {
1714
+ fail();
1715
+ }
1716
+ const body = ownValue(value, "body");
1717
+ if (typeof body !== "string") fail();
1718
+ const parsedBody = parseBody(body);
1719
+ const headers = parseHeaders(ownValue(value, "headers"));
1720
+ const evidence = parseEvidence(ownValue(value, "evidence"), pinnedProfile);
1721
+ // Reading evidence is not trusting evidence. A claim that the seam
1722
+ // preserved a marker is confirmed against the body, and it is confirmed
1723
+ // HERE — before the byte-length and digest checks — so that a forgery which
1724
+ // is byte-length preserving and evidence-self-consistent is refused by the
1725
+ // structural check rather than incidentally by arithmetic.
1726
+ if (
1727
+ evidence.thinkingBlockCacheControlPreserved === true &&
1728
+ !hasThinkingBlockCacheControl(parsedBody["messages"])
1729
+ ) {
1730
+ fail();
1731
+ }
1732
+ const sessionId = headerValue(headers, "x-claude-code-session-id");
1733
+ const additionalHeaders = splitDynamicAndExtraHeaders(headers);
1734
+ const expectedHeaders = buildOrderedHeaders({
1735
+ accessToken: headerValue(headers, "authorization").replace(
1736
+ /^Bearer /u,
1737
+ "",
1738
+ ),
1739
+ runtime: {
1740
+ sessionId,
1741
+ runtime: headerValue(headers, "x-stainless-runtime"),
1742
+ runtimeVersion: headerValue(headers, "x-stainless-runtime-version"),
1743
+ os: headerValue(headers, "x-stainless-os"),
1744
+ arch: headerValue(headers, "x-stainless-arch"),
1745
+ },
1746
+ clientRequestId: headerValue(headers, "x-client-request-id"),
1747
+ betaFeatures: evidence.betaFeatures,
1748
+ app: headerValue(headers, "x-app"),
1749
+ stainlessRetryCount: Number(
1750
+ headerValue(headers, "x-stainless-retry-count"),
1751
+ ),
1752
+ stainlessHelper: additionalHeaders.stainlessHelper,
1753
+ claudeRemoteContainerId: additionalHeaders.claudeRemoteContainerId,
1754
+ claudeRemoteSessionId: additionalHeaders.claudeRemoteSessionId,
1755
+ clientApp: additionalHeaders.clientApp,
1756
+ anthropicAdditionalProtection:
1757
+ additionalHeaders.anthropicAdditionalProtection,
1758
+ extraHeaders: additionalHeaders.extraHeaders,
1759
+ profile: pinnedProfile,
1760
+ });
1761
+ if (JSON.stringify(headers) !== JSON.stringify(expectedHeaders)) fail();
1762
+ if (
1763
+ evidence.logicalHeaderNames.length !== headers.length ||
1764
+ evidence.logicalHeaderNames.some(
1765
+ (name, index) => name !== headers[index]?.[0],
1766
+ ) ||
1767
+ parsedBodySessionId(parsedBody) !== sessionId ||
1768
+ headerValue(headers, "anthropic-beta") !==
1769
+ evidence.betaFeatures.join(",") ||
1770
+ evidence.bodyByteLength !== new TextEncoder().encode(body).byteLength ||
1771
+ evidence.messageCount !==
1772
+ (Array.isArray(parsedBody["messages"])
1773
+ ? parsedBody["messages"].length
1774
+ : -1) ||
1775
+ evidence.systemBlockCount !==
1776
+ (Array.isArray(parsedBody["system"])
1777
+ ? parsedBody["system"].length -
1778
+ canonicalSystemPrefixLength(
1779
+ parsedBody["system"],
1780
+ evidence.billingBlockSuppressed === true,
1781
+ evidence.identityBlockSuppressed === true,
1782
+ )
1783
+ : -1)
1784
+ ) {
1785
+ fail();
1786
+ }
1787
+ const result: BuiltClaudeCodeRequest = {
1788
+ url: pinnedProfile.endpoint,
1789
+ method: METHOD,
1790
+ headers,
1791
+ body,
1792
+ evidence,
1793
+ };
1794
+ if (sha256Hex(body) !== evidence.bodySha256) fail();
1795
+ return deepFreeze(result);
1796
+ } catch (error: unknown) {
1797
+ return sanitizeError(error);
1798
+ }
1799
+ }