@gajae-code/ai 0.11.1 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/types/auth-storage.d.ts +2 -0
- package/dist/types/model-manager.d.ts +2 -0
- package/dist/types/providers/anthropic.d.ts +2 -1
- package/dist/types/providers/pi-native-server.d.ts +9 -17
- package/dist/types/types.d.ts +33 -0
- package/dist/types/utils/fallback-transport.d.ts +13 -2
- package/dist/types/utils/overflow.d.ts +4 -54
- package/package.json +2 -2
- package/src/auth-storage.ts +14 -3
- package/src/model-manager.ts +22 -14
- package/src/providers/anthropic-messages-server.ts +178 -17
- package/src/providers/anthropic.ts +193 -213
- package/src/providers/openai-chat-server.ts +93 -5
- package/src/providers/openai-codex-responses.ts +76 -13
- package/src/providers/openai-responses-server.ts +111 -36
- package/src/providers/openai-responses-shared.ts +71 -12
- package/src/providers/pi-native-server.ts +273 -20
- package/src/types.ts +19 -0
- package/src/utils/event-stream.ts +9 -5
- package/src/utils/fallback-transport.ts +111 -27
- package/src/utils/overflow.ts +72 -31
|
@@ -435,27 +435,31 @@ function getCacheControl(
|
|
|
435
435
|
model: Model<"anthropic-messages">,
|
|
436
436
|
baseUrl: string,
|
|
437
437
|
cacheRetention?: CacheRetention,
|
|
438
|
-
): {
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
458
|
-
cacheControl: {
|
|
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
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
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
|
-
|
|
2035
|
-
let cacheBreakpointsUsed = 0;
|
|
2056
|
+
type AnthropicCacheMode = "automatic" | "explicit" | "none";
|
|
2036
2057
|
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
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
|
-
|
|
2063
|
+
function cacheControlError(path: string, reason: string): Error {
|
|
2064
|
+
return new Error(`Invalid Anthropic cache_control at ${path}: ${reason}`);
|
|
2065
|
+
}
|
|
2043
2066
|
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2108
|
-
delete cacheControl.ttl;
|
|
2109
|
-
}
|
|
2078
|
+
seenFiveMinute.value = true;
|
|
2110
2079
|
}
|
|
2111
2080
|
|
|
2112
|
-
function
|
|
2081
|
+
function validateCacheControls(params: AnthropicCacheParams): void {
|
|
2113
2082
|
const seenFiveMinute = { value: false };
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
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 (
|
|
2120
|
-
for (const block of params.system
|
|
2121
|
-
|
|
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
|
|
2127
|
-
|
|
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
|
|
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
|
-
|
|
2121
|
+
const block = blocks[index];
|
|
2122
|
+
if (!isCacheableContentBlock(block)) continue;
|
|
2123
|
+
blocks[index] = { ...block, cache_control: { ...cacheControl } };
|
|
2124
|
+
return true;
|
|
2135
2125
|
}
|
|
2136
|
-
return
|
|
2126
|
+
return false;
|
|
2137
2127
|
}
|
|
2138
2128
|
|
|
2139
|
-
function
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
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
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
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
|
|
2162
|
-
|
|
2163
|
-
|
|
2170
|
+
function applyPromptCaching(
|
|
2171
|
+
params: AnthropicCacheParams,
|
|
2172
|
+
cacheMode: AnthropicCacheMode,
|
|
2173
|
+
cacheControl?: AnthropicCacheControl,
|
|
2164
2174
|
): void {
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
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
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
if (
|
|
2185
|
-
for (const block of params.system
|
|
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
|
|
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
|
-
|
|
2200
|
-
|
|
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
|
|