fix(ai): cache Anthropic tools separately from transcript closes #3260

This commit is contained in:
Mario Zechner
2026-04-16 11:31:58 +02:00
parent e189b23964
commit 1c016cb018
2 changed files with 19 additions and 9 deletions

View File

@@ -2,6 +2,10 @@
## [Unreleased] ## [Unreleased]
### Changed
- Changed Anthropic prompt caching to add a `cache_control` breakpoint on the last tool definition, so tool schemas can be cached independently from transcript updates while preserving existing cache retention behavior ([#3260](https://github.com/badlogic/pi-mono/issues/3260))
## [0.67.3] - 2026-04-15 ## [0.67.3] - 2026-04-15
### Fixed ### Fixed

View File

@@ -1,5 +1,6 @@
import Anthropic from "@anthropic-ai/sdk"; import Anthropic from "@anthropic-ai/sdk";
import type { import type {
CacheControlEphemeral,
ContentBlockParam, ContentBlockParam,
MessageCreateParamsStreaming, MessageCreateParamsStreaming,
MessageParam, MessageParam,
@@ -49,7 +50,7 @@ function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention
function getCacheControl( function getCacheControl(
baseUrl: string, baseUrl: string,
cacheRetention?: CacheRetention, cacheRetention?: CacheRetention,
): { retention: CacheRetention; cacheControl?: { type: "ephemeral"; ttl?: "1h" } } { ): { retention: CacheRetention; cacheControl?: CacheControlEphemeral } {
const retention = resolveCacheRetention(cacheRetention); const retention = resolveCacheRetention(cacheRetention);
if (retention === "none") { if (retention === "none") {
return { retention }; return { retention };
@@ -657,7 +658,7 @@ function buildParams(
} }
if (context.tools) { if (context.tools) {
params.tools = convertTools(context.tools, isOAuthToken); params.tools = convertTools(context.tools, isOAuthToken, cacheControl);
} }
// Configure thinking mode: adaptive (Opus 4.6 and Sonnet 4.6), // Configure thinking mode: adaptive (Opus 4.6 and Sonnet 4.6),
@@ -709,7 +710,7 @@ function convertMessages(
messages: Message[], messages: Message[],
model: Model<"anthropic-messages">, model: Model<"anthropic-messages">,
isOAuthToken: boolean, isOAuthToken: boolean,
cacheControl?: { type: "ephemeral"; ttl?: "1h" }, cacheControl?: CacheControlEphemeral,
): MessageParam[] { ): MessageParam[] {
const params: MessageParam[] = []; const params: MessageParam[] = [];
@@ -870,20 +871,25 @@ function convertMessages(
return params; return params;
} }
function convertTools(tools: Tool[], isOAuthToken: boolean): Anthropic.Messages.Tool[] { function convertTools(
tools: Tool[],
isOAuthToken: boolean,
cacheControl?: CacheControlEphemeral,
): Anthropic.Messages.Tool[] {
if (!tools) return []; if (!tools) return [];
return tools.map((tool) => { return tools.map((tool, index) => {
const jsonSchema = tool.parameters as any; // TypeBox already generates JSON Schema const schema = tool.parameters as { properties?: unknown; required?: string[] };
return { return {
name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name, name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
description: tool.description, description: tool.description,
input_schema: { input_schema: {
type: "object" as const, type: "object",
properties: jsonSchema.properties || {}, properties: schema.properties ?? {},
required: jsonSchema.required || [], required: schema.required ?? [],
}, },
...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),
}; };
}); });
} }