MCPcopy Create free account
hub / github.com/coder/mux / applyCacheControlToTools

Function applyCacheControlToTools

src/common/utils/ai/cacheStrategy.ts:153–220  ·  view source on GitHub ↗
(
  tools: T,
  modelString: string,
  cacheTtl?: AnthropicCacheTtl | null
)

Source from the content-addressed store, hash-verified

151 * attaching providerOptions.
152 */
153export function applyCacheControlToTools<T extends Record<string, Tool>>(
154 tools: T,
155 modelString: string,
156 cacheTtl?: AnthropicCacheTtl | null
157): T {
158 // Only apply cache control for Anthropic models
159 if (!supportsAnthropicCache(modelString) || !tools || Object.keys(tools).length === 0) {
160 return tools;
161 }
162
163 // Get the last tool key (tools are ordered, last one gets cached)
164 const toolKeys = Object.keys(tools);
165 const lastToolKey = toolKeys[toolKeys.length - 1];
166
167 const cacheOpts = cacheTtl ? anthropicCacheControl(cacheTtl) : ANTHROPIC_CACHE_CONTROL;
168
169 // Clone tools and add cache control ONLY to the last tool
170 // Anthropic caches everything up to the cache breakpoint, so marking
171 // only the last tool will cache all tools
172 const cachedTools = {} as unknown as T;
173 for (const [key, existingTool] of Object.entries(tools)) {
174 if (key === lastToolKey) {
175 if (isProviderNativeTool(existingTool)) {
176 // Provider-native tools (e.g. Anthropic/OpenAI web search) cannot be recreated with
177 // createTool(). Clone while preserving descriptors/getters and attach providerOptions.
178 const cachedProviderTool = cloneToolPreservingDescriptors(
179 existingTool
180 ) as ProviderNativeTool;
181 cachedProviderTool.providerOptions = cacheOpts;
182 cachedTools[key as keyof T] = cachedProviderTool as unknown as T[keyof T];
183 } else if (existingTool.execute == null) {
184 // Some MCP/dynamic tools are valid without execute handlers (provider-/client-executed).
185 // Keep their runtime shape and attach cache control without forcing recreation.
186 const cachedDynamicTool = cloneToolPreservingDescriptors(existingTool);
187 cachedDynamicTool.providerOptions = cacheOpts;
188 cachedTools[key as keyof T] = cachedDynamicTool as unknown as T[keyof T];
189 } else {
190 assert(
191 existingTool.execute != null,
192 `Tool "${key}" must define execute before cache control is applied`
193 );
194
195 // Function tools with execute handlers: re-create with providerOptions (SDK requires this at creation time)
196 const cachedTool = createTool({
197 description: existingTool.description,
198 inputSchema: existingTool.inputSchema,
199 execute: existingTool.execute,
200 providerOptions: cacheOpts,
201 });
202 // createTool() returns a fresh object that drops any extra own symbol markers attached
203 // to the original (e.g. the built-in task-tool marker that lets sibling explore tasks run
204 // in parallel). Copy them over so downstream wrappers still recognize the recreated tool.
205 for (const marker of Object.getOwnPropertySymbols(existingTool)) {
206 const descriptor = Object.getOwnPropertyDescriptor(existingTool, marker);
207 if (descriptor) {
208 Object.defineProperty(cachedTool, marker, descriptor);
209 }
210 }

Callers 2

Calls 5

supportsAnthropicCacheFunction · 0.85
anthropicCacheControlFunction · 0.85
isProviderNativeToolFunction · 0.85
assertFunction · 0.50

Tested by

no test coverage detected