@gajae-code/ai 0.11.0 → 0.11.2

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.
@@ -435,27 +435,31 @@ function getCacheControl(
435
435
  model: Model<"anthropic-messages">,
436
436
  baseUrl: string,
437
437
  cacheRetention?: CacheRetention,
438
- ): { retention: CacheRetention; cacheControl?: AnthropicCacheControl } {
439
- // Default Anthropic prompt caching to long (1h) retention. The provider
440
- // default of ~5m is too fragile for long-running Codex/Gajae-Code subagent
441
- // workflows, where the prefix is frequently evicted between turns. Explicit
442
- // request/model `cacheRetention` and the GJC_CACHE_RETENTION /
443
- // PI_CACHE_RETENTION env overrides still win.
438
+ ): { mode: AnthropicCacheMode; cacheControl?: AnthropicCacheControl } {
444
439
  const retention = resolveCacheRetention(cacheRetention, "long");
445
- if (retention === "none") {
446
- return { retention };
447
- }
448
- // `ttl: "1h"` is only honoured on the canonical Anthropic API for models
449
- // that advertise long-cache support. Everywhere else (proxies, gateways,
450
- // models without the capability) we fall back to the default ephemeral
451
- // breakpoint, which Anthropic services at the standard ~5m TTL.
452
- const ttl =
453
- retention === "long" && isAnthropicApiBaseUrl(baseUrl) && getAnthropicCompat(model).supportsLongCacheRetention
454
- ? "1h"
455
- : undefined;
440
+ if (retention === "none") return { mode: "none" };
441
+
442
+ const isCanonicalApi = isAnthropicApiBaseUrl(baseUrl);
443
+ const promptCacheMode = model.compat?.promptCacheMode;
444
+ const mode: AnthropicCacheMode =
445
+ promptCacheMode === "none"
446
+ ? "none"
447
+ : promptCacheMode === "explicit"
448
+ ? "explicit"
449
+ : isCanonicalApi
450
+ ? "automatic"
451
+ : "none";
452
+ if (mode === "none") return { mode };
453
+
454
+ const supportsLongCacheRetention = isCanonicalApi
455
+ ? getAnthropicCompat(model).supportsLongCacheRetention
456
+ : model.compat?.supportsLongCacheRetention === true;
456
457
  return {
457
- retention,
458
- cacheControl: { type: "ephemeral", ...(ttl && { ttl }) },
458
+ mode,
459
+ cacheControl: {
460
+ type: "ephemeral",
461
+ ...(retention === "long" && supportsLongCacheRetention ? { ttl: "1h" } : {}),
462
+ },
459
463
  };
460
464
  }
461
465
 
@@ -1081,6 +1085,7 @@ function getAnthropicCompat(
1081
1085
  supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? true,
1082
1086
  supportsToolChoice: model.compat?.supportsToolChoice ?? true,
1083
1087
  supportsForcedToolChoice: model.compat?.supportsForcedToolChoice ?? true,
1088
+ promptCacheMode: model.compat?.promptCacheMode ?? "none",
1084
1089
  toolChoiceSupport: model.compat?.toolChoiceSupport,
1085
1090
  };
1086
1091
  }
@@ -1304,6 +1309,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1304
1309
  if (replacementPayload !== undefined) {
1305
1310
  nextParams = replacementPayload as typeof nextParams;
1306
1311
  }
1312
+ validateCacheControls(nextParams as AnthropicCacheParams);
1307
1313
  rawRequestDump = {
1308
1314
  provider: model.provider,
1309
1315
  api: output.api,
@@ -1324,6 +1330,13 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1324
1330
  ) & { index: number };
1325
1331
  const blocks = output.content as Block[];
1326
1332
  const blocksByAnthropicIndex = new Map<number, Block>();
1333
+ // Derive from the ACTUAL request shape, not the option default: the request
1334
+ // only sends `display: "summarized"` on specific paths (adaptive display is
1335
+ // omitted for models where supportsAdaptiveThinkingDisplay is false). Defaulting
1336
+ // to summarized would mislabel raw thinking as a provider-displayable summary.
1337
+ const summarizedThinking =
1338
+ (params.thinking as { display?: AnthropicThinkingDisplay } | undefined)?.display === "summarized";
1339
+ const reasoningBuffers = new WeakMap<object, string>();
1327
1340
  const getBlockByAnthropicIndex = (anthropicIndex: number) => {
1328
1341
  const block = blocksByAnthropicIndex.get(anthropicIndex);
1329
1342
  if (!block) return { block: undefined, contentIndex: -1 };
@@ -1455,11 +1468,24 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1455
1468
  };
