@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,1924 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+ import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
4
+ import { clampMaxTokens, resolveThinking } from "./thinking.js";
5
+ import type { ThinkingDisplay, ThinkingRequest } from "./thinking.js";
6
+
7
+ import { ClaudeCodeWireError } from "./contracts.js";
8
+ import type {
9
+ CacheControlEphemeral,
10
+ ClaudeCodeCacheControlInput,
11
+ ClaudeCodeProtocolProfile,
12
+ CitationsConfigParam,
13
+ DocumentBlock,
14
+ ImageBlock,
15
+ JsonValue,
16
+ Message,
17
+ MessageContentBlock,
18
+ RedactedThinkingBlock,
19
+ SearchResultBlock,
20
+ TextBlock,
21
+ TextCitationParam,
22
+ ThinkingBlock,
23
+ ToolDefinition,
24
+ ToolReferenceBlock,
25
+ ToolResultBlock,
26
+ ToolResultContentBlock,
27
+ ToolUseBlock,
28
+ } from "./contracts.js";
29
+ import { deriveCapabilities } from "./model-capabilities.js";
30
+ import { stripModelMarkers } from "./model-identity.js";
31
+ import { IDENTITY_TEXT } from "./system-prompt.js";
32
+ import { classifySurrogateAt } from "./unicode.js";
33
+
34
+ const MAX_DEPTH = 100;
35
+ const MAX_ITEMS = 100_000;
36
+ const MAX_SIZE = 1_000_000;
37
+ const FORBIDDEN_KEYS = new Set(["__proto__", "prototype", "constructor"]);
38
+ const MESSAGE_KEYS = new Set(["role", "content"]);
39
+ const CACHE_CONTROL_KEYS = new Set(["type", "ttl"]);
40
+ const LEGACY_TEXT_CACHE_CONTROL_KEYS = new Set(["type", "ttl", "scope"]);
41
+ const CITATIONS_CONFIG_KEYS = new Set(["enabled"]);
42
+ const TEXT_KEYS = new Set(["text", "type", "cache_control", "citations"]);
43
+ const TOOL_USE_KEYS = new Set([
44
+ "id",
45
+ "input",
46
+ "name",
47
+ "type",
48
+ "cache_control",
49
+ "caller",
50
+ ]);
51
+ const TOOL_RESULT_KEYS = new Set([
52
+ "tool_use_id",
53
+ "type",
54
+ "cache_control",
55
+ "content",
56
+ "is_error",
57
+ ]);
58
+ const THINKING_BLOCK_KEYS = new Set(["signature", "thinking", "type"]);
59
+ const REDACTED_THINKING_BLOCK_KEYS = new Set(["data", "type"]);
60
+ /*
61
+ * The `preserveThinkingBlockCacheControl` allowlists. They grow by exactly ONE
62
+ * key over the strict sets above; every other key stays refused, so the seam
63
+ * widens the contract by the single field the API itself round-trips and by
64
+ * nothing else. See the seam's JSDoc on `ClaudeCodeRequestInput`.
65
+ */
66
+ const THINKING_BLOCK_CACHE_CONTROL_KEYS = new Set([
67
+ ...THINKING_BLOCK_KEYS,
68
+ "cache_control",
69
+ ]);
70
+ const REDACTED_THINKING_BLOCK_CACHE_CONTROL_KEYS = new Set([
71
+ ...REDACTED_THINKING_BLOCK_KEYS,
72
+ "cache_control",
73
+ ]);
74
+ const IMAGE_BLOCK_KEYS = new Set(["source", "type", "cache_control"]);
75
+ const BASE64_IMAGE_SOURCE_KEYS = new Set(["data", "media_type", "type"]);
76
+ const FILE_IMAGE_SOURCE_KEYS = new Set(["file_id", "type"]);
77
+ const URL_IMAGE_SOURCE_KEYS = new Set(["type", "url"]);
78
+ const DOCUMENT_BLOCK_KEYS = new Set([
79
+ "source",
80
+ "type",
81
+ "cache_control",
82
+ "citations",
83
+ "context",
84
+ "title",
85
+ ]);
86
+ const BASE64_PDF_SOURCE_KEYS = new Set(["data", "media_type", "type"]);
87
+ const CONTENT_BLOCK_SOURCE_KEYS = new Set(["content", "type"]);
88
+ const FILE_DOCUMENT_SOURCE_KEYS = new Set(["file_id", "type"]);
89
+ const PLAIN_TEXT_SOURCE_KEYS = new Set(["data", "media_type", "type"]);
90
+ const URL_PDF_SOURCE_KEYS = new Set(["type", "url"]);
91
+ const SEARCH_RESULT_KEYS = new Set([
92
+ "content",
93
+ "source",
94
+ "title",
95
+ "type",
96
+ "cache_control",
97
+ "citations",
98
+ ]);
99
+ const TOOL_REFERENCE_KEYS = new Set(["tool_name", "type", "cache_control"]);
100
+ const DIRECT_CALLER_KEYS = new Set(["type"]);
101
+ const SERVER_TOOL_CALLER_KEYS = new Set(["tool_id", "type"]);
102
+ const CITATION_CHAR_KEYS = new Set([
103
+ "cited_text",
104
+ "document_index",
105
+ "document_title",
106
+ "end_char_index",
107
+ "start_char_index",
108
+ "type",
109
+ ]);
110
+ const CITATION_CONTENT_BLOCK_KEYS = new Set([
111
+ "cited_text",
112
+ "document_index",
113
+ "document_title",
114
+ "end_block_index",
115
+ "start_block_index",
116
+ "type",
117
+ ]);
118
+ const CITATION_PAGE_KEYS = new Set([
119
+ "cited_text",
120
+ "document_index",
121
+ "document_title",
122
+ "end_page_number",
123
+ "start_page_number",
124
+ "type",
125
+ ]);
126
+ const CITATION_SEARCH_RESULT_KEYS = new Set([
127
+ "cited_text",
128
+ "end_block_index",
129
+ "search_result_index",
130
+ "source",
131
+ "start_block_index",
132
+ "title",
133
+ "type",
134
+ ]);
135
+ const CITATION_WEB_SEARCH_KEYS = new Set([
136
+ "cited_text",
137
+ "encrypted_index",
138
+ "title",
139
+ "type",
140
+ "url",
141
+ ]);
142
+ const CUSTOM_TOOL_KEYS = new Set([
143
+ "input_schema",
144
+ "name",
145
+ "allowed_callers",
146
+ "cache_control",
147
+ "defer_loading",
148
+ "description",
149
+ "eager_input_streaming",
150
+ "input_examples",
151
+ "strict",
152
+ ]);
153
+ const BASH_TOOL_KEYS = new Set([
154
+ "name",
155
+ "type",
156
+ "allowed_callers",
157
+ "cache_control",
158
+ "defer_loading",
159
+ "input_examples",
160
+ "strict",
161
+ ]);
162
+ const CODE_EXECUTION_TOOL_KEYS = new Set([
163
+ "name",
164
+ "type",
165
+ "allowed_callers",
166
+ "cache_control",
167
+ "defer_loading",
168
+ "strict",
169
+ ]);
170
+ const COMPUTER_TOOL_KEYS = new Set([
171
+ "display_height_px",
172
+ "display_width_px",
173
+ "name",
174
+ "type",
175
+ "allowed_callers",
176
+ "cache_control",
177
+ "defer_loading",
178
+ "display_number",
179
+ "input_examples",
180
+ "strict",
181
+ ]);
182
+ const COMPUTER_ZOOM_TOOL_KEYS = new Set([...COMPUTER_TOOL_KEYS, "enable_zoom"]);
183
+ const MEMORY_TOOL_KEYS = new Set([
184
+ "name",
185
+ "type",
186
+ "allowed_callers",
187
+ "cache_control",
188
+ "defer_loading",
189
+ "input_examples",
190
+ "strict",
191
+ ]);
192
+ const TEXT_EDITOR_TOOL_KEYS = new Set([
193
+ "name",
194
+ "type",
195
+ "allowed_callers",
196
+ "cache_control",
197
+ "defer_loading",
198
+ "input_examples",
199
+ "strict",
200
+ ]);
201
+ const TEXT_EDITOR_MAX_TOOL_KEYS = new Set([
202
+ ...TEXT_EDITOR_TOOL_KEYS,
203
+ "max_characters",
204
+ ]);
205
+ const WEB_SEARCH_TOOL_KEYS = new Set([
206
+ "name",
207
+ "type",
208
+ "allowed_callers",
209
+ "allowed_domains",
210
+ "blocked_domains",
211
+ "cache_control",
212
+ "defer_loading",
213
+ "max_uses",
214
+ "strict",
215
+ "user_location",
216
+ ]);
217
+ const USER_LOCATION_KEYS = new Set([
218
+ "type",
219
+ "city",
220
+ "country",
221
+ "region",
222
+ "timezone",
223
+ ]);
224
+ const WEB_FETCH_TOOL_KEYS = new Set([
225
+ "name",
226
+ "type",
227
+ "allowed_callers",
228
+ "allowed_domains",
229
+ "blocked_domains",
230
+ "cache_control",
231
+ "citations",
232
+ "defer_loading",
233
+ "max_content_tokens",
234
+ "max_uses",
235
+ "strict",
236
+ ]);
237
+ const WEB_FETCH_CACHE_TOOL_KEYS = new Set([
238
+ ...WEB_FETCH_TOOL_KEYS,
239
+ "use_cache",
240
+ ]);
241
+ const ADVISOR_TOOL_KEYS = new Set([
242
+ "model",
243
+ "name",
244
+ "type",
245
+ "allowed_callers",
246
+ "cache_control",
247
+ "caching",
248
+ "defer_loading",
249
+ "max_uses",
250
+ "strict",
251
+ ]);
252
+ const TOOL_SEARCH_KEYS = new Set([
253
+ "name",
254
+ "type",
255
+ "allowed_callers",
256
+ "cache_control",
257
+ "defer_loading",
258
+ "strict",
259
+ ]);
260
+ const MCP_TOOLSET_KEYS = new Set([
261
+ "mcp_server_name",
262
+ "type",
263
+ "cache_control",
264
+ "configs",
265
+ "default_config",
266
+ ]);
267
+ const MCP_TOOL_CONFIG_KEYS = new Set(["defer_loading", "enabled"]);
268
+ const CONTEXT_CONFIG_KEYS = new Set(["edits"]);
269
+ const CLEAR_THINKING_KEYS = new Set(["type", "keep"]);
270
+ const ALL_THINKING_KEYS = new Set(["type"]);
271
+ const TYPED_NUMBER_KEYS = new Set(["type", "value"]);
272
+ const CLEAR_TOOL_USES_KEYS = new Set([
273
+ "type",
274
+ "clear_at_least",
275
+ "clear_tool_inputs",
276
+ "exclude_tools",
277
+ "keep",
278
+ "trigger",
279
+ ]);
280
+ const COMPACT_KEYS = new Set([
281
+ "type",
282
+ "instructions",
283
+ "pause_after_compaction",
284
+ "trigger",
285
+ ]);
286
+ const OUTPUT_CONFIG_KEYS = new Set(["effort", "maxOutputTokens"]);
287
+ const CACHE_CONTROL_INPUT_KEYS = new Set([
288
+ "enabled",
289
+ "ttl",
290
+ "systemBreakpoint",
291
+ "toolBreakpoint",
292
+ "messageBreakpoint",
293
+ "suppressIdentityBlock",
294
+ ]);
295
+ const JSON_OUTPUT_FORMAT_KEYS = new Set(["schema", "type"]);
296
+ const TOOL_CHOICE_PARALLEL_KEYS = new Set([
297
+ "type",
298
+ "disable_parallel_tool_use",
299
+ ]);
300
+ const TOOL_CHOICE_NONE_KEYS = new Set(["type"]);
301
+ const TOOL_CHOICE_NAMED_KEYS = new Set([
302
+ "name",
303
+ "type",
304
+ "disable_parallel_tool_use",
305
+ ]);
306
+ const INPUT_KEYS = [
307
+ "accessToken",
308
+ "model",
309
+ "maxTokens",
310
+ "messages",
311
+ "system",
312
+ "tools",
313
+ "cacheControl",
314
+ "runtime",
315
+ "capabilities",
316
+ // Package extension: consumed by beta composition and evidence only. The
317
+ // canonical body carries no trace of it.
318
+ "betaOverrides",
319
+ // Package extension: widens the thinking-block allowlist by `cache_control`
320
+ // alone. The canonical body carries no trace of the FLAG; what it carries is
321
+ // the caller's own `cache_control`, verbatim.
322
+ "preserveThinkingBlockCacheControl",
323
+ "thinking",
324
+ "effort",
325
+ "metadata",
326
+ "experimentalBodyFields",
327
+ "contextManagement",
328
+ "outputConfig",
329
+ "speed",
330
+ "serviceTier",
331
+ "outputFormat",
332
+ "toolChoice",
333
+ "topP",
334
+ "topK",
335
+ "stopSequences",
336
+ "stream",
337
+ "temperature",
338
+ ] as const;
339
+ const INPUT_KEY_SET = new Set(INPUT_KEYS);
340
+
341
+ interface InspectionState {
342
+ readonly active: WeakSet<object>;
343
+ items: number;
344
+ size: number;
345
+ }
346
+
347
+ type ModelResolution = Readonly<{
348
+ id: string;
349
+ wireId: string;
350
+ capabilities: Readonly<{
351
+ thinking: boolean;
352
+ adaptiveThinking: boolean;
353
+ interleavedThinking: boolean;
354
+ effort: boolean;
355
+ maxEffort: boolean;
356
+ xhighEffort: boolean;
357
+ contextManagement: boolean;
358
+ temperature: boolean;
359
+ rejectsDisabledThinking: boolean;
360
+ }>;
361
+ }>;
362
+
363
+ function fail(
364
+ code: ConstructorParameters<typeof ClaudeCodeWireError>[0],
365
+ ): never {
366
+ throw new ClaudeCodeWireError(code);
367
+ }
368
+
369
+ function isRecord(value: unknown): value is Record<string, unknown> {
370
+ return typeof value === "object" && value !== null && !Array.isArray(value);
371
+ }
372
+
373
+ function hasOwn(value: object, key: string): boolean {
374
+ return Object.prototype.hasOwnProperty.call(value, key);
375
+ }
376
+
377
+ function inspectString(
378
+ value: string,
379
+ state: InspectionState,
380
+ validateString?: (value: string) => void,
381
+ ): void {
382
+ state.size += value.length;
383
+ if (state.size > MAX_SIZE) fail("INPUT_TOO_LARGE");
384
+ validateString?.(value);
385
+
386
+ for (let index = 0; index < value.length; index += 1) {
387
+ const unit = value.charCodeAt(index);
388
+ if (
389
+ unit <= 0x08 ||
390
+ unit === 0x0b ||
391
+ unit === 0x0c ||
392
+ (unit >= 0x0e && unit <= 0x1f) ||
393
+ unit === 0x7f
394
+ ) {
395
+ fail("INVALID_INPUT");
396
+ }
397
+ const classification = classifySurrogateAt(value, index);
398
+ if (classification === "loneSurrogate") fail("INVALID_UNICODE");
399
+ if (classification === "surrogatePair") index += 1;
400
+ }
401
+ }
402
+
403
+ function inspect(
404
+ value: unknown,
405
+ depth: number,
406
+ state: InspectionState,
407
+ validateString?: (value: string) => void,
408
+ ): void {
409
+ if (depth > MAX_DEPTH) fail("INPUT_TOO_DEEP");
410
+ if (value === null || typeof value === "boolean") return;
411
+ if (typeof value === "string") {
412
+ inspectString(value, state, validateString);
413
+ return;
414
+ }
415
+ if (typeof value === "number") {
416
+ if (!Number.isFinite(value)) fail("INVALID_INPUT");
417
+ return;
418
+ }
419
+ if (typeof value !== "object") fail("INVALID_INPUT");
420
+
421
+ if (state.active.has(value)) fail("CYCLIC_INPUT");
422
+ state.active.add(value);
423
+ state.items += 1;
424
+ if (state.items > MAX_ITEMS) fail("INPUT_TOO_LARGE");
425
+
426
+ if (Array.isArray(value)) {
427
+ state.size += value.length;
428
+ if (state.size > MAX_SIZE) fail("INPUT_TOO_LARGE");
429
+ for (let index = 0; index < value.length; index += 1) {
430
+ if (!hasOwn(value, String(index))) fail("INVALID_INPUT");
431
+ inspect(value[index], depth + 1, state, validateString);
432
+ }
433
+ } else {
434
+ const prototype = Reflect.getPrototypeOf(value);
435
+ if (prototype !== Object.prototype && prototype !== null) {
436
+ fail("INVALID_INPUT");
437
+ }
438
+ for (const key of Reflect.ownKeys(value)) {
439
+ if (typeof key !== "string" || FORBIDDEN_KEYS.has(key)) {
440
+ fail("INVALID_INPUT");
441
+ }
442
+ inspectString(key, state, validateString);
443
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
444
+ if (descriptor === undefined || !("value" in descriptor)) {
445
+ fail("INVALID_INPUT");
446
+ }
447
+ inspect(descriptor.value, depth + 1, state, validateString);
448
+ }
449
+ }
450
+ state.active.delete(value);
451
+ }
452
+
453
+ export function inspectJsonInputs(
454
+ values: readonly unknown[],
455
+ validateString?: (value: string) => void,
456
+ ): void {
457
+ const state: InspectionState = {
458
+ active: new WeakSet(),
459
+ items: 0,
460
+ size: 0,
461
+ };
462
+ for (const value of values) inspect(value, 0, state, validateString);
463
+ }
464
+
465
+ function requireRecord(value: unknown): Record<string, unknown> {
466
+ if (!isRecord(value)) fail("INVALID_INPUT");
467
+ return value;
468
+ }
469
+
470
+ function assertExactKeys(
471
+ value: Readonly<Record<string, unknown>>,
472
+ allowed: ReadonlySet<string>,
473
+ ): void {
474
+ for (const key of Reflect.ownKeys(value)) {
475
+ if (typeof key !== "string" || !allowed.has(key)) fail("INVALID_INPUT");
476
+ }
477
+ }
478
+
479
+ function requireString(value: unknown): string {
480
+ if (typeof value !== "string") fail("INVALID_INPUT");
481
+ return value;
482
+ }
483
+
484
+ function requirePositiveInteger(value: unknown): number {
485
+ if (!Number.isSafeInteger(value) || typeof value !== "number" || value <= 0) {
486
+ fail("INVALID_INPUT");
487
+ }
488
+ return value;
489
+ }
490
+
491
+ export function validatedJsonObject(
492
+ value: unknown,
493
+ ): Readonly<Record<string, JsonValue>> {
494
+ const record = requireRecord(value);
495
+ const entries: [string, JsonValue][] = [];
496
+ for (const key of Object.keys(record)) {
497
+ entries.push([key, validatedJson(record[key])]);
498
+ }
499
+ return Object.fromEntries(entries);
500
+ }
501
+
502
+ export function validatedJson(value: unknown): JsonValue {
503
+ if (
504
+ value === null ||
505
+ typeof value === "string" ||
506
+ typeof value === "number" ||
507
+ typeof value === "boolean"
508
+ ) {
509
+ return value;
510
+ }
511
+ if (Array.isArray(value)) return value.map((item) => validatedJson(item));
512
+ return validatedJsonObject(value);
513
+ }
514
+
515
+ function requireNumber(value: unknown): number {
516
+ if (typeof value !== "number" || !Number.isFinite(value))
517
+ fail("INVALID_INPUT");
518
+ return value;
519
+ }
520
+
521
+ function requireBoolean(value: unknown): boolean {
522
+ if (typeof value !== "boolean") fail("INVALID_INPUT");
523
+ return value;
524
+ }
525
+
526
+ function requireKeys(
527
+ record: Record<string, unknown>,
528
+ required: readonly string[],
529
+ ): void {
530
+ for (const key of required) {
531
+ if (!hasOwn(record, key)) fail("INVALID_INPUT");
532
+ }
533
+ }
534
+
535
+ function nullable<T>(value: unknown, validate: (item: unknown) => T): T | null {
536
+ return value === null ? null : validate(value);
537
+ }
538
+
539
+ function cacheControl(
540
+ value: unknown,
541
+ allowScope = false,
542
+ ): CacheControlEphemeral {
543
+ const record = requireRecord(value);
544
+ assertExactKeys(
545
+ record,
546
+ allowScope ? LEGACY_TEXT_CACHE_CONTROL_KEYS : CACHE_CONTROL_KEYS,
547
+ );
548
+ requireKeys(record, ["type"]);
549
+ const entries: [string, unknown][] = [];
550
+ for (const key of Object.keys(record)) {
551
+ const item = record[key];
552
+ if (key === "type") {
553
+ if (item !== "ephemeral") fail("INVALID_INPUT");
554
+ entries.push([key, item]);
555
+ } else if (key === "ttl") {
556
+ if (item !== "5m" && item !== "1h") fail("INVALID_INPUT");
557
+ entries.push([key, item]);
558
+ } else {
559
+ if (item !== "global") fail("INVALID_INPUT");
560
+ entries.push([key, item]);
561
+ }
562
+ }
563
+ return Object.fromEntries(entries) as unknown as CacheControlEphemeral;
564
+ }
565
+
566
+ function cacheControlInput(value: unknown): ClaudeCodeCacheControlInput {
567
+ const record = requireRecord(value);
568
+ assertExactKeys(record, CACHE_CONTROL_INPUT_KEYS);
569
+ const entries: [string, boolean | "5m" | "1h" | null][] = [];
570
+ for (const key of Object.keys(record)) {
571
+ const item = record[key];
572
+ if (key === "ttl") {
573
+ if (item !== null && item !== "5m" && item !== "1h") {
574
+ fail("INVALID_INPUT");
575
+ }
576
+ entries.push([key, item]);
577
+ } else {
578
+ entries.push([key, item === null ? null : requireBoolean(item)]);
579
+ }
580
+ }
581
+ return Object.fromEntries(entries);
582
+ }
583
+
584
+ function breakpoint(input: ClaudeCodeCacheControlInput): CacheControlEphemeral {
585
+ return input.ttl === undefined || input.ttl === null
586
+ ? { type: "ephemeral" }
587
+ : { type: "ephemeral", ttl: input.ttl };
588
+ }
589
+
590
+ function withoutCacheControl<T>(value: T): T {
591
+ if (!isRecord(value)) fail("INVALID_INPUT");
592
+ return Object.fromEntries(
593
+ Object.entries(value).filter(([key]) => key !== "cache_control"),
594
+ ) as unknown as T;
595
+ }
596
+
597
+ function withBreakpoint<T>(value: T, marker: CacheControlEphemeral): T {
598
+ if (!isRecord(value)) fail("INVALID_INPUT");
599
+ return Object.fromEntries([
600
+ ...Object.entries(value).filter(([key]) => key !== "cache_control"),
601
+ ["cache_control", marker],
602
+ ]) as unknown as T;
603
+ }
604
+
605
+ function applySystemCacheControl(
606
+ value: readonly TextBlock[],
607
+ input: ClaudeCodeCacheControlInput,
608
+ ): readonly TextBlock[] {
609
+ // The identity block sits at index 1 unless `suppressBillingBlock` removed
610
+ // the billing block, which promotes it to index 0. Matching on the pinned
611
+ // text keeps both layouts correct without threading the seam down here.
612
+ const identityIndex = value.findIndex(
613
+ (block) => block.text === IDENTITY_TEXT,
614
+ );
615
+ // Package extension: `suppressIdentityBlock` is the only way to emit the
616
+ // identity block without a marker. Default (`undefined`/`false`) keeps the
617
+ // unconditional overwrite the genuine client performs.
618
+ const result = value.map((block, index) => {
619
+ if (index !== identityIndex) return block;
620
+ return input.suppressIdentityBlock === true
621
+ ? withoutCacheControl(block)
622
+ : withBreakpoint(block, breakpoint(input));
623
+ });
624
+ if (
625
+ input.enabled === true &&
626
+ input.systemBreakpoint === true &&
627
+ result.length > identityIndex + 1
628
+ ) {
629
+ const index = result.length - 1;
630
+ const block = result[index];
631
+ if (block !== undefined)
632
+ result[index] = withBreakpoint(block, breakpoint(input));
633
+ }
634
+ return result;
635
+ }
636
+
637
+ /**
638
+ * Normalises tool `cache_control` when caching is enabled.
639
+ *
640
+ * The strip is gated on `enabled === true`, exactly like the re-add below it.
641
+ * It used to be unconditional, which made any OTHER member of
642
+ * `ClaudeCodeCacheControlInput` destructive: passing
643
+ * `{ suppressIdentityBlock: true }` — the S3 seam on its own — deleted every
644
+ * `cache_control` the caller had placed on its tools and restored nothing.
645
+ *
646
+ * When caching IS enabled the caller's own breakpoints are still normalised
647
+ * away, because this package owns breakpoint placement in that mode and two
648
+ * competing sets of breakpoints cannot both be honoured.
649
+ */
650
+ function applyToolCacheControl(
651
+ value: readonly ToolDefinition[],
652
+ input: ClaudeCodeCacheControlInput,
653
+ ): readonly ToolDefinition[] {
654
+ if (input.enabled !== true) return value;
655
+ const result = value.map((tool) => withoutCacheControl(tool));
656
+ if (input.toolBreakpoint === true && result.length > 0) {
657
+ const index = result.length - 1;
658
+ const tool = result[index];
659
+ if (tool !== undefined)
660
+ result[index] = withBreakpoint(tool, breakpoint(input));
661
+ }
662
+ return result;
663
+ }
664
+
665
+ /**
666
+ * Normalises message `cache_control` when caching is enabled.
667
+ *
668
+ * Gated on `enabled === true` for the same reason as `applyToolCacheControl`:
669
+ * the strip used to run unconditionally, so a caller populating any other
670
+ * member of `ClaudeCodeCacheControlInput` silently lost the `cache_control` it
671
+ * had placed on its own message blocks.
672
+ */
673
+ function applyMessageCacheControl(
674
+ value: readonly Message[],
675
+ input: ClaudeCodeCacheControlInput,
676
+ ): readonly Message[] {
677
+ if (input.enabled !== true) return value;
678
+ const result = value.map((message): Message => ({
679
+ role: message.role,
680
+ content:
681
+ typeof message.content === "string"
682
+ ? message.content
683
+ : message.content.map((block) =>
684
+ block.type === "thinking" || block.type === "redacted_thinking"
685
+ ? block
686
+ : withoutCacheControl(block),
687
+ ),
688
+ }));
689
+ if (input.messageBreakpoint !== true) return result;
690
+
691
+ for (let index = result.length - 1; index >= 0; index -= 1) {
692
+ const message = result[index];
693
+ if (message?.role !== "user") continue;
694
+ if (typeof message.content === "string" || message.content.length === 0) {
695
+ return result;
696
+ }
697
+ const content: MessageContentBlock[] = [...message.content];
698
+ const blockIndex = content.length - 1;
699
+ const block = content[blockIndex];
700
+ if (
701
+ block !== undefined &&
702
+ block.type !== "thinking" &&
703
+ block.type !== "redacted_thinking"
704
+ ) {
705
+ content[blockIndex] = withBreakpoint(block, breakpoint(input));
706
+ result[index] = { role: message.role, content };
707
+ }
708
+ return result;
709
+ }
710
+ return result;
711
+ }
712
+
713
+ function citationsConfig(value: unknown): CitationsConfigParam {
714
+ const record = requireRecord(value);
715
+ assertExactKeys(record, CITATIONS_CONFIG_KEYS);
716
+ requireKeys(record, []);
717
+ return Object.fromEntries(
718
+ Object.keys(record).map((key) => [key, requireBoolean(record[key])]),
719
+ );
720
+ }
721
+
722
+ function textCitation(value: unknown): TextCitationParam {
723
+ const record = requireRecord(value);
724
+ const type = record["type"];
725
+ let allowed: ReadonlySet<string>;
726
+ let numbers: readonly string[];
727
+ let nullableStrings: readonly string[];
728
+ if (type === "char_location") {
729
+ allowed = CITATION_CHAR_KEYS;
730
+ numbers = ["document_index", "end_char_index", "start_char_index"];
731
+ nullableStrings = ["document_title"];
732
+ } else if (type === "content_block_location") {
733
+ allowed = CITATION_CONTENT_BLOCK_KEYS;
734
+ numbers = ["document_index", "end_block_index", "start_block_index"];
735
+ nullableStrings = ["document_title"];
736
+ } else if (type === "page_location") {
737
+ allowed = CITATION_PAGE_KEYS;
738
+ numbers = ["document_index", "end_page_number", "start_page_number"];
739
+ nullableStrings = ["document_title"];
740
+ } else if (type === "search_result_location") {
741
+ allowed = CITATION_SEARCH_RESULT_KEYS;
742
+ numbers = ["end_block_index", "search_result_index", "start_block_index"];
743
+ nullableStrings = ["title"];
744
+ } else if (type === "web_search_result_location") {
745
+ allowed = CITATION_WEB_SEARCH_KEYS;
746
+ numbers = [];
747
+ nullableStrings = ["title"];
748
+ } else {
749
+ return fail("INVALID_INPUT");
750
+ }
751
+ assertExactKeys(record, allowed);
752
+ requireKeys(record, [...allowed]);
753
+ const entries: [string, unknown][] = [];
754
+ for (const key of Object.keys(record)) {
755
+ const item = record[key];
756
+ if (key === "type") entries.push([key, type]);
757
+ else if (numbers.includes(key)) entries.push([key, requireNumber(item)]);
758
+ else if (nullableStrings.includes(key))
759
+ entries.push([key, nullable(item, requireString)]);
760
+ else entries.push([key, requireString(item)]);
761
+ }
762
+ return Object.fromEntries(entries) as unknown as TextCitationParam;
763
+ }
764
+
765
+ function textBlock(value: unknown): TextBlock {
766
+ const record = requireRecord(value);
767
+ assertExactKeys(record, TEXT_KEYS);
768
+ requireKeys(record, ["text", "type"]);
769
+ if (record["type"] !== "text") fail("INVALID_INPUT");
770
+ const entries: [string, unknown][] = [];
771
+ for (const key of Object.keys(record)) {
772
+ const item = record[key];
773
+ if (key === "text") entries.push([key, requireString(item)]);
774
+ else if (key === "type") entries.push([key, "text"]);
775
+ else if (key === "cache_control")
776
+ entries.push([key, nullable(item, (raw) => cacheControl(raw, true))]);
777
+ else {
778
+ if (item === null) entries.push([key, null]);
779
+ else {
780
+ if (!Array.isArray(item)) fail("INVALID_INPUT");
781
+ entries.push([key, item.map((citation) => textCitation(citation))]);
782
+ }
783
+ }
784
+ }
785
+ return Object.fromEntries(entries) as unknown as TextBlock;
786
+ }
787
+
788
+ function imageBlock(value: unknown): ImageBlock {
789
+ const record = requireRecord(value);
790
+ assertExactKeys(record, IMAGE_BLOCK_KEYS);
791
+ requireKeys(record, ["source", "type"]);
792
+ if (record["type"] !== "image") fail("INVALID_INPUT");
793
+ const entries: [string, unknown][] = [];
794
+ for (const key of Object.keys(record)) {
795
+ const item = record[key];
796
+ if (key === "type") entries.push([key, "image"]);
797
+ else if (key === "cache_control")
798
+ entries.push([key, nullable(item, cacheControl)]);
799
+ else entries.push([key, imageSource(item)]);
800
+ }
801
+ return Object.fromEntries(entries) as unknown as ImageBlock;
802
+ }
803
+
804
+ function imageSource(value: unknown): ImageBlock["source"] {
805
+ const record = requireRecord(value);
806
+ const type = record["type"];
807
+ const allowed =
808
+ type === "base64"
809
+ ? BASE64_IMAGE_SOURCE_KEYS
810
+ : type === "file"
811
+ ? FILE_IMAGE_SOURCE_KEYS
812
+ : type === "url"
813
+ ? URL_IMAGE_SOURCE_KEYS
814
+ : fail("INVALID_INPUT");
815
+ assertExactKeys(record, allowed);
816
+ requireKeys(record, [...allowed]);
817
+ const entries: [string, unknown][] = [];
818
+ for (const key of Object.keys(record)) {
819
+ const item = record[key];
820
+ if (key === "type") entries.push([key, type]);
821
+ else if (key === "media_type") {
822
+ if (
823
+ item !== "image/jpeg" &&
824
+ item !== "image/png" &&
825
+ item !== "image/gif" &&
826
+ item !== "image/webp"
827
+ )
828
+ fail("INVALID_INPUT");
829
+ entries.push([key, item]);
830
+ } else entries.push([key, requireString(item)]);
831
+ }
832
+ return Object.fromEntries(entries) as unknown as ImageBlock["source"];
833
+ }
834
+
835
+ function documentBlock(value: unknown): DocumentBlock {
836
+ const record = requireRecord(value);
837
+ assertExactKeys(record, DOCUMENT_BLOCK_KEYS);
838
+ requireKeys(record, ["source", "type"]);
839
+ if (record["type"] !== "document") fail("INVALID_INPUT");
840
+ const entries: [string, unknown][] = [];
841
+ for (const key of Object.keys(record)) {
842
+ const item = record[key];
843
+ if (key === "source") entries.push([key, documentSource(item)]);
844
+ else if (key === "type") entries.push([key, "document"]);
845
+ else if (key === "cache_control")
846
+ entries.push([key, nullable(item, cacheControl)]);
847
+ else if (key === "citations")
848
+ entries.push([key, nullable(item, citationsConfig)]);
849
+ else entries.push([key, nullable(item, requireString)]);
850
+ }
851
+ return Object.fromEntries(entries) as unknown as DocumentBlock;
852
+ }
853
+
854
+ function documentSource(value: unknown): DocumentBlock["source"] {
855
+ const record = requireRecord(value);
856
+ const type = record["type"];
857
+ let allowed: ReadonlySet<string>;
858
+ if (type === "base64") allowed = BASE64_PDF_SOURCE_KEYS;
859
+ else if (type === "text") allowed = PLAIN_TEXT_SOURCE_KEYS;
860
+ else if (type === "content") allowed = CONTENT_BLOCK_SOURCE_KEYS;
861
+ else if (type === "url") allowed = URL_PDF_SOURCE_KEYS;
862
+ else if (type === "file") allowed = FILE_DOCUMENT_SOURCE_KEYS;
863
+ else return fail("INVALID_INPUT");
864
+ assertExactKeys(record, allowed);
865
+ requireKeys(record, [...allowed]);
866
+ const entries: [string, unknown][] = [];
867
+ for (const key of Object.keys(record)) {
868
+ const item = record[key];
869
+ if (key === "type") entries.push([key, type]);
870
+ else if (key === "media_type") {
871
+ if (
872
+ (type === "base64" && item !== "application/pdf") ||
873
+ (type === "text" && item !== "text/plain")
874
+ )
875
+ fail("INVALID_INPUT");
876
+ entries.push([key, item]);
877
+ } else if (key === "content") {
878
+ if (typeof item === "string") entries.push([key, item]);
879
+ else {
880
+ if (!Array.isArray(item)) fail("INVALID_INPUT");
881
+ entries.push([
882
+ key,
883
+ item.map((block) => {
884
+ const blockRecord = requireRecord(block);
885
+ if (blockRecord["type"] === "text") return textBlock(blockRecord);
886
+ if (blockRecord["type"] === "image") return imageBlock(blockRecord);
887
+ return fail("INVALID_INPUT");
888
+ }),
889
+ ]);
890
+ }
891
+ } else entries.push([key, requireString(item)]);
892
+ }
893
+ return Object.fromEntries(entries) as unknown as DocumentBlock["source"];
894
+ }
895
+
896
+ /**
897
+ * Validates a `thinking` block.
898
+ *
899
+ * @param preserveCacheControl - Opt-in from
900
+ * `ClaudeCodeRequestInput.preserveThinkingBlockCacheControl`. When `true`,
901
+ * `cache_control` becomes an accepted key and is copied to the body VERBATIM:
902
+ * no TTL is applied, no breakpoint is placed, and `applyMessageCacheControl`
903
+ * already leaves thinking blocks untouched. The value still passes the same
904
+ * `cacheControl` validator every other block uses, so a malformed marker fails
905
+ * closed. Default `false` reproduces the strict allowlist byte for byte.
906
+ */
907
+ function thinkingBlock(
908
+ value: unknown,
909
+ preserveCacheControl: boolean,
910
+ ): ThinkingBlock {
911
+ const record = requireRecord(value);
912
+ assertExactKeys(
913
+ record,
914
+ preserveCacheControl
915
+ ? THINKING_BLOCK_CACHE_CONTROL_KEYS
916
+ : THINKING_BLOCK_KEYS,
917
+ );
918
+ requireKeys(record, ["signature", "thinking", "type"]);
919
+ if (record["type"] !== "thinking") fail("INVALID_INPUT");
920
+ const entries: [string, unknown][] = [];
921
+ for (const key of Object.keys(record)) {
922
+ const item = record[key];
923
+ if (key === "type") entries.push([key, "thinking"]);
924
+ else if (key === "cache_control")
925
+ entries.push([key, nullable(item, cacheControl)]);
926
+ else entries.push([key, requireString(item)]);
927
+ }
928
+ return Object.fromEntries(entries) as unknown as ThinkingBlock;
929
+ }
930
+
931
+ /** Validates a `redacted_thinking` block. See `thinkingBlock` for the seam. */
932
+ function redactedThinkingBlock(
933
+ value: unknown,
934
+ preserveCacheControl: boolean,
935
+ ): RedactedThinkingBlock {
936
+ const record = requireRecord(value);
937
+ assertExactKeys(
938
+ record,
939
+ preserveCacheControl
940
+ ? REDACTED_THINKING_BLOCK_CACHE_CONTROL_KEYS
941
+ : REDACTED_THINKING_BLOCK_KEYS,
942
+ );
943
+ requireKeys(record, ["data", "type"]);
944
+ if (record["type"] !== "redacted_thinking") fail("INVALID_INPUT");
945
+ const entries: [string, unknown][] = [];
946
+ for (const key of Object.keys(record)) {
947
+ const item = record[key];
948
+ if (key === "type") entries.push([key, "redacted_thinking"]);
949
+ else if (key === "cache_control")
950
+ entries.push([key, nullable(item, cacheControl)]);
951
+ else entries.push([key, requireString(item)]);
952
+ }
953
+ return Object.fromEntries(entries) as unknown as RedactedThinkingBlock;
954
+ }
955
+
956
+ function searchResultBlock(value: unknown): SearchResultBlock {
957
+ const record = requireRecord(value);
958
+ assertExactKeys(record, SEARCH_RESULT_KEYS);
959
+ requireKeys(record, ["content", "source", "title", "type"]);
960
+ if (record["type"] !== "search_result") fail("INVALID_INPUT");
961
+ const entries: [string, unknown][] = [];
962
+ for (const key of Object.keys(record)) {
963
+ const item = record[key];
964
+ if (key === "content") {
965
+ if (!Array.isArray(item)) fail("INVALID_INPUT");
966
+ entries.push([key, item.map((block) => textBlock(block))]);
967
+ } else if (key === "source" || key === "title")
968
+ entries.push([key, requireString(item)]);
969
+ else if (key === "type") entries.push([key, "search_result"]);
970
+ else if (key === "cache_control")
971
+ entries.push([key, nullable(item, cacheControl)]);
972
+ else entries.push([key, citationsConfig(item)]);
973
+ }
974
+ return Object.fromEntries(entries) as unknown as SearchResultBlock;
975
+ }
976
+
977
+ function toolReferenceBlock(value: unknown): ToolReferenceBlock {
978
+ const record = requireRecord(value);
979
+ assertExactKeys(record, TOOL_REFERENCE_KEYS);
980
+ requireKeys(record, ["tool_name", "type"]);
981
+ if (record["type"] !== "tool_reference") fail("INVALID_INPUT");
982
+ const entries: [string, unknown][] = [];
983
+ for (const key of Object.keys(record)) {
984
+ const item = record[key];
985
+ if (key === "tool_name") entries.push([key, requireString(item)]);
986
+ else if (key === "type") entries.push([key, "tool_reference"]);
987
+ else entries.push([key, nullable(item, cacheControl)]);
988
+ }
989
+ return Object.fromEntries(entries) as unknown as ToolReferenceBlock;
990
+ }
991
+
992
+ function toolUseBlock(value: unknown): ToolUseBlock {
993
+ const record = requireRecord(value);
994
+ assertExactKeys(record, TOOL_USE_KEYS);
995
+ requireKeys(record, ["id", "input", "name", "type"]);
996
+ if (record["type"] !== "tool_use") fail("INVALID_INPUT");
997
+ const entries: [string, unknown][] = [];
998
+ for (const key of Object.keys(record)) {
999
+ const item = record[key];
1000
+ if (key === "id" || key === "name")
1001
+ entries.push([key, requireString(item)]);
1002
+ else if (key === "input") entries.push([key, validatedJson(item)]);
1003
+ else if (key === "type") entries.push([key, "tool_use"]);
1004
+ else if (key === "cache_control")
1005
+ entries.push([key, nullable(item, cacheControl)]);
1006
+ else entries.push([key, toolCaller(item)]);
1007
+ }
1008
+ return Object.fromEntries(entries) as unknown as ToolUseBlock;
1009
+ }
1010
+
1011
+ function toolCaller(value: unknown): ToolUseBlock["caller"] {
1012
+ const record = requireRecord(value);
1013
+ const type = record["type"];
1014
+ if (type === "direct") {
1015
+ assertExactKeys(record, DIRECT_CALLER_KEYS);
1016
+ requireKeys(record, ["type"]);
1017
+ return { type };
1018
+ }
1019
+ if (type !== "code_execution_20250825" && type !== "code_execution_20260120")
1020
+ return fail("INVALID_INPUT");
1021
+ assertExactKeys(record, SERVER_TOOL_CALLER_KEYS);
1022
+ requireKeys(record, ["tool_id", "type"]);
1023
+ return Object.fromEntries(
1024
+ Object.keys(record).map((key) => [
1025
+ key,
1026
+ key === "type" ? type : requireString(record[key]),
1027
+ ]),
1028
+ ) as unknown as NonNullable<ToolUseBlock["caller"]>;
1029
+ }
1030
+
1031
+ function toolResultContentBlock(value: unknown): ToolResultContentBlock {
1032
+ const record = requireRecord(value);
1033
+ if (record["type"] === "text") return textBlock(record);
1034
+ if (record["type"] === "image") return imageBlock(record);
1035
+ if (record["type"] === "search_result") return searchResultBlock(record);
1036
+ if (record["type"] === "document") return documentBlock(record);
1037
+ if (record["type"] === "tool_reference") return toolReferenceBlock(record);
1038
+ return fail("INVALID_INPUT");
1039
+ }
1040
+
1041
+ function toolResultBlock(value: unknown): ToolResultBlock {
1042
+ const record = requireRecord(value);
1043
+ assertExactKeys(record, TOOL_RESULT_KEYS);
1044
+ requireKeys(record, ["tool_use_id", "type"]);
1045
+ if (record["type"] !== "tool_result") fail("INVALID_INPUT");
1046
+ const entries: [string, unknown][] = [];
1047
+ for (const key of Object.keys(record)) {
1048
+ const item = record[key];
1049
+ if (key === "tool_use_id") entries.push([key, requireString(item)]);
1050
+ else if (key === "type") entries.push([key, "tool_result"]);
1051
+ else if (key === "cache_control")
1052
+ entries.push([key, nullable(item, cacheControl)]);
1053
+ else if (key === "content") {
1054
+ if (typeof item === "string") entries.push([key, item]);
1055
+ else {
1056
+ if (!Array.isArray(item)) fail("INVALID_INPUT");
1057
+ entries.push([key, item.map((block) => toolResultContentBlock(block))]);
1058
+ }
1059
+ } else entries.push([key, requireBoolean(item)]);
1060
+ }
1061
+ return Object.fromEntries(entries) as unknown as ToolResultBlock;
1062
+ }
1063
+
1064
+ function messageContentBlock(
1065
+ value: unknown,
1066
+ preserveThinkingCacheControl: boolean,
1067
+ ): MessageContentBlock {
1068
+ const record = requireRecord(value);
1069
+ if (record["type"] === "text") return textBlock(record);
1070
+ if (record["type"] === "image") return imageBlock(record);
1071
+ if (record["type"] === "document") return documentBlock(record);
1072
+ if (record["type"] === "search_result") return searchResultBlock(record);
1073
+ if (record["type"] === "thinking")
1074
+ return thinkingBlock(record, preserveThinkingCacheControl);
1075
+ if (record["type"] === "redacted_thinking")
1076
+ return redactedThinkingBlock(record, preserveThinkingCacheControl);
1077
+ return fail("INVALID_INPUT");
1078
+ }
1079
+
1080
+ function messages(
1081
+ value: unknown,
1082
+ preserveThinkingCacheControl: boolean,
1083
+ ): readonly Message[] {
1084
+ if (!Array.isArray(value)) fail("INVALID_INPUT");
1085
+ const useIds = new Set<string>();
1086
+ const resultIds: string[] = [];
1087
+ const result = value.map((item): Message => {
1088
+ const record = requireRecord(item);
1089
+ assertExactKeys(record, MESSAGE_KEYS);
1090
+ const role = record["role"];
1091
+ if (role !== "user" && role !== "assistant") fail("INVALID_INPUT");
1092
+ const rawContent = record["content"];
1093
+ if (typeof rawContent === "string") return { role, content: rawContent };
1094
+ if (!Array.isArray(rawContent)) fail("INVALID_INPUT");
1095
+ const content = rawContent.map((block) => {
1096
+ const blockRecord = requireRecord(block);
1097
+ if (blockRecord["type"] === "text") return textBlock(blockRecord);
1098
+ if (blockRecord["type"] === "tool_use") {
1099
+ const parsed = toolUseBlock(blockRecord);
1100
+ if (useIds.has(parsed.id)) fail("INVALID_INPUT");
1101
+ useIds.add(parsed.id);
1102
+ return parsed;
1103
+ }
1104
+ if (blockRecord["type"] === "tool_result") {
1105
+ const parsed = toolResultBlock(blockRecord);
1106
+ resultIds.push(parsed.tool_use_id);
1107
+ return parsed;
1108
+ }
1109
+ return messageContentBlock(blockRecord, preserveThinkingCacheControl);
1110
+ });
1111
+ return { role, content };
1112
+ });
1113
+ for (const id of resultIds) {
1114
+ if (!useIds.has(id)) fail("INVALID_INPUT");
1115
+ }
1116
+ return result;
1117
+ }
1118
+
1119
+ function system(value: unknown): readonly TextBlock[] {
1120
+ if (!Array.isArray(value)) fail("INVALID_INPUT");
1121
+ return value.map((item) =>
1122
+ typeof item === "string" ? { type: "text", text: item } : textBlock(item),
1123
+ );
1124
+ }
1125
+
1126
+ function stringArray(value: unknown): readonly string[] {
1127
+ if (!Array.isArray(value)) fail("INVALID_INPUT");
1128
+ return value.map((item) => requireString(item));
1129
+ }
1130
+
1131
+ function allowedCallers(value: unknown): readonly string[] {
1132
+ const callers = stringArray(value);
1133
+ for (const caller of callers) {
1134
+ if (
1135
+ caller !== "direct" &&
1136
+ caller !== "code_execution_20250825" &&
1137
+ caller !== "code_execution_20260120"
1138
+ ) {
1139
+ fail("INVALID_INPUT");
1140
+ }
1141
+ }
1142
+ return callers;
1143
+ }
1144
+
1145
+ function inputExamples(
1146
+ value: unknown,
1147
+ ): readonly Readonly<Record<string, JsonValue>>[] {
1148
+ if (!Array.isArray(value)) fail("INVALID_INPUT");
1149
+ return value.map((example) => validatedJsonObject(example));
1150
+ }
1151
+
1152
+ function toolInputSchema(value: unknown): Readonly<Record<string, JsonValue>> {
1153
+ const record = requireRecord(value);
1154
+ if (hasOwn(record, "type") && record["type"] !== "object")
1155
+ fail("INVALID_INPUT");
1156
+ const entries: [string, JsonValue][] = [];
1157
+ for (const key of Object.keys(record)) {
1158
+ const item = record[key];
1159
+ if (key === "type") entries.push([key, "object"]);
1160
+ else if (key === "required")
1161
+ entries.push([key, nullable(item, stringArray)]);
1162
+ else entries.push([key, validatedJson(item)]);
1163
+ }
1164
+ return Object.fromEntries(entries);
1165
+ }
1166
+
1167
+ function userLocation(value: unknown): Readonly<Record<string, unknown>> {
1168
+ const record = requireRecord(value);
1169
+ assertExactKeys(record, USER_LOCATION_KEYS);
1170
+ requireKeys(record, ["type"]);
1171
+ if (record["type"] !== "approximate") fail("INVALID_INPUT");
1172
+ const entries: [string, unknown][] = [];
1173
+ for (const key of Object.keys(record)) {
1174
+ entries.push([
1175
+ key,
1176
+ key === "type" ? "approximate" : nullable(record[key], requireString),
1177
+ ]);
1178
+ }
1179
+ return Object.fromEntries(entries);
1180
+ }
1181
+
1182
+ function mcpToolConfig(value: unknown): Readonly<Record<string, boolean>> {
1183
+ const record = requireRecord(value);
1184
+ assertExactKeys(record, MCP_TOOL_CONFIG_KEYS);
1185
+ requireKeys(record, []);
1186
+ return Object.fromEntries(
1187
+ Object.keys(record).map((key) => [key, requireBoolean(record[key])]),
1188
+ );
1189
+ }
1190
+
1191
+ function mcpConfigs(
1192
+ value: unknown,
1193
+ ): Readonly<Record<string, Readonly<Record<string, boolean>>>> {
1194
+ const record = requireRecord(value);
1195
+ return Object.fromEntries(
1196
+ Object.keys(record).map((key) => [key, mcpToolConfig(record[key])]),
1197
+ );
1198
+ }
1199
+
1200
+ interface BuiltInToolSpec {
1201
+ readonly name?: string;
1202
+ readonly allowed: ReadonlySet<string>;
1203
+ readonly required: readonly string[];
1204
+ }
1205
+
1206
+ function builtInToolSpec(type: unknown): BuiltInToolSpec {
1207
+ if (type === "bash_20241022" || type === "bash_20250124") {
1208
+ return {
1209
+ name: "bash",
1210
+ allowed: BASH_TOOL_KEYS,
1211
+ required: ["name", "type"],
1212
+ };
1213
+ }
1214
+ if (
1215
+ type === "code_execution_20250522" ||
1216
+ type === "code_execution_20250825" ||
1217
+ type === "code_execution_20260120"
1218
+ ) {
1219
+ return {
1220
+ name: "code_execution",
1221
+ allowed: CODE_EXECUTION_TOOL_KEYS,
1222
+ required: ["name", "type"],
1223
+ };
1224
+ }
1225
+ if (
1226
+ type === "computer_20241022" ||
1227
+ type === "computer_20250124" ||
1228
+ type === "computer_20251124"
1229
+ ) {
1230
+ return {
1231
+ name: "computer",
1232
+ allowed:
1233
+ type === "computer_20251124"
1234
+ ? COMPUTER_ZOOM_TOOL_KEYS
1235
+ : COMPUTER_TOOL_KEYS,
1236
+ required: ["display_height_px", "display_width_px", "name", "type"],
1237
+ };
1238
+ }
1239
+ if (type === "memory_20250818") {
1240
+ return {
1241
+ name: "memory",
1242
+ allowed: MEMORY_TOOL_KEYS,
1243
+ required: ["name", "type"],
1244
+ };
1245
+ }
1246
+ if (
1247
+ type === "text_editor_20241022" ||
1248
+ type === "text_editor_20250124" ||
1249
+ type === "text_editor_20250429" ||
1250
+ type === "text_editor_20250728"
1251
+ ) {
1252
+ return {
1253
+ name:
1254
+ type === "text_editor_20241022" || type === "text_editor_20250124"
1255
+ ? "str_replace_editor"
1256
+ : "str_replace_based_edit_tool",
1257
+ allowed:
1258
+ type === "text_editor_20250728"
1259
+ ? TEXT_EDITOR_MAX_TOOL_KEYS
1260
+ : TEXT_EDITOR_TOOL_KEYS,
1261
+ required: ["name", "type"],
1262
+ };
1263
+ }
1264
+ if (type === "web_search_20250305" || type === "web_search_20260209") {
1265
+ return {
1266
+ name: "web_search",
1267
+ allowed: WEB_SEARCH_TOOL_KEYS,
1268
+ required: ["name", "type"],
1269
+ };
1270
+ }
1271
+ if (
1272
+ type === "web_fetch_20250910" ||
1273
+ type === "web_fetch_20260209" ||
1274
+ type === "web_fetch_20260309"
1275
+ ) {
1276
+ return {
1277
+ name: "web_fetch",
1278
+ allowed:
1279
+ type === "web_fetch_20260309"
1280
+ ? WEB_FETCH_CACHE_TOOL_KEYS
1281
+ : WEB_FETCH_TOOL_KEYS,
1282
+ required: ["name", "type"],
1283
+ };
1284
+ }
1285
+ if (type === "advisor_20260301") {
1286
+ return {
1287
+ name: "advisor",
1288
+ allowed: ADVISOR_TOOL_KEYS,
1289
+ required: ["model", "name", "type"],
1290
+ };
1291
+ }
1292
+ if (
1293
+ type === "tool_search_tool_bm25_20251119" ||
1294
+ type === "tool_search_tool_bm25"
1295
+ ) {
1296
+ return {
1297
+ name: "tool_search_tool_bm25",
1298
+ allowed: TOOL_SEARCH_KEYS,
1299
+ required: ["name", "type"],
1300
+ };
1301
+ }
1302
+ if (
1303
+ type === "tool_search_tool_regex_20251119" ||
1304
+ type === "tool_search_tool_regex"
1305
+ ) {
1306
+ return {
1307
+ name: "tool_search_tool_regex",
1308
+ allowed: TOOL_SEARCH_KEYS,
1309
+ required: ["name", "type"],
1310
+ };
1311
+ }
1312
+ if (type === "mcp_toolset") {
1313
+ return {
1314
+ allowed: MCP_TOOLSET_KEYS,
1315
+ required: ["mcp_server_name", "type"],
1316
+ };
1317
+ }
1318
+ return fail("INVALID_INPUT");
1319
+ }
1320
+
1321
+ function customToolDefinition(record: Record<string, unknown>): ToolDefinition {
1322
+ assertExactKeys(record, CUSTOM_TOOL_KEYS);
1323
+ requireKeys(record, ["input_schema", "name"]);
1324
+ const entries: [string, unknown][] = [];
1325
+ for (const key of Object.keys(record)) {
1326
+ const item = record[key];
1327
+ if (key === "input_schema") entries.push([key, toolInputSchema(item)]);
1328
+ else if (key === "name" || key === "description")
1329
+ entries.push([key, requireString(item)]);
1330
+ else if (key === "allowed_callers")
1331
+ entries.push([key, allowedCallers(item)]);
1332
+ else if (key === "cache_control")
1333
+ entries.push([key, nullable(item, (raw) => cacheControl(raw, true))]);
1334
+ else if (key === "defer_loading" || key === "strict")
1335
+ entries.push([key, requireBoolean(item)]);
1336
+ else if (key === "eager_input_streaming")
1337
+ entries.push([key, nullable(item, requireBoolean)]);
1338
+ else entries.push([key, inputExamples(item)]);
1339
+ }
1340
+ return Object.fromEntries(entries) as unknown as ToolDefinition;
1341
+ }
1342
+
1343
+ function builtInToolDefinition(
1344
+ record: Record<string, unknown>,
1345
+ ): ToolDefinition {
1346
+ const type = record["type"];
1347
+ const spec = builtInToolSpec(type);
1348
+ assertExactKeys(record, spec.allowed);
1349
+ requireKeys(record, spec.required);
1350
+ const entries: [string, unknown][] = [];
1351
+ for (const key of Object.keys(record)) {
1352
+ const item = record[key];
1353
+ if (key === "type") entries.push([key, type]);
1354
+ else if (key === "name") {
1355
+ if (item !== spec.name) fail("INVALID_INPUT");
1356
+ entries.push([key, item]);
1357
+ } else if (key === "mcp_server_name" || key === "model")
1358
+ entries.push([key, requireString(item)]);
1359
+ else if (key === "allowed_callers")
1360
+ entries.push([key, allowedCallers(item)]);
1361
+ else if (key === "cache_control" || key === "caching")
1362
+ entries.push([key, nullable(item, cacheControl)]);
1363
+ else if (
1364
+ key === "defer_loading" ||
1365
+ key === "strict" ||
1366
+ key === "enable_zoom" ||
1367
+ key === "use_cache"
1368
+ )
1369
+ entries.push([key, requireBoolean(item)]);
1370
+ else if (key === "input_examples") entries.push([key, inputExamples(item)]);
1371
+ else if (key === "display_height_px" || key === "display_width_px")
1372
+ entries.push([key, requireNumber(item)]);
1373
+ else if (
1374
+ key === "display_number" ||
1375
+ key === "max_characters" ||
1376
+ key === "max_content_tokens" ||
1377
+ key === "max_uses"
1378
+ )
1379
+ entries.push([key, nullable(item, requireNumber)]);
1380
+ else if (key === "allowed_domains" || key === "blocked_domains")
1381
+ entries.push([key, nullable(item, stringArray)]);
1382
+ else if (key === "citations")
1383
+ entries.push([key, nullable(item, citationsConfig)]);
1384
+ else if (key === "user_location")
1385
+ entries.push([key, nullable(item, userLocation)]);
1386
+ else if (key === "configs") entries.push([key, nullable(item, mcpConfigs)]);
1387
+ else if (key === "default_config") entries.push([key, mcpToolConfig(item)]);
1388
+ else fail("INVALID_INPUT");
1389
+ }
1390
+ return Object.fromEntries(entries) as unknown as ToolDefinition;
1391
+ }
1392
+
1393
+ function tools(value: unknown): readonly ToolDefinition[] {
1394
+ if (!Array.isArray(value)) fail("INVALID_INPUT");
1395
+ const names = new Set<string>();
1396
+ return value.map((item) => {
1397
+ const record = requireRecord(item);
1398
+ const result = hasOwn(record, "type")
1399
+ ? builtInToolDefinition(record)
1400
+ : customToolDefinition(record);
1401
+ if (hasOwn(record, "name")) {
1402
+ const name = requireString(record["name"]);
1403
+ if (names.has(name)) fail("INVALID_INPUT");
1404
+ names.add(name);
1405
+ }
1406
+ return result;
1407
+ });
1408
+ }
1409
+
1410
+ function capabilityBoolean(value: unknown, fallback: boolean): boolean {
1411
+ if (value === undefined) return fallback;
1412
+ if (typeof value !== "boolean") fail("INVALID_INPUT");
1413
+ return value;
1414
+ }
1415
+
1416
+ function modelResolution(value: unknown): ModelResolution {
1417
+ const record = requireRecord(value);
1418
+ const capabilityValue = record["capabilities"];
1419
+ // A catalogue-shaped capability array is accepted at this boundary, but it
1420
+ // never affects derivation: on first party every predicate depends only on
1421
+ // the normalized id. See the header of `model-capabilities.ts`. The elements
1422
+ // are still validated so that malformed input fails closed.
1423
+ if (Array.isArray(capabilityValue)) {
1424
+ for (const capability of capabilityValue) {
1425
+ if (typeof capability !== "string") fail("INVALID_INPUT");
1426
+ }
1427
+ }
1428
+ const capabilities = Array.isArray(capabilityValue)
1429
+ ? deriveCapabilities(String(record["id"]))
1430
+ : requireRecord(capabilityValue);
1431
+ const derived = deriveCapabilities(String(record["id"]));
1432
+ if (
1433
+ (capabilities.thinking !== undefined &&
1434
+ typeof capabilities.thinking !== "boolean") ||
1435
+ (capabilities.adaptiveThinking !== undefined &&
1436
+ typeof capabilities.adaptiveThinking !== "boolean") ||
1437
+ (capabilities.interleavedThinking !== undefined &&
1438
+ typeof capabilities.interleavedThinking !== "boolean") ||
1439
+ (capabilities.effort !== undefined &&
1440
+ typeof capabilities.effort !== "boolean") ||
1441
+ (capabilities.maxEffort !== undefined &&
1442
+ typeof capabilities.maxEffort !== "boolean") ||
1443
+ (capabilities.xhighEffort !== undefined &&
1444
+ typeof capabilities.xhighEffort !== "boolean") ||
1445
+ (capabilities.contextManagement !== undefined &&
1446
+ typeof capabilities.contextManagement !== "boolean") ||
1447
+ (capabilities.temperature !== undefined &&
1448
+ typeof capabilities.temperature !== "boolean") ||
1449
+ (capabilities.rejectsDisabledThinking !== undefined &&
1450
+ typeof capabilities.rejectsDisabledThinking !== "boolean")
1451
+ ) {
1452
+ fail("INVALID_INPUT");
1453
+ }
1454
+ return {
1455
+ id: requireString(record["id"]),
1456
+ wireId: requireString(record["wireId"]),
1457
+ capabilities: {
1458
+ thinking: capabilityBoolean(capabilities.thinking, derived.thinking),
1459
+ adaptiveThinking: capabilityBoolean(
1460
+ capabilities.adaptiveThinking,
1461
+ derived.adaptiveThinking,
1462
+ ),
1463
+ interleavedThinking: capabilityBoolean(
1464
+ capabilities.interleavedThinking,
1465
+ derived.interleavedThinking,
1466
+ ),
1467
+ effort: capabilityBoolean(capabilities.effort, derived.effort),
1468
+ maxEffort: capabilityBoolean(capabilities.maxEffort, derived.maxEffort),
1469
+ xhighEffort: capabilityBoolean(
1470
+ capabilities.xhighEffort,
1471
+ derived.xhighEffort,
1472
+ ),
1473
+ contextManagement: capabilityBoolean(
1474
+ capabilities.contextManagement,
1475
+ derived.contextManagement,
1476
+ ),
1477
+ temperature: capabilityBoolean(
1478
+ capabilities.temperature,
1479
+ derived.temperature,
1480
+ ),
1481
+ rejectsDisabledThinking: capabilityBoolean(
1482
+ capabilities.rejectsDisabledThinking,
1483
+ derived.rejectsDisabledThinking,
1484
+ ),
1485
+ },
1486
+ };
1487
+ }
1488
+
1489
+ function metadata(value: unknown): Readonly<Record<string, JsonValue>> {
1490
+ const record = requireRecord(value);
1491
+ if (
1492
+ hasOwn(record, "user_id") &&
1493
+ record["user_id"] !== null &&
1494
+ typeof record["user_id"] !== "string"
1495
+ ) {
1496
+ fail("INVALID_INPUT");
1497
+ }
1498
+ // validatedJsonObject returns a record by construction.
1499
+ return validatedJsonObject(record);
1500
+ }
1501
+
1502
+ function typedNumberObject(
1503
+ value: unknown,
1504
+ allowedTypes: readonly string[],
1505
+ ): Readonly<Record<string, unknown>> {
1506
+ const record = requireRecord(value);
1507
+ assertExactKeys(record, TYPED_NUMBER_KEYS);
1508
+ requireKeys(record, ["type", "value"]);
1509
+ const type = record["type"];
1510
+ if (typeof type !== "string" || !allowedTypes.includes(type))
1511
+ fail("INVALID_INPUT");
1512
+ const entries: [string, unknown][] = [];
1513
+ for (const key of Object.keys(record)) {
1514
+ entries.push([key, key === "type" ? type : requireNumber(record[key])]);
1515
+ }
1516
+ return Object.fromEntries(entries);
1517
+ }
1518
+
1519
+ function clearThinkingKeep(value: unknown): unknown {
1520
+ if (value === "all") return value;
1521
+ const record = requireRecord(value);
1522
+ if (record["type"] === "all") {
1523
+ assertExactKeys(record, ALL_THINKING_KEYS);
1524
+ requireKeys(record, ["type"]);
1525
+ return { type: "all" };
1526
+ }
1527
+ return typedNumberObject(record, ["thinking_turns"]);
1528
+ }
1529
+
1530
+ function contextManagementEdit(
1531
+ value: unknown,
1532
+ ): Readonly<Record<string, unknown>> {
1533
+ const record = requireRecord(value);
1534
+ const type = record["type"];
1535
+ let allowed: ReadonlySet<string>;
1536
+ if (type === "clear_thinking_20251015") {
1537
+ allowed = CLEAR_THINKING_KEYS;
1538
+ } else if (type === "clear_tool_uses_20250919") {
1539
+ allowed = CLEAR_TOOL_USES_KEYS;
1540
+ } else if (type === "compact_20260112") {
1541
+ allowed = COMPACT_KEYS;
1542
+ } else {
1543
+ return fail("INVALID_INPUT");
1544
+ }
1545
+ assertExactKeys(record, allowed);
1546
+ requireKeys(record, ["type"]);
1547
+ const entries: [string, unknown][] = [];
1548
+ for (const key of Object.keys(record)) {
1549
+ const item = record[key];
1550
+ if (key === "type") entries.push([key, type]);
1551
+ else if (type === "clear_thinking_20251015") {
1552
+ entries.push([key, clearThinkingKeep(item)]);
1553
+ } else if (type === "clear_tool_uses_20250919") {
1554
+ if (key === "clear_at_least")
1555
+ entries.push([
1556
+ key,
1557
+ nullable(item, (raw) => typedNumberObject(raw, ["input_tokens"])),
1558
+ ]);
1559
+ else if (key === "clear_tool_inputs")
1560
+ entries.push([
1561
+ key,
1562
+ nullable(item, (raw) =>
1563
+ typeof raw === "boolean" ? raw : stringArray(raw),
1564
+ ),
1565
+ ]);
1566
+ else if (key === "exclude_tools")
1567
+ entries.push([key, nullable(item, stringArray)]);
1568
+ else if (key === "keep")
1569
+ entries.push([key, typedNumberObject(item, ["tool_uses"])]);
1570
+ else
1571
+ entries.push([
1572
+ key,
1573
+ typedNumberObject(item, ["input_tokens", "tool_uses"]),
1574
+ ]);
1575
+ } else if (key === "instructions")
1576
+ entries.push([key, nullable(item, requireString)]);
1577
+ else if (key === "pause_after_compaction")
1578
+ entries.push([key, requireBoolean(item)]);
1579
+ else
1580
+ entries.push([
1581
+ key,
1582
+ nullable(item, (raw) => typedNumberObject(raw, ["input_tokens"])),
1583
+ ]);
1584
+ }
1585
+ return Object.fromEntries(entries);
1586
+ }
1587
+
1588
+ function contextManagement(value: unknown): Readonly<Record<string, unknown>> {
1589
+ const record = requireRecord(value);
1590
+ assertExactKeys(record, CONTEXT_CONFIG_KEYS);
1591
+ requireKeys(record, []);
1592
+ const entries: [string, unknown][] = [];
1593
+ for (const key of Object.keys(record)) {
1594
+ const item = record[key];
1595
+ if (!Array.isArray(item)) fail("INVALID_INPUT");
1596
+ entries.push([key, item.map((edit) => contextManagementEdit(edit))]);
1597
+ }
1598
+ return Object.fromEntries(entries);
1599
+ }
1600
+
1601
+ function outputFormat(value: unknown): Readonly<Record<string, unknown>> {
1602
+ const record = requireRecord(value);
1603
+ assertExactKeys(record, JSON_OUTPUT_FORMAT_KEYS);
1604
+ requireKeys(record, ["schema", "type"]);
1605
+ if (record["type"] !== "json_schema") fail("INVALID_INPUT");
1606
+ const entries: [string, unknown][] = [];
1607
+ for (const key of Object.keys(record)) {
1608
+ entries.push([
1609
+ key,
1610
+ key === "type" ? "json_schema" : validatedJsonObject(record[key]),
1611
+ ]);
1612
+ }
1613
+ return Object.fromEntries(entries);
1614
+ }
1615
+
1616
+ function toolChoice(value: unknown): Readonly<Record<string, unknown>> {
1617
+ const record = requireRecord(value);
1618
+ const type = record["type"];
1619
+ const allowed =
1620
+ type === "none"
1621
+ ? TOOL_CHOICE_NONE_KEYS
1622
+ : type === "auto" || type === "any"
1623
+ ? TOOL_CHOICE_PARALLEL_KEYS
1624
+ : type === "tool"
1625
+ ? TOOL_CHOICE_NAMED_KEYS
1626
+ : fail("INVALID_INPUT");
1627
+ assertExactKeys(record, allowed);
1628
+ requireKeys(record, type === "tool" ? ["name", "type"] : ["type"]);
1629
+ const entries: [string, unknown][] = [];
1630
+ for (const key of Object.keys(record)) {
1631
+ if (key === "type") entries.push([key, type]);
1632
+ else if (key === "name") entries.push([key, requireString(record[key])]);
1633
+ else entries.push([key, requireBoolean(record[key])]);
1634
+ }
1635
+ return Object.fromEntries(entries);
1636
+ }
1637
+
1638
+ function betaEnabled(profile: ClaudeCodeProtocolProfile | undefined): boolean {
1639
+ return profile?.betaPolicy.experimentalBetasEnabled === true;
1640
+ }
1641
+
1642
+ function outputConfig(
1643
+ value: unknown,
1644
+ profile: ClaudeCodeProtocolProfile | undefined,
1645
+ adapterEffort: unknown,
1646
+ adapterEffortActive: boolean,
1647
+ ): Readonly<Record<string, unknown>> {
1648
+ const record = requireRecord(value);
1649
+ assertExactKeys(record, OUTPUT_CONFIG_KEYS);
1650
+ requireKeys(record, []);
1651
+ if (
1652
+ hasOwn(record, "effort") &&
1653
+ adapterEffort !== undefined &&
1654
+ record["effort"] !== adapterEffort
1655
+ ) {
1656
+ fail("INVALID_INPUT");
1657
+ }
1658
+ const entries: [string, unknown][] = [];
1659
+ for (const key of Object.keys(record)) {
1660
+ const item = record[key];
1661
+ if (key === "effort") {
1662
+ if (
1663
+ item !== null &&
1664
+ item !== "low" &&
1665
+ item !== "medium" &&
1666
+ item !== "high" &&
1667
+ item !== "xhigh" &&
1668
+ item !== "max"
1669
+ ) {
1670
+ fail("INVALID_INPUT");
1671
+ }
1672
+ entries.push([key, item]);
1673
+ } else {
1674
+ if (!betaEnabled(profile)) fail("UNSUPPORTED_CAPABILITY");
1675
+ entries.push([
1676
+ "max_output_tokens",
1677
+ item === null ? null : requirePositiveInteger(item),
1678
+ ]);
1679
+ }
1680
+ }
1681
+ if (adapterEffortActive && !hasOwn(record, "effort")) {
1682
+ entries.push(["effort", adapterEffort]);
1683
+ }
1684
+ return Object.fromEntries(entries);
1685
+ }
1686
+
1687
+ function deepFreeze<T>(value: T): T {
1688
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
1689
+ for (const key of Reflect.ownKeys(value))
1690
+ deepFreeze(Reflect.get(value, key));
1691
+ Object.freeze(value);
1692
+ }
1693
+ return value;
1694
+ }
1695
+
1696
+ function contextHintEnabled(profile: unknown): boolean {
1697
+ return isRecord(profile) && profile["contextHintEnabled"] === true;
1698
+ }
1699
+
1700
+ /**
1701
+ * Canonicalises the message and tool lists for the count-tokens endpoint.
1702
+ *
1703
+ * The count-tokens body shares no other field with the messages body, but it
1704
+ * MUST share these two canonicalisers. Reimplementing them would give the two
1705
+ * public entry points different fail-closed guarantees for the same caller
1706
+ * input, which is precisely the class of divergence this package exists to
1707
+ * prevent.
1708
+ */
1709
+ export function canonicalCountTokensLists(
1710
+ rawMessages: unknown,
1711
+ rawTools: unknown,
1712
+ ): {
1713
+ readonly messages: readonly Message[];
1714
+ readonly tools: readonly ToolDefinition[];
1715
+ } {
1716
+ return {
1717
+ // The count-tokens endpoint has no seam of its own, so thinking blocks stay
1718
+ // on the strict allowlist here.
1719
+ messages: messages(rawMessages, false),
1720
+ tools: rawTools === undefined ? Object.freeze([]) : tools(rawTools),
1721
+ };
1722
+ }
1723
+
1724
+ export function buildCanonicalBody(
1725
+ rawInput: unknown,
1726
+ rawResolvedModel: unknown,
1727
+ rawSystemBlocks: unknown,
1728
+ rawMetadata: unknown,
1729
+ profile?: ClaudeCodeProtocolProfile,
1730
+ ): Readonly<Record<string, unknown>> {
1731
+ inspectJsonInputs([
1732
+ rawInput,
1733
+ rawResolvedModel,
1734
+ rawSystemBlocks,
1735
+ rawMetadata,
1736
+ ...(profile === undefined ? [] : [profile]),
1737
+ ]);
1738
+
1739
+ const input = requireRecord(rawInput);
1740
+ assertExactKeys(input, INPUT_KEY_SET);
1741
+ requireKeys(input, ["maxTokens", "messages"]);
1742
+ const resolvedModel = modelResolution(rawResolvedModel);
1743
+ if (
1744
+ hasOwn(input, "model") &&
1745
+ (typeof input["model"] !== "string" ||
1746
+ stripModelMarkers(input["model"]) !== resolvedModel.wireId)
1747
+ ) {
1748
+ fail("INVALID_INPUT");
1749
+ }
1750
+
1751
+ const cacheOverride = hasOwn(input, "cacheControl")
1752
+ ? nullable(input["cacheControl"], cacheControlInput)
1753
+ : undefined;
1754
+ // Package extension. Only a boolean states a decision; omission is `false`,
1755
+ // which keeps the strict thinking-block allowlist and the emitted body
1756
+ // byte-identical.
1757
+ const preserveThinkingCacheControl =
1758
+ hasOwn(input, "preserveThinkingBlockCacheControl") &&
1759
+ requireBoolean(input["preserveThinkingBlockCacheControl"]);
1760
+ let systemBlocks = system(rawSystemBlocks);
1761
+ let messageList = messages(input["messages"], preserveThinkingCacheControl);
1762
+ let toolList = hasOwn(input, "tools") ? tools(input["tools"]) : undefined;
1763
+ if (cacheOverride !== undefined && cacheOverride !== null) {
1764
+ systemBlocks = applySystemCacheControl(systemBlocks, cacheOverride);
1765
+ messageList = applyMessageCacheControl(messageList, cacheOverride);
1766
+ if (toolList !== undefined) {
1767
+ toolList = applyToolCacheControl(toolList, cacheOverride);
1768
+ }
1769
+ }
1770
+
1771
+ // D16. The genuine client never sends a `max_tokens` above the model's own
1772
+ // default output limit; a larger caller value is silently capped, not
1773
+ // rejected. The clamped result is reused for the thinking budget below,
1774
+ // because upstream feeds the same `Fi` into both.
1775
+ const maxTokens = clampMaxTokens(
1776
+ requirePositiveInteger(input["maxTokens"]),
1777
+ resolvedModel.id,
1778
+ );
1779
+ const result: Record<string, unknown> = {
1780
+ model: resolvedModel.wireId,
1781
+ max_tokens: maxTokens,
1782
+ system: systemBlocks,
1783
+ messages: messageList,
1784
+ };
1785
+
1786
+ if (toolList !== undefined) result["tools"] = toolList;
1787
+
1788
+ let thinkingRequest: ThinkingRequest | undefined;
1789
+ if (hasOwn(input, "thinking")) {
1790
+ const rawThinking = input["thinking"];
1791
+ if (!isRecord(rawThinking) || Array.isArray(rawThinking)) {
1792
+ fail("INVALID_THINKING");
1793
+ }
1794
+ for (const key of Reflect.ownKeys(rawThinking)) {
1795
+ if (key !== "type" && key !== "budgetTokens" && key !== "display") {
1796
+ fail("INVALID_THINKING");
1797
+ }
1798
+ }
1799
+ const type = rawThinking["type"];
1800
+ if (type !== "enabled" && type !== "adaptive" && type !== "disabled") {
1801
+ fail("INVALID_THINKING");
1802
+ }
1803
+ let budgetTokens: number | undefined;
1804
+ if (hasOwn(rawThinking, "budgetTokens")) {
1805
+ const raw = rawThinking["budgetTokens"];
1806
+ if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw <= 0) {
1807
+ fail("INVALID_THINKING");
1808
+ }
1809
+ budgetTokens = raw;
1810
+ }
1811
+ let display: ThinkingDisplay | undefined;
1812
+ if (hasOwn(rawThinking, "display")) {
1813
+ const raw = rawThinking["display"];
1814
+ if (raw !== "summarized" && raw !== "omitted") fail("INVALID_THINKING");
1815
+ // Upstream's schema attaches `display` to the enabled and adaptive
1816
+ // variants only; the disabled variant declares no such property.
1817
+ if (type === "disabled") fail("INVALID_THINKING");
1818
+ display = raw;
1819
+ }
1820
+ thinkingRequest = {
1821
+ type,
1822
+ ...(budgetTokens === undefined ? {} : { budgetTokens }),
1823
+ ...(display === undefined ? {} : { display }),
1824
+ };
1825
+ }
1826
+
1827
+ const resolved = resolveThinking(
1828
+ thinkingRequest,
1829
+ resolvedModel.id,
1830
+ resolvedModel.capabilities,
1831
+ (profile ?? CLAUDE_CODE_2_1_195_PROFILE).betaPolicy,
1832
+ maxTokens,
1833
+ );
1834
+ if (resolved.emitted !== undefined) result["thinking"] = resolved.emitted;
1835
+
1836
+ if (!resolved.requestActive && resolvedModel.capabilities.temperature) {
1837
+ result["temperature"] = hasOwn(input, "temperature")
1838
+ ? requireNumber(input["temperature"])
1839
+ : 1;
1840
+ }
1841
+
1842
+ let adapterEffort: unknown;
1843
+ let adapterEffortActive = false;
1844
+ if (hasOwn(input, "effort")) {
1845
+ const effort = input["effort"];
1846
+ if (
1847
+ !resolvedModel.capabilities.effort ||
1848
+ (effort !== "low" &&
1849
+ effort !== "medium" &&
1850
+ effort !== "high" &&
1851
+ effort !== "xhigh" &&
1852
+ effort !== "max")
1853
+ ) {
1854
+ fail("INVALID_EFFORT");
1855
+ }
1856
+ if (
1857
+ (effort === "max" && !resolvedModel.capabilities.maxEffort) ||
1858
+ (effort === "xhigh" && !resolvedModel.capabilities.xhighEffort)
1859
+ ) {
1860
+ fail("INVALID_EFFORT");
1861
+ }
1862
+ adapterEffort = effort;
1863
+ if (
1864
+ isRecord(result["thinking"]) &&
1865
+ result["thinking"]["type"] === "adaptive"
1866
+ ) {
1867
+ adapterEffortActive = true;
1868
+ if (!hasOwn(input, "outputConfig")) {
1869
+ result["output_config"] = { effort };
1870
+ }
1871
+ }
1872
+ }
1873
+
1874
+ for (const key of Object.keys(input)) {
1875
+ const item = input[key];
1876
+ if (key === "contextManagement")
1877
+ result["context_management"] = nullable(item, contextManagement);
1878
+ else if (key === "outputConfig")
1879
+ result["output_config"] = outputConfig(
1880
+ item,
1881
+ profile,
1882
+ adapterEffort,
1883
+ adapterEffortActive,
1884
+ );
1885
+ else if (key === "speed") {
1886
+ if (item !== null && item !== "standard" && item !== "fast")
1887
+ fail("INVALID_INPUT");
1888
+ if (item === "fast" && !betaEnabled(profile))
1889
+ fail("UNSUPPORTED_CAPABILITY");
1890
+ result["speed"] = item;
1891
+ } else if (key === "serviceTier") {
1892
+ if (item !== "auto" && item !== "standard_only") fail("INVALID_INPUT");
1893
+ result["service_tier"] = item;
1894
+ } else if (key === "outputFormat")
1895
+ result["output_format"] = nullable(item, outputFormat);
1896
+ else if (key === "toolChoice") {
1897
+ const validatedToolChoice = toolChoice(item);
1898
+ result["tool_choice"] =
1899
+ validatedToolChoice["type"] === "tool" &&
1900
+ resolved.extendedThinkingActive
1901
+ ? { type: "auto" }
1902
+ : validatedToolChoice;
1903
+ } else if (key === "topP") result["top_p"] = requireNumber(item);
1904
+ else if (key === "topK") result["top_k"] = requireNumber(item);
1905
+ else if (key === "stopSequences")
1906
+ result["stop_sequences"] = stringArray(item);
1907
+ else if (key === "stream") result["stream"] = requireBoolean(item);
1908
+ }
1909
+
1910
+ if (contextHintEnabled(profile)) {
1911
+ result["context_hint"] = { enabled: true };
1912
+ }
1913
+ result["metadata"] = metadata(rawMetadata);
1914
+ if (hasOwn(input, "experimentalBodyFields")) {
1915
+ const experimentalBodyFields = validatedJsonObject(
1916
+ input["experimentalBodyFields"],
1917
+ );
1918
+ for (const key of Object.keys(experimentalBodyFields)) {
1919
+ if (hasOwn(result, key)) fail("INVALID_INPUT");
1920
+ result[key] = experimentalBodyFields[key];
1921
+ }
1922
+ }
1923
+ return deepFreeze(result);
1924
+ }