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