agentfootprint 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/dist/adapters/llm/BrowserAnthropicProvider.js +68 -0
- package/dist/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
- package/dist/cache/CacheDecisionSubflow.js +172 -0
- package/dist/cache/CacheDecisionSubflow.js.map +1 -0
- package/dist/cache/CacheGateDecider.js +122 -0
- package/dist/cache/CacheGateDecider.js.map +1 -0
- package/dist/cache/applyCachePolicy.js +55 -0
- package/dist/cache/applyCachePolicy.js.map +1 -0
- package/dist/cache/cacheRecorder.js +120 -0
- package/dist/cache/cacheRecorder.js.map +1 -0
- package/dist/cache/index.js +47 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/strategies/AnthropicCacheStrategy.js +102 -0
- package/dist/cache/strategies/AnthropicCacheStrategy.js.map +1 -0
- package/dist/cache/strategies/BedrockCacheStrategy.js +81 -0
- package/dist/cache/strategies/BedrockCacheStrategy.js.map +1 -0
- package/dist/cache/strategies/NoOpCacheStrategy.js +40 -0
- package/dist/cache/strategies/NoOpCacheStrategy.js.map +1 -0
- package/dist/cache/strategies/OpenAICacheStrategy.js +75 -0
- package/dist/cache/strategies/OpenAICacheStrategy.js.map +1 -0
- package/dist/cache/strategyRegistry.js +80 -0
- package/dist/cache/strategyRegistry.js.map +1 -0
- package/dist/cache/types.js +25 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/conventions.js +18 -0
- package/dist/conventions.js.map +1 -1
- package/dist/core/Agent.js +173 -4
- package/dist/core/Agent.js.map +1 -1
- package/dist/esm/adapters/llm/BrowserAnthropicProvider.js +68 -0
- package/dist/esm/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
- package/dist/esm/cache/CacheDecisionSubflow.js +166 -0
- package/dist/esm/cache/CacheDecisionSubflow.js.map +1 -0
- package/dist/esm/cache/CacheGateDecider.js +116 -0
- package/dist/esm/cache/CacheGateDecider.js.map +1 -0
- package/dist/esm/cache/applyCachePolicy.js +50 -0
- package/dist/esm/cache/applyCachePolicy.js.map +1 -0
- package/dist/esm/cache/cacheRecorder.js +116 -0
- package/dist/esm/cache/cacheRecorder.js.map +1 -0
- package/dist/esm/cache/index.js +36 -0
- package/dist/esm/cache/index.js.map +1 -0
- package/dist/esm/cache/strategies/AnthropicCacheStrategy.js +98 -0
- package/dist/esm/cache/strategies/AnthropicCacheStrategy.js.map +1 -0
- package/dist/esm/cache/strategies/BedrockCacheStrategy.js +77 -0
- package/dist/esm/cache/strategies/BedrockCacheStrategy.js.map +1 -0
- package/dist/esm/cache/strategies/NoOpCacheStrategy.js +36 -0
- package/dist/esm/cache/strategies/NoOpCacheStrategy.js.map +1 -0
- package/dist/esm/cache/strategies/OpenAICacheStrategy.js +71 -0
- package/dist/esm/cache/strategies/OpenAICacheStrategy.js.map +1 -0
- package/dist/esm/cache/strategyRegistry.js +73 -0
- package/dist/esm/cache/strategyRegistry.js.map +1 -0
- package/dist/esm/cache/types.js +24 -0
- package/dist/esm/cache/types.js.map +1 -0
- package/dist/esm/conventions.js +18 -0
- package/dist/esm/conventions.js.map +1 -1
- package/dist/esm/core/Agent.js +173 -4
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/factories/defineFact.js +3 -0
- package/dist/esm/lib/injection-engine/factories/defineFact.js.map +1 -1
- package/dist/esm/lib/injection-engine/factories/defineInstruction.js +3 -0
- package/dist/esm/lib/injection-engine/factories/defineInstruction.js.map +1 -1
- package/dist/esm/lib/injection-engine/factories/defineSkill.js +5 -0
- package/dist/esm/lib/injection-engine/factories/defineSkill.js.map +1 -1
- package/dist/esm/lib/injection-engine/factories/defineSteering.js +3 -0
- package/dist/esm/lib/injection-engine/factories/defineSteering.js.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/injection-engine/factories/defineFact.js +3 -0
- package/dist/lib/injection-engine/factories/defineFact.js.map +1 -1
- package/dist/lib/injection-engine/factories/defineInstruction.js +3 -0
- package/dist/lib/injection-engine/factories/defineInstruction.js.map +1 -1
- package/dist/lib/injection-engine/factories/defineSkill.js +5 -0
- package/dist/lib/injection-engine/factories/defineSkill.js.map +1 -1
- package/dist/lib/injection-engine/factories/defineSteering.js +3 -0
- package/dist/lib/injection-engine/factories/defineSteering.js.map +1 -1
- package/dist/types/adapters/types.d.ts +11 -0
- package/dist/types/adapters/types.d.ts.map +1 -1
- package/dist/types/cache/CacheDecisionSubflow.d.ts +89 -0
- package/dist/types/cache/CacheDecisionSubflow.d.ts.map +1 -0
- package/dist/types/cache/CacheGateDecider.d.ts +114 -0
- package/dist/types/cache/CacheGateDecider.d.ts.map +1 -0
- package/dist/types/cache/applyCachePolicy.d.ts +38 -0
- package/dist/types/cache/applyCachePolicy.d.ts.map +1 -0
- package/dist/types/cache/cacheRecorder.d.ts +86 -0
- package/dist/types/cache/cacheRecorder.d.ts.map +1 -0
- package/dist/types/cache/index.d.ts +34 -0
- package/dist/types/cache/index.d.ts.map +1 -0
- package/dist/types/cache/strategies/AnthropicCacheStrategy.d.ts +39 -0
- package/dist/types/cache/strategies/AnthropicCacheStrategy.d.ts.map +1 -0
- package/dist/types/cache/strategies/BedrockCacheStrategy.d.ts +34 -0
- package/dist/types/cache/strategies/BedrockCacheStrategy.d.ts.map +1 -0
- package/dist/types/cache/strategies/NoOpCacheStrategy.d.ts +30 -0
- package/dist/types/cache/strategies/NoOpCacheStrategy.d.ts.map +1 -0
- package/dist/types/cache/strategies/OpenAICacheStrategy.d.ts +37 -0
- package/dist/types/cache/strategies/OpenAICacheStrategy.d.ts.map +1 -0
- package/dist/types/cache/strategyRegistry.d.ts +46 -0
- package/dist/types/cache/strategyRegistry.d.ts.map +1 -0
- package/dist/types/cache/types.d.ts +244 -0
- package/dist/types/cache/types.d.ts.map +1 -0
- package/dist/types/conventions.d.ts +18 -0
- package/dist/types/conventions.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +86 -2
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/factories/defineFact.d.ts +9 -0
- package/dist/types/lib/injection-engine/factories/defineFact.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/factories/defineInstruction.d.ts +11 -0
- package/dist/types/lib/injection-engine/factories/defineInstruction.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/factories/defineSkill.d.ts +15 -0
- package/dist/types/lib/injection-engine/factories/defineSkill.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/factories/defineSteering.d.ts +12 -0
- package/dist/types/lib/injection-engine/factories/defineSteering.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CacheDecision subflow — provider-agnostic translation from
|
|
3
|
+
* `activeInjections + DSL directives` → `CacheMarker[]`.
|
|
4
|
+
*
|
|
5
|
+
* This is the core "policy → markers" Lego layer. It runs every
|
|
6
|
+
* iteration (after slot subflows produce their output, before the
|
|
7
|
+
* CacheGate decider). Pure transform: no IO, no LLM calls, no
|
|
8
|
+
* provider knowledge.
|
|
9
|
+
*
|
|
10
|
+
* Algorithm:
|
|
11
|
+
* 1. Build a `CachePolicyContext` from agent state
|
|
12
|
+
* 2. For each injection in `activeInjections`, evaluate its
|
|
13
|
+
* `metadata.cache` directive against the context → cacheable boolean
|
|
14
|
+
* 3. For each slot (system / tools / messages):
|
|
15
|
+
* a. Walk the slot's contributions in order
|
|
16
|
+
* b. Find the LAST index that's contiguous-from-start cacheable
|
|
17
|
+
* c. Emit one CacheMarker at that boundary if any cacheable
|
|
18
|
+
*
|
|
19
|
+
* Each marker is provider-agnostic. Provider strategy translates
|
|
20
|
+
* to wire format in Phase 6+.
|
|
21
|
+
*
|
|
22
|
+
* Special case — base system prompt: the agent's
|
|
23
|
+
* `agent.getSystemPromptCachePolicy()` value is folded in at index 0
|
|
24
|
+
* of the system slot. Always-on injections (Steering / Fact /
|
|
25
|
+
* always-active rules) follow.
|
|
26
|
+
*/
|
|
27
|
+
import { flowChart } from 'footprintjs';
|
|
28
|
+
/**
|
|
29
|
+
* Evaluate a `CachePolicy` against the current context.
|
|
30
|
+
* Returns `true` if the policy says THIS iteration's content is cacheable.
|
|
31
|
+
*/
|
|
32
|
+
export function evaluateCachePolicy(policy, ctx) {
|
|
33
|
+
if (policy === 'always')
|
|
34
|
+
return true;
|
|
35
|
+
if (policy === 'never')
|
|
36
|
+
return false;
|
|
37
|
+
if (policy === 'while-active') {
|
|
38
|
+
// Membership in `activeInjections` IS being-active. By the time
|
|
39
|
+
// the subflow walks an injection, the InjectionEngine has already
|
|
40
|
+
// confirmed it's active for THIS iteration. So 'while-active'
|
|
41
|
+
// policy → cacheable while in the list.
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (typeof policy === 'object' && policy !== null && 'until' in policy) {
|
|
45
|
+
// Cache UNTIL predicate returns true. So cacheable iff !predicate.
|
|
46
|
+
try {
|
|
47
|
+
return !policy.until(ctx);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Failing predicates are treated as "do not cache" — fail-closed.
|
|
51
|
+
// Avoids the failure mode where a buggy predicate accidentally
|
|
52
|
+
// caches volatile content.
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Unknown policy form — fail-closed (don't cache).
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Identify which slots an injection contributes to. An injection can
|
|
61
|
+
* target multiple slots simultaneously (Skills target both system +
|
|
62
|
+
* tools); we visit each contributing slot independently.
|
|
63
|
+
*/
|
|
64
|
+
export function injectionTargetSlots(injection) {
|
|
65
|
+
const slots = [];
|
|
66
|
+
if (injection.inject.systemPrompt && injection.inject.systemPrompt.length > 0) {
|
|
67
|
+
slots.push('system');
|
|
68
|
+
}
|
|
69
|
+
if (injection.inject.tools && injection.inject.tools.length > 0) {
|
|
70
|
+
slots.push('tools');
|
|
71
|
+
}
|
|
72
|
+
if (injection.inject.messages && injection.inject.messages.length > 0) {
|
|
73
|
+
slots.push('messages');
|
|
74
|
+
}
|
|
75
|
+
return slots;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Pure transform: state → markers. Exported so tests can exercise
|
|
79
|
+
* the algorithm directly without the FlowChartExecutor ceremony of
|
|
80
|
+
* mounting the subflow as a child of a parent chart.
|
|
81
|
+
*
|
|
82
|
+
* The subflow body (`decide` below) is a thin wrapper that pulls
|
|
83
|
+
* state from scope and delegates here.
|
|
84
|
+
*/
|
|
85
|
+
export function computeCacheMarkers(state) {
|
|
86
|
+
// Kill switch short-circuits immediately
|
|
87
|
+
if (state.cachingDisabled)
|
|
88
|
+
return [];
|
|
89
|
+
const ctx = {
|
|
90
|
+
iteration: state.iteration,
|
|
91
|
+
iterationsRemaining: Math.max(0, state.maxIterations - state.iteration),
|
|
92
|
+
userMessage: state.userMessage,
|
|
93
|
+
...(state.lastToolName !== undefined && { lastToolName: state.lastToolName }),
|
|
94
|
+
cumulativeInputTokens: state.cumulativeInputTokens,
|
|
95
|
+
};
|
|
96
|
+
const perSlot = {
|
|
97
|
+
system: [],
|
|
98
|
+
tools: [],
|
|
99
|
+
messages: [],
|
|
100
|
+
};
|
|
101
|
+
// Index 0 of system slot is the base system prompt
|
|
102
|
+
perSlot.system.push({
|
|
103
|
+
cacheable: evaluateCachePolicy(state.systemPromptCachePolicy, ctx),
|
|
104
|
+
reason: 'base system prompt',
|
|
105
|
+
});
|
|
106
|
+
// Walk each active injection
|
|
107
|
+
for (const inj of state.activeInjections) {
|
|
108
|
+
const policy = inj.metadata?.cache ?? 'never';
|
|
109
|
+
const cacheable = evaluateCachePolicy(policy, ctx);
|
|
110
|
+
const reason = `${inj.flavor}:${inj.id}`;
|
|
111
|
+
for (const slot of injectionTargetSlots(inj)) {
|
|
112
|
+
perSlot[slot].push({ cacheable, reason });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Find per-slot last-contiguous-cacheable boundary; emit a marker per
|
|
116
|
+
// slot that has at least one cacheable entry from index 0.
|
|
117
|
+
const markers = [];
|
|
118
|
+
for (const slot of ['system', 'tools', 'messages']) {
|
|
119
|
+
const entries = perSlot[slot];
|
|
120
|
+
let boundary = -1;
|
|
121
|
+
let lastReason = '';
|
|
122
|
+
for (let i = 0; i < entries.length; i++) {
|
|
123
|
+
if (!entries[i].cacheable)
|
|
124
|
+
break;
|
|
125
|
+
boundary = i;
|
|
126
|
+
lastReason = entries[i].reason;
|
|
127
|
+
}
|
|
128
|
+
if (boundary >= 0) {
|
|
129
|
+
markers.push({
|
|
130
|
+
field: slot,
|
|
131
|
+
boundaryIndex: boundary,
|
|
132
|
+
ttl: 'short',
|
|
133
|
+
reason: `${slot} stable prefix (${boundary + 1} entries, ending at ${lastReason})`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return markers;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The decision function. Thin scope-binding wrapper around
|
|
141
|
+
* `computeCacheMarkers`.
|
|
142
|
+
*/
|
|
143
|
+
function decide(scope) {
|
|
144
|
+
scope.cacheMarkers = computeCacheMarkers({
|
|
145
|
+
activeInjections: scope.activeInjections,
|
|
146
|
+
iteration: scope.iteration,
|
|
147
|
+
maxIterations: scope.maxIterations,
|
|
148
|
+
userMessage: scope.userMessage,
|
|
149
|
+
...(scope.lastToolName !== undefined && { lastToolName: scope.lastToolName }),
|
|
150
|
+
cumulativeInputTokens: scope.cumulativeInputTokens,
|
|
151
|
+
systemPromptCachePolicy: scope.systemPromptCachePolicy,
|
|
152
|
+
cachingDisabled: scope.cachingDisabled,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The cache-decision subflow. Mounted into the agent's main chart
|
|
157
|
+
* after the slot subflows (System / Messages / Tools) and before
|
|
158
|
+
* the CacheGate decider stage.
|
|
159
|
+
*
|
|
160
|
+
* Mounted via `addSubFlowChartNext(SUBFLOW_IDS.CACHE_DECISION, ...)`
|
|
161
|
+
* with `arrayMerge: ArrayMergeMode.Replace` on the outputMapper —
|
|
162
|
+
* `cacheMarkers` MUST replace, not concatenate, across iterations
|
|
163
|
+
* (same lesson as the v2.5.1 InjectionEngine fix).
|
|
164
|
+
*/
|
|
165
|
+
export const cacheDecisionSubflow = flowChart('DecideCacheMarkers', decide, 'decide-cache-markers', undefined, 'CacheDecision: walk activeInjections, evaluate cache directives, emit CacheMarker[]').build();
|
|
166
|
+
//# sourceMappingURL=CacheDecisionSubflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CacheDecisionSubflow.js","sourceRoot":"","sources":["../../../src/cache/CacheDecisionSubflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CacheGate — runtime decider that gates cache-marker application.
|
|
3
|
+
*
|
|
4
|
+
* Runs every iteration AFTER the CacheDecision subflow produces
|
|
5
|
+
* `scope.cacheMarkers` and BEFORE the BuildLLMRequest stage applies
|
|
6
|
+
* them. Three rules can fall through to "no-markers" (skip caching);
|
|
7
|
+
* default branch is "apply-markers" (proceed with caching).
|
|
8
|
+
*
|
|
9
|
+
* Why a decider stage and not a function: footprintjs's `decide()`
|
|
10
|
+
* captures evidence on `FlowRecorder.onDecision` natively. The
|
|
11
|
+
* `cacheRecorder()` (Phase 9) reads
|
|
12
|
+
* `event.evidence.rules.find(r => r.matched).inputs[]` to surface
|
|
13
|
+
* WHY caching was applied or skipped each iter. Same channel
|
|
14
|
+
* footprintjs uses for every other decision; same renderer in Lens.
|
|
15
|
+
*
|
|
16
|
+
* Three rules (evaluated top-down; first match wins):
|
|
17
|
+
* 1. Kill switch — `Agent.create({ caching: 'off' })` was set
|
|
18
|
+
* 2. Hit-rate floor — recent hit rate < 30%; cache writes outpacing
|
|
19
|
+
* reads, auto-disable to avoid the cache-write penalty
|
|
20
|
+
* 3. Skill churn — active skills changing too rapidly for caching
|
|
21
|
+
* to amortize (Anthropic LLM expert's concern from Phase 4 review)
|
|
22
|
+
*
|
|
23
|
+
* Default branch (no rule matches): `'apply-markers'`.
|
|
24
|
+
*/
|
|
25
|
+
import { decide } from 'footprintjs';
|
|
26
|
+
/**
|
|
27
|
+
* Hit-rate floor below which we auto-disable caching. The 30% number
|
|
28
|
+
* is calibrated for Anthropic's pricing: cache write costs +25%
|
|
29
|
+
* premium, cache read costs 90% off. Break-even at ~25% hit rate.
|
|
30
|
+
* 30% gives a buffer; below that we're losing money on writes that
|
|
31
|
+
* never recoup.
|
|
32
|
+
*
|
|
33
|
+
* Reasoning: if hit rate is X, cost-per-token vs no caching is
|
|
34
|
+
* (1 - X) * 1.0 + X * 0.1 // baseline
|
|
35
|
+
* minus
|
|
36
|
+
* write_iters * 1.25 + read_iters * 0.1 // with caching
|
|
37
|
+
* Solving for break-even gives X ≈ 0.25 for typical agent shapes.
|
|
38
|
+
*/
|
|
39
|
+
export const HIT_RATE_FLOOR = 0.3;
|
|
40
|
+
/**
|
|
41
|
+
* Window size for skill-churn detection. Last 5 iterations of
|
|
42
|
+
* active skill IDs are inspected.
|
|
43
|
+
*/
|
|
44
|
+
export const SKILL_CHURN_WINDOW = 5;
|
|
45
|
+
/**
|
|
46
|
+
* Threshold above which skill churn is considered detected: this many
|
|
47
|
+
* UNIQUE skills in the rolling window. With window=5 and threshold=3,
|
|
48
|
+
* the pattern A → B → A → C still triggers (3 unique skills in 4 iters).
|
|
49
|
+
*/
|
|
50
|
+
export const SKILL_CHURN_THRESHOLD = 3;
|
|
51
|
+
/**
|
|
52
|
+
* Pure helper: detect skill churn given a rolling history.
|
|
53
|
+
* Exported for direct testing without decider/scope ceremony.
|
|
54
|
+
*/
|
|
55
|
+
export function detectSkillChurn(history, windowSize = SKILL_CHURN_WINDOW, threshold = SKILL_CHURN_THRESHOLD) {
|
|
56
|
+
if (history.length < threshold)
|
|
57
|
+
return false; // not enough history yet
|
|
58
|
+
const recent = history.slice(-windowSize);
|
|
59
|
+
const uniqueSkills = new Set();
|
|
60
|
+
for (const s of recent) {
|
|
61
|
+
if (s !== undefined)
|
|
62
|
+
uniqueSkills.add(s);
|
|
63
|
+
}
|
|
64
|
+
return uniqueSkills.size >= threshold;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The decider function. Mounted via `addDeciderFunction` in the
|
|
68
|
+
* agent's main chart in Phase 6.
|
|
69
|
+
*
|
|
70
|
+
* Returns a `DecisionResult` (footprintjs's `decide()` helper output)
|
|
71
|
+
* which the engine unwraps via `.branch` for routing AND publishes
|
|
72
|
+
* `evidence.rules[matched].inputs[]` to FlowRecorder.onDecision.
|
|
73
|
+
* cacheRecorder (Phase 9) subscribes to that channel for the audit trail.
|
|
74
|
+
*
|
|
75
|
+
* For non-routing consumers (testing the decision in isolation), read
|
|
76
|
+
* the `.branch` field of the returned DecisionResult.
|
|
77
|
+
*/
|
|
78
|
+
export function cacheGateDecide(scope) {
|
|
79
|
+
return decide(scope, [
|
|
80
|
+
{
|
|
81
|
+
when: (s) => s.cachingDisabled === true,
|
|
82
|
+
then: 'no-markers',
|
|
83
|
+
label: "kill switch active (Agent.create({ caching: 'off' }))",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
when: (s) => s.recentHitRate !== undefined && s.recentHitRate < HIT_RATE_FLOOR,
|
|
87
|
+
then: 'no-markers',
|
|
88
|
+
label: `hit rate < ${HIT_RATE_FLOOR * 100}% — auto-disable`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
when: (s) => detectSkillChurn(s.skillHistory),
|
|
92
|
+
then: 'no-markers',
|
|
93
|
+
label: `skill churn (≥${SKILL_CHURN_THRESHOLD} unique skills in last ${SKILL_CHURN_WINDOW} iters)`,
|
|
94
|
+
},
|
|
95
|
+
], 'apply-markers');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Update the skill-history rolling window. Called as a function
|
|
99
|
+
* stage BEFORE the CacheGate decider. Reads the current iteration's
|
|
100
|
+
* active skill (head of `activatedInjectionIds`) and appends to the
|
|
101
|
+
* `skillHistory` array.
|
|
102
|
+
*
|
|
103
|
+
* Window length is bounded at `SKILL_CHURN_WINDOW * 2` so the array
|
|
104
|
+
* doesn't grow unboundedly across long agent runs. Old entries
|
|
105
|
+
* fall off the front naturally.
|
|
106
|
+
*/
|
|
107
|
+
export function updateSkillHistory(scope) {
|
|
108
|
+
const current = scope.activatedInjectionIds?.[0];
|
|
109
|
+
const prior = scope.skillHistory ?? [];
|
|
110
|
+
const next = [...prior, current];
|
|
111
|
+
// Bounded buffer — keep window*2 to give detectSkillChurn room
|
|
112
|
+
// without pinning every prior iteration in memory.
|
|
113
|
+
const trimmed = next.length > SKILL_CHURN_WINDOW * 2 ? next.slice(-SKILL_CHURN_WINDOW * 2) : next;
|
|
114
|
+
scope.skillHistory = trimmed;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=CacheGateDecider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CacheGateDecider.js","sourceRoot":"","sources":["../../../src/cache/CacheGateDecider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,MAAM,EAAwC,MAAM,aAAa,CAAC;AA6B3E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAwC,EACxC,aAAqB,kBAAkB,EACvC,YAAoB,qBAAqB;IAEzC,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,yBAAyB;IACvE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,KAAK,SAAS;YAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,YAAY,CAAC,IAAI,IAAI,SAAS,CAAC;AACxC,CAAC;AAQD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,KAAiC;IAC/D,OAAO,MAAM,CACX,KAAK,EACL;QACE;YACE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,KAAK,IAAI;YACvC,IAAI,EAAE,YAAY;YAClB,KAAK,EAAE,uDAAuD;SAC/D;QACD;YACE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,SAAS,IAAI,CAAC,CAAC,aAAa,GAAG,cAAc;YAC9E,IAAI,EAAE,YAAY;YAClB,KAAK,EAAE,cAAc,cAAc,GAAG,GAAG,kBAAkB;SAC5D;QACD;YACE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY,CAAC;YAC7C,IAAI,EAAE,YAAY;YAClB,KAAK,EAAE,iBAAiB,qBAAqB,0BAA0B,kBAAkB,SAAS;SACnG;KACF,EACD,eAAe,CAChB,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAGE;IAEF,MAAM,OAAO,GAAG,KAAK,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,CAAC;IACjC,+DAA+D;IAC/D,mDAAmD;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,kBAAkB,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,kBAAkB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAClG,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* applyCachePolicy — internal helper for injection factories.
|
|
3
|
+
*
|
|
4
|
+
* Each factory (`defineSkill`, `defineSteering`, `defineFact`,
|
|
5
|
+
* `defineInstruction`, `defineMemory`) calls this to merge the
|
|
6
|
+
* consumer-supplied `cache` option with the flavor-specific default.
|
|
7
|
+
* The merged policy lands in `Injection.metadata.cache` for the
|
|
8
|
+
* CacheDecision subflow (Phase 4) to read back.
|
|
9
|
+
*
|
|
10
|
+
* Why it lives in src/cache/ (not co-located with each factory):
|
|
11
|
+
* - Single source of truth for per-flavor defaults
|
|
12
|
+
* - Tests for the defaults live in one place (test/cache/)
|
|
13
|
+
* - Adding a new factory means adding one entry to the map below;
|
|
14
|
+
* not duplicating the default-resolution logic five times
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Per-flavor default `cache` values when consumer doesn't specify.
|
|
18
|
+
* These match the documentation in `CachePolicy`'s JSDoc — keep
|
|
19
|
+
* synchronized.
|
|
20
|
+
*/
|
|
21
|
+
const FLAVOR_DEFAULTS = Object.freeze({
|
|
22
|
+
steering: 'always',
|
|
23
|
+
fact: 'always',
|
|
24
|
+
skill: 'while-active',
|
|
25
|
+
instruction: 'never',
|
|
26
|
+
memory: 'while-active',
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the effective `cache` policy for an injection.
|
|
30
|
+
*
|
|
31
|
+
* @param flavor - The injection flavor (drives the default if `consumerValue` undefined)
|
|
32
|
+
* @param consumerValue - What the consumer wrote in `cache:` (or undefined)
|
|
33
|
+
* @returns The effective CachePolicy. Always defined.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveCachePolicy(flavor, consumerValue) {
|
|
36
|
+
if (consumerValue !== undefined)
|
|
37
|
+
return consumerValue;
|
|
38
|
+
// Fall back to per-flavor default; if flavor unknown, default is 'never'
|
|
39
|
+
// (conservative — unfamiliar injections shouldn't accidentally cache).
|
|
40
|
+
return FLAVOR_DEFAULTS[flavor] ?? 'never';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read-only access to the per-flavor default table. Exported for
|
|
44
|
+
* tests asserting the documented defaults are wired correctly. Not
|
|
45
|
+
* intended for runtime use — callers should use `resolveCachePolicy`.
|
|
46
|
+
*/
|
|
47
|
+
export function getFlavorDefault(flavor) {
|
|
48
|
+
return FLAVOR_DEFAULTS[flavor] ?? 'never';
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=applyCachePolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applyCachePolicy.js","sourceRoot":"","sources":["../../../src/cache/applyCachePolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;GAIG;AACH,MAAM,eAAe,GAA0C,MAAM,CAAC,MAAM,CAAC;IAC3E,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,cAAc;IACrB,WAAW,EAAE,OAAO;IACpB,MAAM,EAAE,cAAc;CACvB,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAA6C,EAC7C,aAAsC;IAEtC,IAAI,aAAa,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC;IACtD,yEAAyE;IACzE,uEAAuE;IACvE,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cacheRecorder() — observability for the v2.6 cache layer.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to:
|
|
5
|
+
* - `FlowRecorder.onDecision` — captures CacheGate routing decisions
|
|
6
|
+
* (apply-markers / no-markers + the rule that fired + evidence
|
|
7
|
+
* from `decide()`). Read directly from `event.evidence.rules[matched]`
|
|
8
|
+
* since footprintjs already auto-captures predicate `inputs[]`.
|
|
9
|
+
* - `agentfootprint.stream.llm_end` events — read provider's `usage`
|
|
10
|
+
* and call the agent's CacheStrategy.extractMetrics() to normalize
|
|
11
|
+
* into CacheMetrics (cacheReadTokens / cacheWriteTokens / fresh).
|
|
12
|
+
*
|
|
13
|
+
* Produces:
|
|
14
|
+
* - per-iteration `agentfootprint.cache.applied` events (markers
|
|
15
|
+
* applied this iter or empty if skipped) — for Lens trace
|
|
16
|
+
* - per-iteration `agentfootprint.cache.metrics` events (hit/write
|
|
17
|
+
* token counts + estimated dollars via PricingTable) — for
|
|
18
|
+
* dashboards
|
|
19
|
+
* - a turn-end summary printable via `recorder.report()` —
|
|
20
|
+
* numeric tally plus dollars saved
|
|
21
|
+
*
|
|
22
|
+
* v2.6 LIMITATION: doesn't yet write `scope.recentHitRate` back into
|
|
23
|
+
* agent state. CacheGate's hit-rate-floor rule won't fire automatically;
|
|
24
|
+
* consumers can manually wire feedback via `Agent.create(...).attach(rec)`.
|
|
25
|
+
* Full feedback loop deferred to v2.7 (needs an agent-side accessor
|
|
26
|
+
* convention since recorders don't normally write to scope).
|
|
27
|
+
*/
|
|
28
|
+
export function cacheRecorder(options = {}) {
|
|
29
|
+
const perIter = [];
|
|
30
|
+
let lastDecision;
|
|
31
|
+
let iterationCounter = 0;
|
|
32
|
+
function dollars(tokens, kind) {
|
|
33
|
+
if (!options.pricing)
|
|
34
|
+
return 0;
|
|
35
|
+
const model = options.model ?? 'unknown';
|
|
36
|
+
return tokens * options.pricing.pricePerToken(model, kind);
|
|
37
|
+
}
|
|
38
|
+
const handle = {
|
|
39
|
+
id: 'cache-recorder',
|
|
40
|
+
onDecision(event) {
|
|
41
|
+
// Only care about CacheGate decisions; identified by the
|
|
42
|
+
// decider's stage id (the third arg to addDeciderFunction).
|
|
43
|
+
if (event.decider !== 'cache-gate')
|
|
44
|
+
return;
|
|
45
|
+
const matched = event.evidence?.rules.find((r) => r.matched);
|
|
46
|
+
lastDecision = {
|
|
47
|
+
branch: event.chosen,
|
|
48
|
+
...(matched?.label !== undefined && { rule: matched.label }),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
onEmit(event) {
|
|
52
|
+
if (event.type !== 'agentfootprint.stream.llm_end')
|
|
53
|
+
return;
|
|
54
|
+
iterationCounter++;
|
|
55
|
+
const usage = event.payload.usage;
|
|
56
|
+
const metrics = options.strategy?.extractMetrics(usage);
|
|
57
|
+
const branch = lastDecision?.branch ?? 'apply-markers';
|
|
58
|
+
// Compute dollar math:
|
|
59
|
+
// spent = freshInput * inputPrice
|
|
60
|
+
// + cacheRead * cacheReadPrice
|
|
61
|
+
// + cacheWrite * cacheWritePrice
|
|
62
|
+
// no-cache cost = (freshInput + cacheRead + cacheWrite) * inputPrice
|
|
63
|
+
// saved = no-cache cost - spent
|
|
64
|
+
let dollarsSpent = 0;
|
|
65
|
+
let savedVsNoCache = 0;
|
|
66
|
+
if (metrics) {
|
|
67
|
+
dollarsSpent =
|
|
68
|
+
dollars(metrics.freshInputTokens, 'input') +
|
|
69
|
+
dollars(metrics.cacheReadTokens, 'cacheRead') +
|
|
70
|
+
dollars(metrics.cacheWriteTokens, 'cacheWrite');
|
|
71
|
+
const noCacheCost = dollars(metrics.freshInputTokens + metrics.cacheReadTokens + metrics.cacheWriteTokens, 'input');
|
|
72
|
+
savedVsNoCache = noCacheCost - dollarsSpent;
|
|
73
|
+
}
|
|
74
|
+
const entry = {
|
|
75
|
+
iteration: iterationCounter,
|
|
76
|
+
branch,
|
|
77
|
+
...(lastDecision?.rule !== undefined && { rule: lastDecision.rule }),
|
|
78
|
+
...(metrics !== undefined && { metrics }),
|
|
79
|
+
dollarsSpent,
|
|
80
|
+
dollarsSavedVsNoCache: savedVsNoCache,
|
|
81
|
+
};
|
|
82
|
+
perIter.push(entry);
|
|
83
|
+
lastDecision = undefined;
|
|
84
|
+
},
|
|
85
|
+
report() {
|
|
86
|
+
const apply = perIter.filter((p) => p.branch === 'apply-markers').length;
|
|
87
|
+
const skip = perIter.filter((p) => p.branch === 'no-markers').length;
|
|
88
|
+
const cacheRead = perIter.reduce((s, p) => s + (p.metrics?.cacheReadTokens ?? 0), 0);
|
|
89
|
+
const cacheWrite = perIter.reduce((s, p) => s + (p.metrics?.cacheWriteTokens ?? 0), 0);
|
|
90
|
+
const fresh = perIter.reduce((s, p) => s + (p.metrics?.freshInputTokens ?? 0), 0);
|
|
91
|
+
const totalRequest = cacheRead + cacheWrite + fresh;
|
|
92
|
+
const hitRate = totalRequest > 0 ? cacheRead / totalRequest : 0;
|
|
93
|
+
const dollarsSpent = perIter.reduce((s, p) => s + p.dollarsSpent, 0);
|
|
94
|
+
const dollarsSaved = perIter.reduce((s, p) => s + p.dollarsSavedVsNoCache, 0);
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
totalIterations: perIter.length,
|
|
97
|
+
applyMarkersIterations: apply,
|
|
98
|
+
noMarkersIterations: skip,
|
|
99
|
+
cacheReadTokensTotal: cacheRead,
|
|
100
|
+
cacheWriteTokensTotal: cacheWrite,
|
|
101
|
+
freshInputTokensTotal: fresh,
|
|
102
|
+
hitRate,
|
|
103
|
+
estimatedDollarsSpent: dollarsSpent,
|
|
104
|
+
estimatedDollarsSavedVsNoCache: dollarsSaved,
|
|
105
|
+
perIter: Object.freeze([...perIter]),
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
reset() {
|
|
109
|
+
perIter.length = 0;
|
|
110
|
+
lastDecision = undefined;
|
|
111
|
+
iterationCounter = 0;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
return handle;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=cacheRecorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheRecorder.js","sourceRoot":"","sources":["../../../src/cache/cacheRecorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentfootprint/cache — public surface for the cache layer (v2.6+).
|
|
3
|
+
*
|
|
4
|
+
* Importing this module side-effect-registers every built-in cache
|
|
5
|
+
* strategy in the registry. The agentfootprint main barrel imports
|
|
6
|
+
* from here so consumers get the registered strategies without
|
|
7
|
+
* needing to know they exist.
|
|
8
|
+
*
|
|
9
|
+
* Strategies registered as of v2.6:
|
|
10
|
+
* - NoOp (wildcard '*' fallback) — always available, registered by
|
|
11
|
+
* the registry module itself
|
|
12
|
+
* - AnthropicCacheStrategy ('anthropic', 'browser-anthropic')
|
|
13
|
+
*
|
|
14
|
+
* Future strategies (Phase 8+):
|
|
15
|
+
* - OpenAICacheStrategy
|
|
16
|
+
* - BedrockCacheStrategy
|
|
17
|
+
* - GeminiCacheStrategy (v2.7+, async handle-based)
|
|
18
|
+
*
|
|
19
|
+
* Public types (re-exported for consumers):
|
|
20
|
+
* - CachePolicy, CacheMarker, CacheStrategy, CacheCapabilities,
|
|
21
|
+
* CacheMetrics, CachePolicyContext, CacheStrategyContext
|
|
22
|
+
*/
|
|
23
|
+
// Side-effect imports — register strategies on module load.
|
|
24
|
+
import './strategies/AnthropicCacheStrategy.js';
|
|
25
|
+
import './strategies/OpenAICacheStrategy.js';
|
|
26
|
+
import './strategies/BedrockCacheStrategy.js';
|
|
27
|
+
// Strategy registry
|
|
28
|
+
export { getDefaultCacheStrategy, registerCacheStrategy, listRegisteredStrategies, } from './strategyRegistry.js';
|
|
29
|
+
// Built-in strategy classes (for consumers who want explicit overrides)
|
|
30
|
+
export { NoOpCacheStrategy } from './strategies/NoOpCacheStrategy.js';
|
|
31
|
+
export { AnthropicCacheStrategy } from './strategies/AnthropicCacheStrategy.js';
|
|
32
|
+
export { OpenAICacheStrategy } from './strategies/OpenAICacheStrategy.js';
|
|
33
|
+
export { BedrockCacheStrategy } from './strategies/BedrockCacheStrategy.js';
|
|
34
|
+
// Recorder
|
|
35
|
+
export { cacheRecorder } from './cacheRecorder.js';
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,4DAA4D;AAC5D,OAAO,wCAAwC,CAAC;AAChD,OAAO,qCAAqC,CAAC;AAC7C,OAAO,sCAAsC,CAAC;AAa9C,oBAAoB;AACpB,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAE/B,wEAAwE;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAE5E,WAAW;AACX,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnthropicCacheStrategy — translates agnostic CacheMarker[] to
|
|
3
|
+
* Anthropic API's `cache_control: { type: 'ephemeral' }` markers.
|
|
4
|
+
*
|
|
5
|
+
* Anthropic-specific behaviors honored:
|
|
6
|
+
* - **4-marker limit**: Anthropic allows ≤4 cache breakpoints per
|
|
7
|
+
* request. Strategy clamps oversize candidate sets, keeping the
|
|
8
|
+
* first 4 in slot order.
|
|
9
|
+
* - **TTL mapping**: 'short' → default 5min ephemeral; 'long' →
|
|
10
|
+
* `ttl: '1h'` (1-hour beta).
|
|
11
|
+
* - **Provider-side hashing**: this strategy doesn't hash — Anthropic
|
|
12
|
+
* keys cache by exact byte prefix server-side. We don't need
|
|
13
|
+
* content hashes for the v2.6 surface; reserved for v2.7+ if a
|
|
14
|
+
* pre-flight cache-warm-check API ships.
|
|
15
|
+
*
|
|
16
|
+
* What this strategy DOES vs DOESN'T do:
|
|
17
|
+
* - DOES: clamp markers, attach to LLMRequest.cacheMarkers,
|
|
18
|
+
* extract metrics from response.usage
|
|
19
|
+
* - DOES NOT: rewrite the wire body. The provider
|
|
20
|
+
* (BrowserAnthropicProvider) reads `cacheMarkers` and applies
|
|
21
|
+
* `cache_control` blocks during body construction. Separation of
|
|
22
|
+
* concerns: strategy decides WHAT to cache; provider knows HOW
|
|
23
|
+
* to encode on its specific wire.
|
|
24
|
+
*
|
|
25
|
+
* Auto-registers in the strategy registry on module import for
|
|
26
|
+
* provider names: 'anthropic', 'browser-anthropic'.
|
|
27
|
+
*/
|
|
28
|
+
import { registerCacheStrategy } from '../strategyRegistry.js';
|
|
29
|
+
/** Anthropic enforces 4 cache breakpoints per request. */
|
|
30
|
+
const ANTHROPIC_MAX_MARKERS = 4;
|
|
31
|
+
const ANTHROPIC_CAPABILITIES = Object.freeze({
|
|
32
|
+
enabled: true,
|
|
33
|
+
maxMarkers: ANTHROPIC_MAX_MARKERS,
|
|
34
|
+
ttls: ['short', 'long'],
|
|
35
|
+
fields: ['system', 'tools', 'messages'],
|
|
36
|
+
automatic: false,
|
|
37
|
+
});
|
|
38
|
+
export class AnthropicCacheStrategy {
|
|
39
|
+
providerName = 'anthropic';
|
|
40
|
+
capabilities = ANTHROPIC_CAPABILITIES;
|
|
41
|
+
async prepareRequest(req, candidates, ctx) {
|
|
42
|
+
// Honor the agent-side kill switch even if reached this far —
|
|
43
|
+
// belt-and-suspenders. CacheGate should have routed to no-markers
|
|
44
|
+
// already, leaving `candidates` empty, but if a buggy gate lets
|
|
45
|
+
// markers through with cachingDisabled=true, we still respect it.
|
|
46
|
+
if (ctx.cachingDisabled) {
|
|
47
|
+
return { request: req, markersApplied: [] };
|
|
48
|
+
}
|
|
49
|
+
if (candidates.length === 0) {
|
|
50
|
+
return { request: req, markersApplied: [] };
|
|
51
|
+
}
|
|
52
|
+
// Clamp to Anthropic's 4-marker limit. Keep the first N in
|
|
53
|
+
// slot order so we cover the most-stable prefixes (system /
|
|
54
|
+
// always-on injections / tools) before less-stable trailing ones.
|
|
55
|
+
const markersApplied = candidates.length <= ANTHROPIC_MAX_MARKERS
|
|
56
|
+
? candidates
|
|
57
|
+
: candidates.slice(0, ANTHROPIC_MAX_MARKERS);
|
|
58
|
+
const request = {
|
|
59
|
+
...req,
|
|
60
|
+
cacheMarkers: markersApplied,
|
|
61
|
+
};
|
|
62
|
+
return { request, markersApplied };
|
|
63
|
+
}
|
|
64
|
+
extractMetrics(usage) {
|
|
65
|
+
if (!usage || typeof usage !== 'object')
|
|
66
|
+
return undefined;
|
|
67
|
+
const u = usage;
|
|
68
|
+
const cacheRead = u.cache_read_input_tokens ?? 0;
|
|
69
|
+
const cacheWrite = u.cache_creation_input_tokens ?? 0;
|
|
70
|
+
const fresh = u.input_tokens ?? 0;
|
|
71
|
+
// If neither cache field present, response didn't involve caching.
|
|
72
|
+
// Returning undefined signals "no cache info" so cacheRecorder
|
|
73
|
+
// doesn't compute a misleading 0% hit rate.
|
|
74
|
+
if (cacheRead === 0 && cacheWrite === 0)
|
|
75
|
+
return undefined;
|
|
76
|
+
return {
|
|
77
|
+
cacheReadTokens: cacheRead,
|
|
78
|
+
cacheWriteTokens: cacheWrite,
|
|
79
|
+
freshInputTokens: fresh,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Auto-register on module import. Both 'anthropic' (server-side) and
|
|
84
|
+
// 'browser-anthropic' (browser fetch) providers map to this strategy.
|
|
85
|
+
{
|
|
86
|
+
const strategy = new AnthropicCacheStrategy();
|
|
87
|
+
registerCacheStrategy(strategy);
|
|
88
|
+
// Register the browser variant by cloning with the matching provider name.
|
|
89
|
+
// Same behavior, different provider.name match-key.
|
|
90
|
+
const browserStrategy = {
|
|
91
|
+
providerName: 'browser-anthropic',
|
|
92
|
+
capabilities: strategy.capabilities,
|
|
93
|
+
prepareRequest: strategy.prepareRequest.bind(strategy),
|
|
94
|
+
extractMetrics: strategy.extractMetrics.bind(strategy),
|
|
95
|
+
};
|
|
96
|
+
registerCacheStrategy(browserStrategy);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=AnthropicCacheStrategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnthropicCacheStrategy.js","sourceRoot":"","sources":["../../../../src/cache/strategies/AnthropicCacheStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,0DAA0D;AAC1D,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,MAAM,sBAAsB,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC9D,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,qBAAqB;IACjC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,CAAkC;IACxD,MAAM,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAiD;IACvF,SAAS,EAAE,KAAK;CACjB,CAAC,CAAC;AAEH,MAAM,OAAO,sBAAsB;IACxB,YAAY,GAAG,WAAW,CAAC;IAC3B,YAAY,GAAG,sBAAsB,CAAC;IAE/C,KAAK,CAAC,cAAc,CAClB,GAAe,EACf,UAAkC,EAClC,GAAyB;QAKzB,8DAA8D;QAC9D,kEAAkE;QAClE,gEAAgE;QAChE,kEAAkE;QAClE,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC;YACxB,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;QAC9C,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;QAC9C,CAAC;QAED,2DAA2D;QAC3D,4DAA4D;QAC5D,kEAAkE;QAClE,MAAM,cAAc,GAClB,UAAU,CAAC,MAAM,IAAI,qBAAqB;YACxC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;QAEjD,MAAM,OAAO,GAAe;YAC1B,GAAG,GAAG;YACN,YAAY,EAAE,cAAc;SAC7B,CAAC;QACF,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IACrC,CAAC;IAED,cAAc,CAAC,KAAc;QAC3B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,CAAC,GAAG,KAIT,CAAC;QACF,MAAM,SAAS,GAAG,CAAC,CAAC,uBAAuB,IAAI,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,CAAC,CAAC,2BAA2B,IAAI,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;QAClC,mEAAmE;QACnE,+DAA+D;QAC/D,4CAA4C;QAC5C,IAAI,SAAS,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAC1D,OAAO;YACL,eAAe,EAAE,SAAS;YAC1B,gBAAgB,EAAE,UAAU;YAC5B,gBAAgB,EAAE,KAAK;SACxB,CAAC;IACJ,CAAC;CACF;AAED,qEAAqE;AACrE,sEAAsE;AACtE,CAAC;IACC,MAAM,QAAQ,GAAG,IAAI,sBAAsB,EAAE,CAAC;IAC9C,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAChC,2EAA2E;IAC3E,oDAAoD;IACpD,MAAM,eAAe,GAAkB;QACrC,YAAY,EAAE,mBAAmB;QACjC,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;QACtD,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;KACvD,CAAC;IACF,qBAAqB,CAAC,eAAe,CAAC,CAAC;AACzC,CAAC"}
|