1456
1469
  output.content.push(block);
1457
1470
  trackBlockByAnthropicIndex(event.index, block);
1471
+ // Emit thinking_start FIRST so a reasoning item is open before any
1472
+ // summary-start: the Responses SSE encoder only accepts a summary
1473
+ // start when state.open.kind === "reasoning", otherwise the
1474
+ // reasoning_summary_part.added frame is dropped and deltas arrive
1475
+ // out of order.
1458
1476
  stream.push({
1459
1477
  type: "thinking_start",
1460
1478
  contentIndex: output.content.length - 1,
1461
1479
  partial: output,
1462
1480
  });
1481
+ if (summarizedThinking) {
1482
+ reasoningBuffers.set(block, "");
1483
+ stream.push({
1484
+ type: "reasoning_summary_start",
1485
+ contentIndex: output.content.length - 1,
1486
+ partial: output,
1487
+ });
1488
+ }
1463
1489
  } else if (event.content_block.type === "redacted_thinking") {
1464
1490
  const block: Block = {
1465
1491
  type: "redactedThinking",
@@ -1504,12 +1530,23 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1504
1530
  const { block, contentIndex: index } = getBlockByAnthropicIndex(event.index);
1505
1531
  if (block && block.type === "thinking") {
1506
1532
  block.thinking += event.delta.thinking;
1507
- stream.push({
1508
- type: "thinking_delta",
1509
- contentIndex: index,
1510
- delta: event.delta.thinking,
1511
- partial: output,
1512
- });
1533
+ if (summarizedThinking) {
1534
+ const summary = (reasoningBuffers.get(block) ?? "") + event.delta.thinking;
1535
+ reasoningBuffers.set(block, summary);
1536
+ stream.push({
1537
+ type: "reasoning_summary_delta",
1538
+ contentIndex: index,
1539
+ delta: event.delta.thinking,
1540
+ partial: output,
1541
+ });
1542
+ } else {
1543
+ stream.push({
1544
+ type: "thinking_delta",
1545
+ contentIndex: index,
1546
+ delta: event.delta.thinking,
1547
+ partial: output,
1548
+ });
1549
+ }
1513
1550
  }
1514
1551
  } else if (event.delta.type === "input_json_delta") {
1515
1552
  const { block, contentIndex: index } = getBlockByAnthropicIndex(event.index);
@@ -1543,6 +1580,21 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1543
1580
  partial: output,
1544
1581
  });
