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.
Files changed (116) hide show
  1. package/README.md +86 -0
  2. package/dist/adapters/llm/BrowserAnthropicProvider.js +68 -0
  3. package/dist/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
  4. package/dist/cache/CacheDecisionSubflow.js +172 -0
  5. package/dist/cache/CacheDecisionSubflow.js.map +1 -0
  6. package/dist/cache/CacheGateDecider.js +122 -0
  7. package/dist/cache/CacheGateDecider.js.map +1 -0
  8. package/dist/cache/applyCachePolicy.js +55 -0
  9. package/dist/cache/applyCachePolicy.js.map +1 -0
  10. package/dist/cache/cacheRecorder.js +120 -0
  11. package/dist/cache/cacheRecorder.js.map +1 -0
  12. package/dist/cache/index.js +47 -0
  13. package/dist/cache/index.js.map +1 -0
  14. package/dist/cache/strategies/AnthropicCacheStrategy.js +102 -0
  15. package/dist/cache/strategies/AnthropicCacheStrategy.js.map +1 -0
  16. package/dist/cache/strategies/BedrockCacheStrategy.js +81 -0
  17. package/dist/cache/strategies/BedrockCacheStrategy.js.map +1 -0
  18. package/dist/cache/strategies/NoOpCacheStrategy.js +40 -0
  19. package/dist/cache/strategies/NoOpCacheStrategy.js.map +1 -0
  20. package/dist/cache/strategies/OpenAICacheStrategy.js +75 -0
  21. package/dist/cache/strategies/OpenAICacheStrategy.js.map +1 -0
  22. package/dist/cache/strategyRegistry.js +80 -0
  23. package/dist/cache/strategyRegistry.js.map +1 -0
  24. package/dist/cache/types.js +25 -0
  25. package/dist/cache/types.js.map +1 -0
  26. package/dist/conventions.js +18 -0
  27. package/dist/conventions.js.map +1 -1
  28. package/dist/core/Agent.js +173 -4
  29. package/dist/core/Agent.js.map +1 -1
  30. package/dist/esm/adapters/llm/BrowserAnthropicProvider.js +68 -0
  31. package/dist/esm/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
  32. package/dist/esm/cache/CacheDecisionSubflow.js +166 -0
  33. package/dist/esm/cache/CacheDecisionSubflow.js.map +1 -0
  34. package/dist/esm/cache/CacheGateDecider.js +116 -0
  35. package/dist/esm/cache/CacheGateDecider.js.map +1 -0
  36. package/dist/esm/cache/applyCachePolicy.js +50 -0
  37. package/dist/esm/cache/applyCachePolicy.js.map +1 -0
  38. package/dist/esm/cache/cacheRecorder.js +116 -0
  39. package/dist/esm/cache/cacheRecorder.js.map +1 -0
  40. package/dist/esm/cache/index.js +36 -0
  41. package/dist/esm/cache/index.js.map +1 -0
  42. package/dist/esm/cache/strategies/AnthropicCacheStrategy.js +98 -0
  43. package/dist/esm/cache/strategies/AnthropicCacheStrategy.js.map +1 -0
  44. package/dist/esm/cache/strategies/BedrockCacheStrategy.js +77 -0
  45. package/dist/esm/cache/strategies/BedrockCacheStrategy.js.map +1 -0
  46. package/dist/esm/cache/strategies/NoOpCacheStrategy.js +36 -0
  47. package/dist/esm/cache/strategies/NoOpCacheStrategy.js.map +1 -0
  48. package/dist/esm/cache/strategies/OpenAICacheStrategy.js +71 -0
  49. package/dist/esm/cache/strategies/OpenAICacheStrategy.js.map +1 -0
  50. package/dist/esm/cache/strategyRegistry.js +73 -0
  51. package/dist/esm/cache/strategyRegistry.js.map +1 -0
  52. package/dist/esm/cache/types.js +24 -0
  53. package/dist/esm/cache/types.js.map +1 -0
  54. package/dist/esm/conventions.js +18 -0
  55. package/dist/esm/conventions.js.map +1 -1
  56. package/dist/esm/core/Agent.js +173 -4
  57. package/dist/esm/core/Agent.js.map +1 -1
  58. package/dist/esm/index.js +9 -0
  59. package/dist/esm/index.js.map +1 -1
  60. package/dist/esm/lib/injection-engine/factories/defineFact.js +3 -0
  61. package/dist/esm/lib/injection-engine/factories/defineFact.js.map +1 -1
  62. package/dist/esm/lib/injection-engine/factories/defineInstruction.js +3 -0
  63. package/dist/esm/lib/injection-engine/factories/defineInstruction.js.map +1 -1
  64. package/dist/esm/lib/injection-engine/factories/defineSkill.js +5 -0
  65. package/dist/esm/lib/injection-engine/factories/defineSkill.js.map +1 -1
  66. package/dist/esm/lib/injection-engine/factories/defineSteering.js +3 -0
  67. package/dist/esm/lib/injection-engine/factories/defineSteering.js.map +1 -1
  68. package/dist/index.js +9 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/lib/injection-engine/factories/defineFact.js +3 -0
  71. package/dist/lib/injection-engine/factories/defineFact.js.map +1 -1
  72. package/dist/lib/injection-engine/factories/defineInstruction.js +3 -0
  73. package/dist/lib/injection-engine/factories/defineInstruction.js.map +1 -1
  74. package/dist/lib/injection-engine/factories/defineSkill.js +5 -0
  75. package/dist/lib/injection-engine/factories/defineSkill.js.map +1 -1
  76. package/dist/lib/injection-engine/factories/defineSteering.js +3 -0
  77. package/dist/lib/injection-engine/factories/defineSteering.js.map +1 -1
  78. package/dist/types/adapters/types.d.ts +11 -0
  79. package/dist/types/adapters/types.d.ts.map +1 -1
  80. package/dist/types/cache/CacheDecisionSubflow.d.ts +89 -0
  81. package/dist/types/cache/CacheDecisionSubflow.d.ts.map +1 -0
  82. package/dist/types/cache/CacheGateDecider.d.ts +114 -0
  83. package/dist/types/cache/CacheGateDecider.d.ts.map +1 -0
  84. package/dist/types/cache/applyCachePolicy.d.ts +38 -0
  85. package/dist/types/cache/applyCachePolicy.d.ts.map +1 -0
  86. package/dist/types/cache/cacheRecorder.d.ts +86 -0
  87. package/dist/types/cache/cacheRecorder.d.ts.map +1 -0
  88. package/dist/types/cache/index.d.ts +34 -0
  89. package/dist/types/cache/index.d.ts.map +1 -0
  90. package/dist/types/cache/strategies/AnthropicCacheStrategy.d.ts +39 -0
  91. package/dist/types/cache/strategies/AnthropicCacheStrategy.d.ts.map +1 -0
  92. package/dist/types/cache/strategies/BedrockCacheStrategy.d.ts +34 -0
  93. package/dist/types/cache/strategies/BedrockCacheStrategy.d.ts.map +1 -0
  94. package/dist/types/cache/strategies/NoOpCacheStrategy.d.ts +30 -0
  95. package/dist/types/cache/strategies/NoOpCacheStrategy.d.ts.map +1 -0
  96. package/dist/types/cache/strategies/OpenAICacheStrategy.d.ts +37 -0
  97. package/dist/types/cache/strategies/OpenAICacheStrategy.d.ts.map +1 -0
  98. package/dist/types/cache/strategyRegistry.d.ts +46 -0
  99. package/dist/types/cache/strategyRegistry.d.ts.map +1 -0
  100. package/dist/types/cache/types.d.ts +244 -0
  101. package/dist/types/cache/types.d.ts.map +1 -0
  102. package/dist/types/conventions.d.ts +18 -0
  103. package/dist/types/conventions.d.ts.map +1 -1
  104. package/dist/types/core/Agent.d.ts +86 -2
  105. package/dist/types/core/Agent.d.ts.map +1 -1
  106. package/dist/types/index.d.ts +3 -0
  107. package/dist/types/index.d.ts.map +1 -1
  108. package/dist/types/lib/injection-engine/factories/defineFact.d.ts +9 -0
  109. package/dist/types/lib/injection-engine/factories/defineFact.d.ts.map +1 -1
  110. package/dist/types/lib/injection-engine/factories/defineInstruction.d.ts +11 -0
  111. package/dist/types/lib/injection-engine/factories/defineInstruction.d.ts.map +1 -1
  112. package/dist/types/lib/injection-engine/factories/defineSkill.d.ts +15 -0
  113. package/dist/types/lib/injection-engine/factories/defineSkill.d.ts.map +1 -1
  114. package/dist/types/lib/injection-engine/factories/defineSteering.d.ts +12 -0
  115. package/dist/types/lib/injection-engine/factories/defineSteering.d.ts.map +1 -1
  116. 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,SAAS,EAAmC,MAAM,aAAa,CAAC;AAmCzE;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAmB,EAAE,GAAuB;IAC9E,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACrC,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;QAC9B,gEAAgE;QAChE,kEAAkE;QAClE,8DAA8D;QAC9D,wCAAwC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;QACvE,mEAAmE;QACnE,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,kEAAkE;YAClE,+DAA+D;YAC/D,2BAA2B;YAC3B,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,mDAAmD;IACnD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAoB;IAEpB,MAAM,KAAK,GAA2C,EAAE,CAAC;IACzD,IAAI,SAAS,CAAC,MAAM,CAAC,YAAY,IAAI,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9E,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvB,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAA+C;IAE/C,yCAAyC;IACzC,IAAI,KAAK,CAAC,eAAe;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,GAAG,GAAuB;QAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,mBAAmB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,SAAS,CAAC;QACvE,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;QAC7E,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;KACnD,CAAC;IAIF,MAAM,OAAO,GAAyD;QACpE,MAAM,EAAE,EAAE;QACV,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,EAAE;KACb,CAAC;IAEF,mDAAmD;IACnD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;QAClB,SAAS,EAAE,mBAAmB,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC;QAClE,MAAM,EAAE,oBAAoB;KAC7B,CAAC,CAAC;IAEH,6BAA6B;IAC7B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACzC,MAAM,MAAM,GAAI,GAAG,CAAC,QAAQ,EAAE,KAAiC,IAAI,OAAO,CAAC;QAC3E,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,sEAAsE;IACtE,2DAA2D;IAC3D,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAU,EAAE,CAAC;QAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;QAClB,IAAI,UAAU,GAAG,EAAE,CAAC;QACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;gBAAE,MAAM;YACjC,QAAQ,GAAG,CAAC,CAAC;YACb,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACjC,CAAC;QACD,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC;gBACX,KAAK,EAAE,IAAI;gBACX,aAAa,EAAE,QAAQ;gBACvB,GAAG,EAAE,OAAO;gBACZ,MAAM,EAAE,GAAG,IAAI,mBAAmB,QAAQ,GAAG,CAAC,uBAAuB,UAAU,GAAG;aACnF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,SAAS,MAAM,CAAC,KAAqC;IACnD,KAAK,CAAC,YAAY,GAAG,mBAAmB,CAAC;QACvC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;QAC7E,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;QAClD,uBAAuB,EAAE,KAAK,CAAC,uBAAuB;QACtD,eAAe,EAAE,KAAK,CAAC,eAAe;KACvC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAc,SAAS,CACtD,oBAAoB,EACpB,MAAM,EACN,sBAAsB,EACtB,SAAS,EACT,qFAAqF,CACtF,CAAC,KAAK,EAAE,CAAC"}
@@ -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"}