agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
113 lines • 5.49 kB
JavaScript
;
/**
* cacheStrategyFor — the cache strategy for a provider, chosen from what the
* provider DECLARES (`LLMProvider.promptCaching`), never from its name.
*
* Pattern: Strategy, selected by capability. The provider adapter declares
* how its wire caches (`'breakpoints'` | `'automatic'` | absent);
* this function maps the declaration to the one strategy that
* serves it.
* Role: the ONE place an agent's cache strategy is chosen (`Agent`, and a
* skill brain whose provider differs from the agent's).
*
* Why not by name: the name is a label, and labels get rewritten. The
* registry this replaces keyed strategies by `provider.name`, so every
* decorator that renamed its provider — `withRetry` (`anthropic+retry`),
* `withFallback` (`a|b`), an application's own routing wrapper
* (`my-app/anthropic`) — silently fell through to the no-op strategy: no
* cache markers, every call at full input price, and nothing in the run to
* say why. A wrapper that forwards the declaration (the library's three
* decorators do) caches exactly like the adapter it wraps.
*
* @example
* ```ts
* import { cacheStrategyFor } from 'agentfootprint/cache';
* import { withRetry } from 'agentfootprint/resilience';
* import { anthropic } from 'agentfootprint/providers';
*
* cacheStrategyFor(withRetry(anthropic())).name; // 'breakpoints'
* cacheStrategyFor({ name: 'x', complete }).name; // 'none' — nothing declared
* ```
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.promptCachingFor = exports.cacheStrategyFor = void 0;
const AutomaticCacheStrategy_js_1 = require("./strategies/AutomaticCacheStrategy.js");
const BreakpointCacheStrategy_js_1 = require("./strategies/BreakpointCacheStrategy.js");
const NoOpCacheStrategy_js_1 = require("./strategies/NoOpCacheStrategy.js");
/** One shared instance: the no-op strategy holds no state. */
const NO_CACHING = new NoOpCacheStrategy_js_1.NoOpCacheStrategy();
/**
* The strategy that serves `provider`'s declared prompt caching for `model`.
* Static declarations keep the one-argument form. For a model-aware adapter,
* pass its model (including its own supported shorthand); omitting it selects
* no caching rather than guessing a model.
*
* @throws TypeError when the declaration is malformed (an unknown `mode`, a
* `maxBreakpoints` that is not a positive integer, a non-boolean
* `reportsUsage`) — refused at agent build, by provider name. A typo that
* quietly fell back to "no caching" would be the very failure this
* function exists to end.
*/
function cacheStrategyFor(provider, model) {
const declared = promptCachingFor(provider, model);
if (declared === undefined)
return NO_CACHING;
const who = `the '${provider.name}' provider`;
return declared.mode === 'breakpoints'
? new BreakpointCacheStrategy_js_1.BreakpointCacheStrategy(declared, who)
: new AutomaticCacheStrategy_js_1.AutomaticCacheStrategy(declared, who);
}
exports.cacheStrategyFor = cacheStrategyFor;
/** Resolve the provider's one declaration for this model. An omitted model
* cannot establish a model-aware capability, so it fails closed. Static
* declarations retain the existing one-argument behavior.
*
* Use this reader instead of accessing `provider.promptCaching.mode`: the
* port also permits a model function. No resolver is duplicated in adapters.
*
* @example
* ```ts
* import { promptCachingFor } from 'agentfootprint/cache';
* const declaration = promptCachingFor(provider, model);
* if (declaration?.mode === 'breakpoints') console.log(declaration.maxBreakpoints);
* ```
*/
function promptCachingFor(provider, model) {
const declaration = provider.promptCaching;
const declared = typeof declaration === 'function'
? model === undefined
? undefined
: declaration(model)
: declaration;
if (declared !== undefined)
assertDeclaration(declared, `the '${provider.name}' provider`);
return declared;
}
exports.promptCachingFor = promptCachingFor;
function assertDeclaration(declared, who) {
const shape = declared;
const refuse = (what) => {
throw new TypeError(`${who} declares promptCaching ${what}. Declare { mode: 'breakpoints', maxBreakpoints, ` +
"reportsUsage } or { mode: 'automatic', reportsUsage } — or leave promptCaching out " +
'if the provider does no caching the agent can drive.');
};
if (shape.mode !== 'breakpoints' && shape.mode !== 'automatic') {
refuse(`with mode ${JSON.stringify(shape.mode)}`);
}
if (shape.mode === 'breakpoints' &&
!(Number.isInteger(shape.maxBreakpoints) && shape.maxBreakpoints > 0)) {
refuse(`with maxBreakpoints ${JSON.stringify(shape.maxBreakpoints)} (a positive integer)`);
}
if (typeof shape.reportsUsage !== 'boolean') {
refuse(`with reportsUsage ${JSON.stringify(shape.reportsUsage)} (true or false)`);
}
if (shape.fields !== undefined &&
(!Array.isArray(shape.fields) ||
shape.fields.length === 0 ||
shape.fields.length > 3 ||
shape.fields.some((field) => !['system', 'tools', 'messages'].includes(field)) ||
new Set(shape.fields).size !== shape.fields.length)) {
refuse('with fields other than a nonempty unique subset of system, tools, messages');
}
}
//# sourceMappingURL=cacheStrategyFor.js.map