agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
115 lines • 5.51 kB
TypeScript
/**
* anthropicCacheWire — the cache↔wire translation both Anthropic adapters share.
*
* Pattern: extracted helper (was module-private inside BrowserAnthropicProvider).
* Role: Outer ring. Three jobs, one file:
* 1. `ANTHROPIC_PROMPT_CACHING` — what every adapter on this wire DECLARES
* (`LLMProvider.promptCaching`): explicit breakpoints, four per request,
* usage reported. The agent chooses its cache strategy from it.
* 2. `applyCacheMarkers` — stamp `cache_control` onto an Anthropic request
* body where the framework's `CacheMarker`s point.
* 3. `readCacheUsage` — lift Anthropic's cache token counts off a response
* usage payload into the port's `cacheRead` / `cacheWrite` fields.
*
* Why it exists: the server adapter (`AnthropicProvider`) used to drop
* `LLMRequest.cacheMarkers` on the floor and report no cache tokens, so on
* the server path a byte-identical prompt prefix could neither be cached nor
* even OBSERVED as uncached — only the browser half kept its side of the
* contract. One module serving every adapter on this wire removes the gap
* and prevents byte-twin drift between them. The declaration lives here for
* the same reason: the three adapters that build this body
* (`anthropic()`, `browserAnthropic()`, `invokeModelGateway()`) make one
* promise, written once.
*
* @example
* const indexMap: number[] = [];
* const body = { model, max_tokens, messages: toAnthropicMessages(req.messages, indexMap) };
* if (req.cacheMarkers?.length) applyCacheMarkers(body, req.cacheMarkers, indexMap);
* // ...and on the way back:
* return { usage: { input, output, ...readCacheUsage(message.usage) } };
*/
import type { PromptCaching } from '../../cache/types.js';
/**
* What an adapter on the Anthropic Messages wire promises about prompt
* caching: it writes `cache_control` where the request's markers point
* (`applyCacheMarkers`), the API honours at most four such breakpoints per
* request, and the response's cache token counts reach the port
* (`readCacheUsage`).
*
* Declared by the adapter, not looked up by its name — so `withRetry(anthropic())`
* or an application's own wrapper keeps caching by forwarding this one field.
*/
export declare const ANTHROPIC_PROMPT_CACHING: PromptCaching;
/**
* Where each request message ended up in the provider body — `-1` for one
* that did not survive the transform.
*
* This exists because the two arrays are NOT the same length or the same
* order: a `role: 'system'` message is dropped (system is a separate
* top-level field) and consecutive `role: 'tool'` messages are coalesced
* into ONE user turn. A `CacheMarker{field:'messages'}` names a position in
* `LLMRequest.messages`, so without this map it would be applied by ordinal
* into a differently-indexed array and land on the wrong turn — silently,
* and further off the more tool round-trips the conversation has had.
*/
export type MessageIndexMap = readonly number[];
/**
* The slice of an Anthropic request body that markers touch. Structural on
* purpose: each adapter keeps its own full body interface, and this module
* must not become a third place that re-declares the whole wire.
*/
interface MarkableBody {
system?: unknown;
tools?: unknown[];
messages: Array<{
role: string;
content: unknown;
}>;
}
/**
* Apply `CacheMarker[]` to an Anthropic request body in-place.
*
* Per-field positional rules (Anthropic API):
* - `system`: convert from `string` → array of text blocks; mark the block
* with `cache_control`. The body carries the whole system prompt in ONE
* block today, so any system marker caches the whole prompt.
* - `tools`: mark the tool at `boundaryIndex` (clamped to last tool).
* - `messages`: mark the LAST content block of the LAST message in the
* cacheable prefix. Anthropic only honors `cache_control` there.
*
* `messageIndexMap` translates the marker's index — a position in
* `LLMRequest.messages` — into a position in `body.messages`. A message that
* did not survive the transform (`-1`, a system message) cannot be marked at
* all; silently marking its neighbour would claim a boundary nobody asked for.
*
* Markers arrive already clamped to the declared `maxBreakpoints` (four, see
* `ANTHROPIC_PROMPT_CACHING`) by `BreakpointCacheStrategy`, so no enforcement
* here.
*/
export declare function applyCacheMarkers(body: MarkableBody, markers: readonly {
field: string;
boundaryIndex: number;
ttl: 'short' | 'long';
}[], messageIndexMap?: MessageIndexMap): void;
/**
* Anthropic reports cache traffic on the response usage object:
* `cache_read_input_tokens` (prefix served from cache) and
* `cache_creation_input_tokens` (prefix written to cache this call).
* Both are ABSENT when the request carried no cache_control.
*/
export interface AnthropicUsageWire {
readonly cache_read_input_tokens?: number;
readonly cache_creation_input_tokens?: number;
}
/**
* Lift Anthropic's cache counts into the port's optional `cacheRead` /
* `cacheWrite` fields. Absent stays absent — an adapter must never invent a
* zero for a number the provider did not report (that is the difference
* between "no cache traffic" and "nobody measured").
*/
export declare function readCacheUsage(usage: AnthropicUsageWire | undefined): {
cacheRead?: number;
cacheWrite?: number;
};
export {};
//# sourceMappingURL=anthropicCacheWire.d.ts.map