1545
1582
  } else if (block.type === "thinking") {
1583
+ if (summarizedThinking) {
1584
+ const summaryText = reasoningBuffers.get(block) ?? "";
1585
+ const mutable = block as {
1586
+ provenance?: "summary" | "raw" | "mixed";
1587
+ summaryText?: string;
1588
+ };
1589
+ if (mutable.summaryText === undefined) mutable.summaryText = summaryText;
1590
+ if (mutable.provenance === undefined) mutable.provenance = "summary";
1591
+ stream.push({
1592
+ type: "reasoning_summary_end",
1593
+ contentIndex: index,
1594
+ content: summaryText,
1595
+ partial: output,
1596
+ });
1597
+ }
1546
1598
  stream.push({
1547
1599
  type: "thinking_end",
1548
1600
  contentIndex: index,
@@ -1997,232 +2049,159 @@ type CacheControlBlock = {
1997
2049
  cache_control?: AnthropicCacheControl | null;
1998
2050
  };
1999
2051
 
2000
- function applyCacheControlToLastBlock<T extends CacheControlBlock>(
2001
- blocks: T[],
2002
- cacheControl: AnthropicCacheControl,
2003
- ): void {
2004
- if (blocks.length === 0) return;
2005
- const lastIndex = blocks.length - 1;
2006
- blocks[lastIndex] = { ...blocks[lastIndex], cache_control: cacheControl };
2007
- }
2008
-
2009
- function applyCacheControlToLastTextBlock(
2010
- blocks: Array<ContentBlockParam & CacheControlBlock>,
2011
- cacheControl: AnthropicCacheControl,
2012
- ): void {
2013
- if (blocks.length === 0) return;
2014
- for (let i = blocks.length - 1; i >= 0; i--) {
2015
- if (blocks[i].type === "text") {
2016
- blocks[i] = { ...blocks[i], cache_control: cacheControl };
2017
- return;
2018
- }
2019
- }
2020
- applyCacheControlToLastBlock(blocks, cacheControl);
2021
- }
2022
-
2023
- function applyPromptCaching(params: MessageCreateParamsStreaming, cacheControl?: AnthropicCacheControl): void {
2024
- if (!cacheControl) return;
2025
-
2026
- // Skip if cache_control breakpoints were already placed externally on messages.
2027
- for (const message of params.messages) {
2028
- if (Array.isArray(message.content)) {
2029
- if ((message.content as Array<ContentBlockParam & CacheControlBlock>).some(b => b.cache_control != null))
2030
- return;
2031
- }
2032
- }
2052
+ type AnthropicCacheParams = MessageCreateParamsStreaming & {
2053
+ cache_control?: AnthropicCacheControl;
2054
+ };
2033
2055
 
2034
- const MAX_CACHE_BREAKPOINTS = 4;
2035
- let cacheBreakpointsUsed = 0;
2056
+ type AnthropicCacheMode = "automatic" | "explicit" | "none";
2036
2057
 
2037
- if (params.tools && params.tools.length > 0) {
2038
- applyCacheControlToLastBlock(params.tools as Array<CacheControlBlock>, cacheControl);
2039
- cacheBreakpointsUsed++;
2040
- }
2058
+ function isCacheableContentBlock(block: ContentBlockParam): boolean {
2059
+ if (block.type === "thinking" || block.type === "redacted_thinking") return false;
2060
+ return block.type !== "text" || block.text.trim().length > 0;
2061
+ }
2041
2062
 
2042
- if (cacheBreakpointsUsed >= MAX_CACHE_BREAKPOINTS) return;
2063
+ function cacheControlError(path: string, reason: string): Error {
2064
+ return new Error(`Invalid Anthropic cache_control at ${path}: ${reason}`);
2065
+ }
2043
2066
 
2044
- if (params.system && Array.isArray(params.system) && params.system.length > 0) {
2045
- applyCacheControlToLastBlock(params.system, cacheControl);
2046
- cacheBreakpointsUsed++;
2067
+ function validateCacheControl(control: unknown, path: string, seenFiveMinute: { value: boolean }): void {
2068
+ if (!isRecord(control) || control.type !== "ephemeral") {
2069
+ throw cacheControlError(path, 'expected { type: "ephemeral" }');
2047
2070
  }
2048
-
2049
- if (cacheBreakpointsUsed >= MAX_CACHE_BREAKPOINTS) return;
2050
-
2051
- const userIndexes = params.messages
2052
- .map((message, index) => (message.role === "user" ? index : -1))
2053
- .filter(index => index >= 0);
2054
-
2055
- if (userIndexes.length >= 2) {
2056
- const penultimateUserIndex = userIndexes[userIndexes.length - 2];
2057
- const penultimateUser = params.messages[penultimateUserIndex];
2058
- if (penultimateUser) {
2059
- if (typeof penultimateUser.content === "string") {
2060
- const contentBlock: ContentBlockParam & CacheControlBlock = {
2061
- type: "text",
2062
- text: penultimateUser.content,
2063
- cache_control: cacheControl,
2064
- };
2065
- penultimateUser.content = [contentBlock];
2066
- cacheBreakpointsUsed++;
2067
- } else if (Array.isArray(penultimateUser.content) && penultimateUser.content.length > 0) {
2068
- applyCacheControlToLastTextBlock(
2069
- penultimateUser.content as Array<ContentBlockParam & CacheControlBlock>,
2070
- cacheControl,
2071
- );
2072
- cacheBreakpointsUsed++;
2073
- }
2074
- }
2071
+ if (control.ttl !== undefined && control.ttl !== "5m" && control.ttl !== "1h") {
2072
+ throw cacheControlError(path, 'ttl must be "5m" or "1h"');
2075
2073
  }
2076
-
2077
- if (cacheBreakpointsUsed >= MAX_CACHE_BREAKPOINTS) return;
2078
-
2079
- if (userIndexes.length >= 1) {
2080
- const lastUserIndex = userIndexes[userIndexes.length - 1];
2081
- const lastUser = params.messages[lastUserIndex];
2082
- if (lastUser) {
2083
- if (typeof lastUser.content === "string") {
2084
- const contentBlock: ContentBlockParam & CacheControlBlock = {
2085
- type: "text",
2086
- text: lastUser.content,
2087
- cache_control: cacheControl,
2088
- };
2089
- lastUser.content = [contentBlock];
2090
- } else if (Array.isArray(lastUser.content) && lastUser.content.length > 0) {
2091
- applyCacheControlToLastTextBlock(
2092
- lastUser.content as Array<ContentBlockParam & CacheControlBlock>,
2093
- cacheControl,
2094
- );
2095
- }
2096
- }
2097
- }
2098
- }
2099
-
2100
- function normalizeCacheControlBlockTtl(block: CacheControlBlock, seenFiveMinute: { value: boolean }): void {
2101
- const cacheControl = block.cache_control;
2102
- if (!cacheControl) return;
2103
- if (cacheControl.ttl !== "1h") {
2104
- seenFiveMinute.value = true;
2074
+ if (control.ttl === "1h") {
2075
+ if (seenFiveMinute.value) throw cacheControlError(path, "1h TTL must precede 5m TTL");
2105
2076
  return;
2106
2077
  }
2107
- if (seenFiveMinute.value) {
2108
- delete cacheControl.ttl;
2109
- }
2078
+ seenFiveMinute.value = true;
2110
2079
  }
2111
2080
 
2112
- function normalizeCacheControlTtlOrdering(params: MessageCreateParamsStreaming): void {
2081
+ function validateCacheControls(params: AnthropicCacheParams): void {
2113
2082
  const seenFiveMinute = { value: false };
2114
- if (params.tools) {
2115
- for (const tool of params.tools as Array<Anthropic.Messages.Tool & CacheControlBlock>) {
2116
- normalizeCacheControlBlockTtl(tool, seenFiveMinute);
2117
- }
2083
+ let count = 0;
2084
+ const validate = (control: unknown, path: string): void => {
2085
+ if (control == null) return;
2086
+ count++;
2087
+ validateCacheControl(control, path, seenFiveMinute);
2088
+ };
2089
+ if (!Array.isArray(params.messages)) throw cacheControlError("messages", "must be an array");
2090
+
2091
+ validate(params.cache_control, "cache_control");
2092
+ for (const [index, tool] of (params.tools ?? []).entries()) {
2093
+ validate((tool as CacheControlBlock).cache_control, `tools[${index}].cache_control`);
2118
2094
  }
2119
- if (params.system && Array.isArray(params.system)) {
2120
- for (const block of params.system as Array<AnthropicSystemBlock & CacheControlBlock>) {
2121
- normalizeCacheControlBlockTtl(block, seenFiveMinute);
2095
+ if (Array.isArray(params.system)) {
2096
+ for (const [index, block] of params.system.entries()) {
2097
+ validate((block as CacheControlBlock).cache_control, `system[${index}].cache_control`);
2122
2098
  }
2123
2099
  }
2124
- for (const message of params.messages) {
2100
+ for (const [messageIndex, message] of params.messages.entries()) {
2125
2101
  if (!Array.isArray(message.content)) continue;
2126
- for (const block of message.content as Array<ContentBlockParam & CacheControlBlock>) {
2127
- normalizeCacheControlBlockTtl(block, seenFiveMinute);
2102
+ for (const [blockIndex, block] of message.content.entries()) {
2103
+ const control = (block as CacheControlBlock).cache_control;
2104
+ if (control != null && !isCacheableContentBlock(block)) {
2105
+ throw cacheControlError(
2106
+ `messages[${messageIndex}].content[${blockIndex}].cache_control`,
2107
+ "block is not cacheable",
2108
+ );
2109
+ }
2110
+ validate(control, `messages[${messageIndex}].content[${blockIndex}].cache_control`);
2128
2111
  }
2129
2112
  }
2113
+ if (count > 4) throw cacheControlError("cache_control", "at most four total breakpoints are allowed");
2130
2114
  }
2131
2115
 
2132
- function findLastCacheControlIndex<T extends CacheControlBlock>(blocks: T[]): number {
2116
+ function applyCacheControlToLastCacheableBlock(
2117
+ blocks: Array<ContentBlockParam & CacheControlBlock>,
2118
+ cacheControl: AnthropicCacheControl,
2119
+ ): boolean {
2133
2120
  for (let index = blocks.length - 1; index >= 0; index--) {
2134
- if (blocks[index]?.cache_control != null) return index;
2121
+ const block = blocks[index];
2122
+ if (!isCacheableContentBlock(block)) continue;
2123
+ blocks[index] = { ...block, cache_control: { ...cacheControl } };
2124
+ return true;
2135
2125
  }
2136
- return -1;
2126
+ return false;
2137
2127
  }
2138
2128
 
2139
- function stripCacheControlExceptIndex<T extends CacheControlBlock>(
2140
- blocks: T[],
2141
- preserveIndex: number,
2142
- excessCounter: { value: number },
2143
- ): void {
2144
- for (let index = 0; index < blocks.length && excessCounter.value > 0; index++) {
2145
- if (index === preserveIndex) continue;
2146
- if (!blocks[index]?.cache_control) continue;
2147
- delete blocks[index].cache_control;
2148
- excessCounter.value--;
2149
- }
2129
+ function isHumanUserMessage(message: MessageCreateParamsStreaming["messages"][number]): boolean {
2130
+ if (message.role !== "user") return false;
2131
+ if (typeof message.content === "string") return true;
2132
+ return message.content.some(block => block.type !== "tool_result");
2150
2133
  }
2151
2134
 
2152
- function stripAllCacheControl<T extends CacheControlBlock>(blocks: T[], excessCounter: { value: number }): void {
2153
- for (const block of blocks) {
2154
- if (excessCounter.value <= 0) return;
2155
- if (!block.cache_control) continue;
2156
- delete block.cache_control;
2157
- excessCounter.value--;
2135
+ function applyExplicitPromptCaching(params: AnthropicCacheParams, cacheControl: AnthropicCacheControl): void {
2136
+ if (countCacheControlBreakpoints(params) >= 4) return;
2137
+
2138
+ const currentUserIndex = params.messages.findLastIndex(isHumanUserMessage);
2139
+ if (currentUserIndex < 0) return;
2140
+ const currentUser = params.messages[currentUserIndex];
2141
+ if (!currentUser) return;
2142
+
2143
+ // A tool result is encoded as role "user" on the wire, but belongs to the
2144
+ // preceding assistant turn. Anchor that assistant turn, not the tool result,
2145
+ // so changing tool output does not invalidate the reusable conversation prefix.
2146
+ for (let index = currentUserIndex - 1; index >= 0; index--) {
2147
+ const message = params.messages[index];
2148
+ if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
2149
+ if (
2150
+ applyCacheControlToLastCacheableBlock(
2151
+ message.content as Array<ContentBlockParam & CacheControlBlock>,
2152
+ cacheControl,
2153
+ )
2154
+ ) {
2155
+ break;
2156
+ }
2157
+ }
2158
+
2159
+ if (countCacheControlBreakpoints(params) >= 4) return;
2160
+ if (typeof currentUser.content === "string" && currentUser.content.trim()) {
2161
+ currentUser.content = [{ type: "text", text: currentUser.content, cache_control: { ...cacheControl } }];
2162
+ } else if (Array.isArray(currentUser.content)) {
2163
+ applyCacheControlToLastCacheableBlock(
2164
+ currentUser.content as Array<ContentBlockParam & CacheControlBlock>,
2165
+ cacheControl,
2166
+ );
2158
2167
  }
2159
2168
  }
2160
2169
 
2161
- function stripMessageCacheControl(
2162
- messages: MessageCreateParamsStreaming["messages"],
2163
- excessCounter: { value: number },
2170
+ function applyPromptCaching(
2171
+ params: AnthropicCacheParams,
2172
+ cacheMode: AnthropicCacheMode,
2173
+ cacheControl?: AnthropicCacheControl,
2164
2174
  ): void {
2165
- for (const message of messages) {
2166
- if (excessCounter.value <= 0) return;
2167
- if (!Array.isArray(message.content)) continue;
2168
- for (const block of message.content as Array<ContentBlockParam & CacheControlBlock>) {
2169
- if (excessCounter.value <= 0) return;
2170
- if (!block.cache_control) continue;
2171
- delete block.cache_control;
2172
- excessCounter.value--;
2173
- }
2175
+ if (!cacheControl || cacheMode === "none") return;
2176
+ validateCacheControls(params);
2177
+ if (cacheMode === "automatic") {
2178
+ params.cache_control = { ...cacheControl };
2179
+ return;
2174
2180
  }
2181
+ applyExplicitPromptCaching(params, cacheControl);
2182
+ validateCacheControls(params);
2175
2183
  }
2176
2184
 
2177
- function countCacheControlBreakpoints(params: MessageCreateParamsStreaming): number {
2178
- let total = 0;
2179
- if (params.tools) {
2180
- for (const tool of params.tools as Array<Anthropic.Messages.Tool & CacheControlBlock>) {
2181
- if (tool.cache_control) total++;
2182
- }
2183
- }
2184
- if (params.system && Array.isArray(params.system)) {
2185
- for (const block of params.system as Array<AnthropicSystemBlock & CacheControlBlock>) {
2186
- if (block.cache_control) total++;
2187
- }
2185
+ export function normalizeCacheControlTtlOrdering(params: MessageCreateParamsStreaming): void {
2186
+ validateCacheControls(params as AnthropicCacheParams);
2187
+ }
2188
+
2189
+ function countCacheControlBreakpoints(params: AnthropicCacheParams): number {
2190
+ let total = params.cache_control ? 1 : 0;
2191
+ for (const tool of params.tools ?? []) if ((tool as CacheControlBlock).cache_control) total++;
2192
+ if (Array.isArray(params.system)) {
2193
+ for (const block of params.system) if ((block as CacheControlBlock).cache_control) total++;
2188
2194
  }
2189
2195
  for (const message of params.messages) {
2190
2196
  if (!Array.isArray(message.content)) continue;
2191
- for (const block of message.content as Array<ContentBlockParam & CacheControlBlock>) {
2192
- if (block.cache_control) total++;
2193
- }
2197
+ for (const block of message.content) if ((block as CacheControlBlock).cache_control) total++;
2194
2198
  }
2195
2199
  return total;
2196
2200
  }
2197
2201
 
2198
2202
  function enforceCacheControlLimit(params: MessageCreateParamsStreaming, maxBreakpoints: number): void {
2199
- const total = countCacheControlBreakpoints(params);
2200
- if (total <= maxBreakpoints) return;
2201
- const excessCounter = { value: total - maxBreakpoints };
2202
- const systemBlocks =
2203
- params.system && Array.isArray(params.system)
2204
- ? (params.system as Array<AnthropicSystemBlock & CacheControlBlock>)
2205
- : [];
2206
- const toolBlocks = (params.tools ?? []) as Array<Anthropic.Messages.Tool & CacheControlBlock>;
2207
- const lastSystemIndex = findLastCacheControlIndex(systemBlocks);
2208
- const lastToolIndex = findLastCacheControlIndex(toolBlocks);
2209
- if (systemBlocks.length > 0) {
2210
- stripCacheControlExceptIndex(systemBlocks, lastSystemIndex, excessCounter);
2211
- }
2212
- if (excessCounter.value <= 0) return;
2213
- if (toolBlocks.length > 0) {
2214
- stripCacheControlExceptIndex(toolBlocks, lastToolIndex, excessCounter);
2215
- }
2216
- if (excessCounter.value <= 0) return;
2217
- stripMessageCacheControl(params.messages, excessCounter);
2218
- if (excessCounter.value <= 0) return;
2219
- if (systemBlocks.length > 0) {
2220
- stripAllCacheControl(systemBlocks, excessCounter);
2221
- }
2222
- if (excessCounter.value <= 0) return;
2223
- if (toolBlocks.length > 0) {
2224
- stripAllCacheControl(toolBlocks, excessCounter);
2225
- }
2203
+ if (maxBreakpoints !== 4) throw new Error("Anthropic supports exactly four cache breakpoints");
2204
+ validateCacheControls(params as AnthropicCacheParams);
2226
2205
  }
2227
2206
  function buildParams(
2228
2207
  model: Model<"anthropic-messages">,
@@ -2233,7 +2212,8 @@ function buildParams(
2233
2212
  disableStrictTools = false,
2234
2213
  repairLatestAssistantThinking = false,
2235
2214
  ): MessageCreateParamsStreaming {
2236
- const { cacheControl } = getCacheControl(model, baseUrl, options?.cacheRetention);
2215
+ const { mode: cacheMode, cacheControl } = getCacheControl(model, baseUrl, options?.cacheRetention);
2216
+
2237
2217
  const params: AnthropicSamplingParams = {
2238
2218
  model: model.id,
2239
2219
  messages: convertAnthropicMessages(context.messages, model, isOAuthToken, { repairLatestAssistantThinking }),
@@ -2364,7 +2344,7 @@ function buildParams(
2364
2344
  }
2365
2345
  disableThinkingIfToolChoiceForced(params);
2366
2346
  ensureMaxTokensForThinking(params, model);
2367
- applyPromptCaching(params, cacheControl);
2347
+ applyPromptCaching(params as AnthropicCacheParams, cacheMode, cacheControl);
2368
2348
  enforceCacheControlLimit(params, 4);
2369
2349
  normalizeCacheControlTtlOrdering(params);
2370
2350