UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

108 lines • 5.18 kB
/** * 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 * ``` */ import { AutomaticCacheStrategy } from './strategies/AutomaticCacheStrategy.js'; import { BreakpointCacheStrategy } from './strategies/BreakpointCacheStrategy.js'; import { NoOpCacheStrategy } from './strategies/NoOpCacheStrategy.js'; /** One shared instance: the no-op strategy holds no state. */ const NO_CACHING = new 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. */ export 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(declared, who) : new AutomaticCacheStrategy(declared, who); } /** 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); * ``` */ export 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; } 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