agentfootprint 9.57.0 → 9.59.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/AGENTS.md +1 -1
- package/CLAUDE.md +5 -2
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/adapters/llm/AnthropicProvider.js +33 -4
- package/dist/adapters/llm/AnthropicProvider.js.map +1 -1
- package/dist/adapters/llm/BrowserAnthropicProvider.js +12 -78
- package/dist/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
- package/dist/adapters/llm/anthropicCacheWire.js +101 -0
- package/dist/adapters/llm/anthropicCacheWire.js.map +1 -0
- package/dist/cache/cacheRecorder.js +105 -46
- package/dist/cache/cacheRecorder.js.map +1 -1
- package/dist/cache/index.js +20 -6
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/portUsage.js +47 -0
- package/dist/cache/portUsage.js.map +1 -0
- package/dist/cache/strategies/AnthropicCacheStrategy.js +5 -17
- package/dist/cache/strategies/AnthropicCacheStrategy.js.map +1 -1
- package/dist/cache/strategies/BedrockCacheStrategy.js +74 -58
- package/dist/cache/strategies/BedrockCacheStrategy.js.map +1 -1
- package/dist/cache/strategies/NoOpCacheStrategy.js +2 -1
- package/dist/cache/strategies/NoOpCacheStrategy.js.map +1 -1
- package/dist/cache/strategies/OpenAICacheStrategy.js +14 -13
- package/dist/cache/strategies/OpenAICacheStrategy.js.map +1 -1
- package/dist/core/Agent.js +20 -1
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +125 -1
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +42 -0
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildDynamicAgentChart.js +68 -0
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +49 -3
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/agent/window/notice.js +5 -2
- package/dist/core/agent/window/notice.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +27 -1
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/doors/maps.js +56 -0
- package/dist/doors/maps.js.map +1 -0
- package/dist/esm/adapters/llm/AnthropicProvider.d.ts +2 -0
- package/dist/esm/adapters/llm/AnthropicProvider.js +33 -4
- package/dist/esm/adapters/llm/AnthropicProvider.js.map +1 -1
- package/dist/esm/adapters/llm/BrowserAnthropicProvider.js +11 -77
- package/dist/esm/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
- package/dist/esm/adapters/llm/anthropicCacheWire.d.ts +96 -0
- package/dist/esm/adapters/llm/anthropicCacheWire.js +96 -0
- package/dist/esm/adapters/llm/anthropicCacheWire.js.map +1 -0
- package/dist/esm/cache/cacheRecorder.d.ts +73 -36
- package/dist/esm/cache/cacheRecorder.js +105 -46
- package/dist/esm/cache/cacheRecorder.js.map +1 -1
- package/dist/esm/cache/index.d.ts +12 -7
- package/dist/esm/cache/index.js +13 -5
- package/dist/esm/cache/index.js.map +1 -1
- package/dist/esm/cache/portUsage.d.ts +30 -0
- package/dist/esm/cache/portUsage.js +43 -0
- package/dist/esm/cache/portUsage.js.map +1 -0
- package/dist/esm/cache/strategies/AnthropicCacheStrategy.d.ts +6 -3
- package/dist/esm/cache/strategies/AnthropicCacheStrategy.js +5 -17
- package/dist/esm/cache/strategies/AnthropicCacheStrategy.js.map +1 -1
- package/dist/esm/cache/strategies/BedrockCacheStrategy.d.ts +48 -19
- package/dist/esm/cache/strategies/BedrockCacheStrategy.js +74 -58
- package/dist/esm/cache/strategies/BedrockCacheStrategy.js.map +1 -1
- package/dist/esm/cache/strategies/NoOpCacheStrategy.d.ts +3 -2
- package/dist/esm/cache/strategies/NoOpCacheStrategy.js +2 -1
- package/dist/esm/cache/strategies/NoOpCacheStrategy.js.map +1 -1
- package/dist/esm/cache/strategies/OpenAICacheStrategy.d.ts +13 -2
- package/dist/esm/cache/strategies/OpenAICacheStrategy.js +14 -13
- package/dist/esm/cache/strategies/OpenAICacheStrategy.js.map +1 -1
- package/dist/esm/cache/types.d.ts +35 -7
- package/dist/esm/core/Agent.d.ts +3 -1
- package/dist/esm/core/Agent.js +20 -1
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +45 -0
- package/dist/esm/core/agent/AgentBuilder.js +125 -1
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.d.ts +8 -0
- package/dist/esm/core/agent/buildAgentChart.js +42 -0
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildDynamicAgentChart.js +68 -0
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.d.ts +7 -0
- package/dist/esm/core/agent/stages/toolCalls.js +47 -1
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +16 -0
- package/dist/esm/core/agent/window/notice.d.ts +5 -2
- package/dist/esm/core/agent/window/notice.js +5 -2
- package/dist/esm/core/agent/window/notice.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.js +27 -1
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/doors/maps.d.ts +41 -0
- package/dist/esm/doors/maps.js +41 -0
- package/dist/esm/doors/maps.js.map +1 -0
- package/dist/esm/events/dispatcher.d.ts +1 -1
- package/dist/esm/events/dispatcher.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +52 -2
- package/dist/esm/events/registry.d.ts +7 -1
- package/dist/esm/events/registry.js +6 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/lib/claim/claim.d.ts +64 -0
- package/dist/esm/lib/claim/claim.js +66 -0
- package/dist/esm/lib/claim/claim.js.map +1 -0
- package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.d.ts +10 -0
- package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js +94 -3
- package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
- package/dist/esm/lib/injection-engine/evaluator.d.ts +9 -0
- package/dist/esm/lib/injection-engine/evaluator.js +29 -0
- package/dist/esm/lib/injection-engine/evaluator.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillEntryEvidence.d.ts +59 -0
- package/dist/esm/lib/injection-engine/skillEntryEvidence.js +86 -0
- package/dist/esm/lib/injection-engine/skillEntryEvidence.js.map +1 -0
- package/dist/esm/lib/injection-engine/skillGraph.js +15 -0
- package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillGraphCheckup.d.ts +1 -1
- package/dist/esm/lib/injection-engine/skillGraphCheckup.js.map +1 -1
- package/dist/esm/lib/injection-engine/startRuleClaim.d.ts +6 -4
- package/dist/esm/lib/injection-engine/startRuleClaim.js.map +1 -1
- package/dist/esm/lib/injection-engine/types.d.ts +14 -1
- package/dist/esm/lib/injection-engine/types.js.map +1 -1
- package/dist/esm/maps/engagement/evidence.d.ts +111 -0
- package/dist/esm/maps/engagement/evidence.js +123 -0
- package/dist/esm/maps/engagement/evidence.js.map +1 -0
- package/dist/esm/maps/engagement/lease.d.ts +73 -0
- package/dist/esm/maps/engagement/lease.js +272 -0
- package/dist/esm/maps/engagement/lease.js.map +1 -0
- package/dist/esm/maps/engagement/parkCard.d.ts +48 -0
- package/dist/esm/maps/engagement/parkCard.js +88 -0
- package/dist/esm/maps/engagement/parkCard.js.map +1 -0
- package/dist/esm/maps/engagement/types.d.ts +238 -0
- package/dist/esm/maps/engagement/types.js +35 -0
- package/dist/esm/maps/engagement/types.js.map +1 -0
- package/dist/esm/recorders/core/MapRecorder.d.ts +20 -0
- package/dist/esm/recorders/core/MapRecorder.js +25 -0
- package/dist/esm/recorders/core/MapRecorder.js.map +1 -0
- package/dist/events/dispatcher.js.map +1 -1
- package/dist/events/registry.js +6 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/lib/claim/claim.js +75 -0
- package/dist/lib/claim/claim.js.map +1 -0
- package/dist/lib/injection-engine/buildInjectionEngineSubflow.js +93 -2
- package/dist/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
- package/dist/lib/injection-engine/evaluator.js +29 -0
- package/dist/lib/injection-engine/evaluator.js.map +1 -1
- package/dist/lib/injection-engine/skillEntryEvidence.js +90 -0
- package/dist/lib/injection-engine/skillEntryEvidence.js.map +1 -0
- package/dist/lib/injection-engine/skillGraph.js +15 -0
- package/dist/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/lib/injection-engine/skillGraphCheckup.js.map +1 -1
- package/dist/lib/injection-engine/startRuleClaim.js.map +1 -1
- package/dist/lib/injection-engine/types.js.map +1 -1
- package/dist/maps/engagement/evidence.js +132 -0
- package/dist/maps/engagement/evidence.js.map +1 -0
- package/dist/maps/engagement/lease.js +276 -0
- package/dist/maps/engagement/lease.js.map +1 -0
- package/dist/maps/engagement/parkCard.js +92 -0
- package/dist/maps/engagement/parkCard.js.map +1 -0
- package/dist/maps/engagement/types.js +39 -0
- package/dist/maps/engagement/types.js.map +1 -0
- package/dist/recorders/core/MapRecorder.js +29 -0
- package/dist/recorders/core/MapRecorder.js.map +1 -0
- package/dist/types/adapters/llm/AnthropicProvider.d.ts +2 -0
- package/dist/types/adapters/llm/AnthropicProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/BrowserAnthropicProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/anthropicCacheWire.d.ts +97 -0
- package/dist/types/adapters/llm/anthropicCacheWire.d.ts.map +1 -0
- package/dist/types/cache/cacheRecorder.d.ts +73 -36
- package/dist/types/cache/cacheRecorder.d.ts.map +1 -1
- package/dist/types/cache/index.d.ts +12 -7
- package/dist/types/cache/index.d.ts.map +1 -1
- package/dist/types/cache/portUsage.d.ts +31 -0
- package/dist/types/cache/portUsage.d.ts.map +1 -0
- package/dist/types/cache/strategies/AnthropicCacheStrategy.d.ts +6 -3
- package/dist/types/cache/strategies/AnthropicCacheStrategy.d.ts.map +1 -1
- package/dist/types/cache/strategies/BedrockCacheStrategy.d.ts +48 -19
- package/dist/types/cache/strategies/BedrockCacheStrategy.d.ts.map +1 -1
- package/dist/types/cache/strategies/NoOpCacheStrategy.d.ts +3 -2
- package/dist/types/cache/strategies/NoOpCacheStrategy.d.ts.map +1 -1
- package/dist/types/cache/strategies/OpenAICacheStrategy.d.ts +13 -2
- package/dist/types/cache/strategies/OpenAICacheStrategy.d.ts.map +1 -1
- package/dist/types/cache/types.d.ts +35 -7
- package/dist/types/cache/types.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +3 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +45 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts +8 -0
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts +7 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +16 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/agent/window/notice.d.ts +5 -2
- package/dist/types/core/agent/window/notice.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/doors/maps.d.ts +42 -0
- package/dist/types/doors/maps.d.ts.map +1 -0
- package/dist/types/events/dispatcher.d.ts +1 -1
- package/dist/types/events/dispatcher.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +52 -2
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +7 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/lib/claim/claim.d.ts +65 -0
- package/dist/types/lib/claim/claim.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/buildInjectionEngineSubflow.d.ts +10 -0
- package/dist/types/lib/injection-engine/buildInjectionEngineSubflow.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/evaluator.d.ts +9 -0
- package/dist/types/lib/injection-engine/evaluator.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillEntryEvidence.d.ts +60 -0
- package/dist/types/lib/injection-engine/skillEntryEvidence.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillGraphCheckup.d.ts +1 -1
- package/dist/types/lib/injection-engine/skillGraphCheckup.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/startRuleClaim.d.ts +6 -4
- package/dist/types/lib/injection-engine/startRuleClaim.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/types.d.ts +14 -1
- package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
- package/dist/types/maps/engagement/evidence.d.ts +112 -0
- package/dist/types/maps/engagement/evidence.d.ts.map +1 -0
- package/dist/types/maps/engagement/lease.d.ts +74 -0
- package/dist/types/maps/engagement/lease.d.ts.map +1 -0
- package/dist/types/maps/engagement/parkCard.d.ts +49 -0
- package/dist/types/maps/engagement/parkCard.d.ts.map +1 -0
- package/dist/types/maps/engagement/types.d.ts +239 -0
- package/dist/types/maps/engagement/types.d.ts.map +1 -0
- package/dist/types/recorders/core/MapRecorder.d.ts +21 -0
- package/dist/types/recorders/core/MapRecorder.d.ts.map +1 -0
- package/package.json +16 -2
|
@@ -1,58 +1,96 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cacheRecorder() —
|
|
2
|
+
* cacheRecorder() — the cache layer's meter.
|
|
3
3
|
*
|
|
4
4
|
* Subscribes to:
|
|
5
|
-
* - `FlowRecorder.onDecision` —
|
|
6
|
-
* (apply-markers / no-markers + the rule that fired + evidence
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* into CacheMetrics (cacheReadTokens / cacheWriteTokens / fresh).
|
|
5
|
+
* - `FlowRecorder.onDecision` — CacheGate routing decisions
|
|
6
|
+
* (apply-markers / no-markers + the rule that fired + evidence from
|
|
7
|
+
* `decide()`).
|
|
8
|
+
* - `agentfootprint.stream.llm_end` — reads that event's `usage` (the PORT
|
|
9
|
+
* shape: `{ input, output, cacheRead?, cacheWrite? }`) and asks the
|
|
10
|
+
* agent's `CacheStrategy.extractMetrics` what is known about it.
|
|
12
11
|
*
|
|
13
|
-
* Produces:
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
12
|
+
* Produces: a per-turn report via `recorder.report()` — token tallies, hit
|
|
13
|
+
* rate and dollar estimates, each carried as a `Claim` so an UNMEASURED turn
|
|
14
|
+
* can never render as a zero one.
|
|
21
15
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
16
|
+
* ── Why every number here is a Claim (9.59.0) ─────────────────────────
|
|
17
|
+
* The shipped 9.58.0 meter reported `hitRate: 0` for a 20-call turn that hit
|
|
18
|
+
* cache on every call. Two faults, and the second is why the first went
|
|
19
|
+
* unnoticed for so long: (1) the strategies parsed RAW WIRE field names off a
|
|
20
|
+
* value that carries the PORT shape, so every field read `undefined`; (2) the
|
|
21
|
+
* report typed its aggregates as bare `number`, so "nobody measured" and
|
|
22
|
+
* "measured, and it was zero" rendered identically. Fixing (1) alone would
|
|
23
|
+
* have left a meter that still cannot say "unmeasured" — hence `Claim<T>`,
|
|
24
|
+
* plus `measuredIterations` / `unmeasuredIterations` so a rate computed from
|
|
25
|
+
* 3 of 20 calls states its own denominator.
|
|
26
|
+
*
|
|
27
|
+
* Read the number through `isKnown(report.hitRate)` (or `describeClaim` for a
|
|
28
|
+
* one-line render). There is deliberately no door that hands you a bare
|
|
29
|
+
* number without your having branched.
|
|
30
|
+
*
|
|
31
|
+
* ── What this recorder does NOT do ────────────────────────────────────
|
|
32
|
+
* It emits no events. (Earlier prose here promised per-iteration
|
|
33
|
+
* `agentfootprint.cache.applied` / `agentfootprint.cache.metrics` events;
|
|
34
|
+
* neither name has ever existed in the event registry and no `typedEmit` has
|
|
35
|
+
* ever been in this file. The prose was the bug.)
|
|
36
|
+
*
|
|
37
|
+
* It does not write `scope.recentHitRate` back into agent state either, so
|
|
38
|
+
* CacheGate's hit-rate-floor rule never fires on its own — the loop is
|
|
39
|
+
* severed at both ends (the key is seeded `undefined` and written by
|
|
40
|
+
* nothing). Recorders do not write to chart scope, so closing that loop needs
|
|
41
|
+
* an agent-side accessor convention; it is separable work from measuring the
|
|
42
|
+
* number, which is what this file now does.
|
|
27
43
|
*/
|
|
28
44
|
import type { CombinedRecorder } from 'footprintjs';
|
|
29
45
|
import type { CacheMetrics, CacheStrategy } from './types.js';
|
|
30
46
|
import type { PricingTable } from '../adapters/types.js';
|
|
31
|
-
|
|
47
|
+
import { type Claim } from '../lib/claim/claim.js';
|
|
48
|
+
/** One LLM call's row on the record. */
|
|
49
|
+
export interface PerIterEntry {
|
|
32
50
|
readonly iteration: number;
|
|
33
51
|
readonly branch: 'apply-markers' | 'no-markers';
|
|
34
52
|
readonly rule?: string;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
/**
|
|
54
|
+
* What the strategy could say about this call's cache traffic. `known` =
|
|
55
|
+
* the provider reported counts; `unknown` = nothing was measured;
|
|
56
|
+
* `not-applicable` = this adapter cannot report cache usage at all.
|
|
57
|
+
*/
|
|
58
|
+
readonly metrics: Claim<CacheMetrics>;
|
|
59
|
+
/** Dollar estimates, `unknown` on any call whose metrics were not known. */
|
|
60
|
+
readonly dollarsSpent: Claim<number>;
|
|
61
|
+
readonly dollarsSavedVsNoCache: Claim<number>;
|
|
38
62
|
}
|
|
39
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The turn's tally. Every quantity derived from provider usage is a
|
|
65
|
+
* {@link Claim}: unmeasured is a first-class answer, never a zero.
|
|
66
|
+
*/
|
|
67
|
+
export interface CacheReportSummary {
|
|
68
|
+
/** LLM calls seen. Always known — the recorder counted them itself. */
|
|
40
69
|
readonly totalIterations: number;
|
|
41
70
|
readonly applyMarkersIterations: number;
|
|
42
71
|
readonly noMarkersIterations: number;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
readonly
|
|
72
|
+
/**
|
|
73
|
+
* Calls whose cache traffic the provider actually reported. The DENOMINATOR
|
|
74
|
+
* every claim below is computed over — a rate from 3 of 20 calls is not the
|
|
75
|
+
* turn's rate, and this is how a reader can tell.
|
|
76
|
+
*/
|
|
77
|
+
readonly measuredIterations: number;
|
|
78
|
+
/** Calls that reported nothing (no usage, no cache fields, or an adapter that cannot). */
|
|
79
|
+
readonly unmeasuredIterations: number;
|
|
80
|
+
readonly cacheReadTokensTotal: Claim<number>;
|
|
81
|
+
readonly cacheWriteTokensTotal: Claim<number>;
|
|
82
|
+
readonly freshInputTokensTotal: Claim<number>;
|
|
83
|
+
/** cacheRead / (cacheRead + cacheWrite + fresh), over MEASURED calls only. */
|
|
84
|
+
readonly hitRate: Claim<number>;
|
|
85
|
+
readonly estimatedDollarsSpent: Claim<number>;
|
|
86
|
+
readonly estimatedDollarsSavedVsNoCache: Claim<number>;
|
|
49
87
|
readonly perIter: readonly PerIterEntry[];
|
|
50
88
|
}
|
|
51
89
|
export interface CacheRecorderOptions {
|
|
52
90
|
/**
|
|
53
|
-
* The agent's CacheStrategy
|
|
54
|
-
*
|
|
55
|
-
*
|
|
91
|
+
* The agent's CacheStrategy — the thing that reads the port usage. Without
|
|
92
|
+
* one every row's metrics are `unknown` (with that as the stated reason),
|
|
93
|
+
* which is the honest answer: nothing read the usage.
|
|
56
94
|
*/
|
|
57
95
|
readonly strategy?: CacheStrategy;
|
|
58
96
|
/**
|
|
@@ -82,4 +120,3 @@ export interface CacheRecorderHandle extends CombinedRecorder {
|
|
|
82
120
|
reset(): void;
|
|
83
121
|
}
|
|
84
122
|
export declare function cacheRecorder(options?: CacheRecorderOptions): CacheRecorderHandle;
|
|
85
|
-
export {};
|
|
@@ -1,32 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cacheRecorder() —
|
|
2
|
+
* cacheRecorder() — the cache layer's meter.
|
|
3
3
|
*
|
|
4
4
|
* Subscribes to:
|
|
5
|
-
* - `FlowRecorder.onDecision` —
|
|
6
|
-
* (apply-markers / no-markers + the rule that fired + evidence
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* into CacheMetrics (cacheReadTokens / cacheWriteTokens / fresh).
|
|
5
|
+
* - `FlowRecorder.onDecision` — CacheGate routing decisions
|
|
6
|
+
* (apply-markers / no-markers + the rule that fired + evidence from
|
|
7
|
+
* `decide()`).
|
|
8
|
+
* - `agentfootprint.stream.llm_end` — reads that event's `usage` (the PORT
|
|
9
|
+
* shape: `{ input, output, cacheRead?, cacheWrite? }`) and asks the
|
|
10
|
+
* agent's `CacheStrategy.extractMetrics` what is known about it.
|
|
12
11
|
*
|
|
13
|
-
* Produces:
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
12
|
+
* Produces: a per-turn report via `recorder.report()` — token tallies, hit
|
|
13
|
+
* rate and dollar estimates, each carried as a `Claim` so an UNMEASURED turn
|
|
14
|
+
* can never render as a zero one.
|
|
21
15
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
16
|
+
* ── Why every number here is a Claim (9.59.0) ─────────────────────────
|
|
17
|
+
* The shipped 9.58.0 meter reported `hitRate: 0` for a 20-call turn that hit
|
|
18
|
+
* cache on every call. Two faults, and the second is why the first went
|
|
19
|
+
* unnoticed for so long: (1) the strategies parsed RAW WIRE field names off a
|
|
20
|
+
* value that carries the PORT shape, so every field read `undefined`; (2) the
|
|
21
|
+
* report typed its aggregates as bare `number`, so "nobody measured" and
|
|
22
|
+
* "measured, and it was zero" rendered identically. Fixing (1) alone would
|
|
23
|
+
* have left a meter that still cannot say "unmeasured" — hence `Claim<T>`,
|
|
24
|
+
* plus `measuredIterations` / `unmeasuredIterations` so a rate computed from
|
|
25
|
+
* 3 of 20 calls states its own denominator.
|
|
26
|
+
*
|
|
27
|
+
* Read the number through `isKnown(report.hitRate)` (or `describeClaim` for a
|
|
28
|
+
* one-line render). There is deliberately no door that hands you a bare
|
|
29
|
+
* number without your having branched.
|
|
30
|
+
*
|
|
31
|
+
* ── What this recorder does NOT do ────────────────────────────────────
|
|
32
|
+
* It emits no events. (Earlier prose here promised per-iteration
|
|
33
|
+
* `agentfootprint.cache.applied` / `agentfootprint.cache.metrics` events;
|
|
34
|
+
* neither name has ever existed in the event registry and no `typedEmit` has
|
|
35
|
+
* ever been in this file. The prose was the bug.)
|
|
36
|
+
*
|
|
37
|
+
* It does not write `scope.recentHitRate` back into agent state either, so
|
|
38
|
+
* CacheGate's hit-rate-floor rule never fires on its own — the loop is
|
|
39
|
+
* severed at both ends (the key is seeded `undefined` and written by
|
|
40
|
+
* nothing). Recorders do not write to chart scope, so closing that loop needs
|
|
41
|
+
* an agent-side accessor convention; it is separable work from measuring the
|
|
42
|
+
* number, which is what this file now does.
|
|
27
43
|
*/
|
|
28
44
|
import { splitStageId } from 'footprintjs/trace';
|
|
29
45
|
import { STAGE_IDS } from '../conventions.js';
|
|
46
|
+
import { known, unknown, isKnown } from '../lib/claim/claim.js';
|
|
47
|
+
/** The metrics of a row already filtered by `isKnown`. Narrowing helper only. */
|
|
48
|
+
function valueOfMetrics(entry) {
|
|
49
|
+
return isKnown(entry.metrics)
|
|
50
|
+
? entry.metrics.value
|
|
51
|
+
: { cacheReadTokens: 0, cacheWriteTokens: 0, freshInputTokens: 0 };
|
|
52
|
+
}
|
|
30
53
|
export function cacheRecorder(options = {}) {
|
|
31
54
|
const perIter = [];
|
|
32
55
|
let lastDecision;
|
|
@@ -61,8 +84,11 @@ export function cacheRecorder(options = {}) {
|
|
|
61
84
|
if (event.type !== 'agentfootprint.stream.llm_end')
|
|
62
85
|
return;
|
|
63
86
|
iterationCounter++;
|
|
87
|
+
// The PORT shape, which is the only shape this event has ever carried.
|
|
64
88
|
const usage = event.payload.usage;
|
|
65
|
-
const metrics = options.strategy
|
|
89
|
+
const metrics = options.strategy === undefined
|
|
90
|
+
? unknown('no CacheStrategy was given to cacheRecorder(), so nothing read the usage')
|
|
91
|
+
: options.strategy.extractMetrics(usage);
|
|
66
92
|
const branch = lastDecision?.branch ?? 'apply-markers';
|
|
67
93
|
// Compute dollar math:
|
|
68
94
|
// spent = freshInput * inputPrice
|
|
@@ -70,21 +96,34 @@ export function cacheRecorder(options = {}) {
|
|
|
70
96
|
// + cacheWrite * cacheWritePrice
|
|
71
97
|
// no-cache cost = (freshInput + cacheRead + cacheWrite) * inputPrice
|
|
72
98
|
// saved = no-cache cost - spent
|
|
73
|
-
let dollarsSpent
|
|
74
|
-
let savedVsNoCache
|
|
75
|
-
if (metrics) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const noCacheCost = dollars(
|
|
81
|
-
|
|
99
|
+
let dollarsSpent;
|
|
100
|
+
let savedVsNoCache;
|
|
101
|
+
if (isKnown(metrics)) {
|
|
102
|
+
const m = metrics.value;
|
|
103
|
+
const spent = dollars(m.freshInputTokens, 'input') +
|
|
104
|
+
dollars(m.cacheReadTokens, 'cacheRead') +
|
|
105
|
+
dollars(m.cacheWriteTokens, 'cacheWrite');
|
|
106
|
+
const noCacheCost = dollars(m.freshInputTokens + m.cacheReadTokens + m.cacheWriteTokens, 'input');
|
|
107
|
+
const ev = options.pricing === undefined
|
|
108
|
+
? 'measured tokens, but no PricingTable was given — every price is 0'
|
|
109
|
+
: 'measured tokens priced with the given PricingTable';
|
|
110
|
+
dollarsSpent = known(spent, ev);
|
|
111
|
+
savedVsNoCache = known(noCacheCost - spent, ev);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// No tokens measured ⇒ no dollars. Carrying the metrics claim's own
|
|
115
|
+
// reason forward is what keeps a $0 estimate from reading as "free".
|
|
116
|
+
const why = metrics.kind === 'unknown'
|
|
117
|
+
? metrics.reason
|
|
118
|
+
: `no cache tokens are measurable here — ${metrics.evidence}`;
|
|
119
|
+
dollarsSpent = unknown(why);
|
|
120
|
+
savedVsNoCache = unknown(why);
|
|
82
121
|
}
|
|
83
122
|
const entry = {
|
|
84
123
|
iteration: iterationCounter,
|
|
85
124
|
branch,
|
|
86
125
|
...(lastDecision?.rule !== undefined && { rule: lastDecision.rule }),
|
|
87
|
-
|
|
126
|
+
metrics,
|
|
88
127
|
dollarsSpent,
|
|
89
128
|
dollarsSavedVsNoCache: savedVsNoCache,
|
|
90
129
|
};
|
|
@@ -94,23 +133,43 @@ export function cacheRecorder(options = {}) {
|
|
|
94
133
|
report() {
|
|
95
134
|
const apply = perIter.filter((p) => p.branch === 'apply-markers').length;
|
|
96
135
|
const skip = perIter.filter((p) => p.branch === 'no-markers').length;
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
136
|
+
const measured = perIter.filter((p) => isKnown(p.metrics));
|
|
137
|
+
const n = measured.length;
|
|
138
|
+
const total = perIter.length;
|
|
139
|
+
// ONE reason, stated once, reused by every claim below — so a reader
|
|
140
|
+
// who prints any single field learns why the whole report is empty.
|
|
141
|
+
const why = total === 0
|
|
142
|
+
? 'no LLM call was observed, so there is nothing to measure'
|
|
143
|
+
: `none of the ${total} observed call(s) reported cache usage — ` +
|
|
144
|
+
(perIter[0]?.metrics.kind === 'not-applicable'
|
|
145
|
+
? `this provider cannot report it (${perIter[0].metrics.evidence})`
|
|
146
|
+
: 'the provider reported no cache fields');
|
|
147
|
+
const evidence = `summed over the ${n} of ${total} call(s) whose usage was measured`;
|
|
148
|
+
const claimOf = (value) => n === 0 ? unknown(why) : known(value, evidence);
|
|
149
|
+
const cacheRead = measured.reduce((s2, p) => s2 + valueOfMetrics(p).cacheReadTokens, 0);
|
|
150
|
+
const cacheWrite = measured.reduce((s2, p) => s2 + valueOfMetrics(p).cacheWriteTokens, 0);
|
|
151
|
+
const fresh = measured.reduce((s2, p) => s2 + valueOfMetrics(p).freshInputTokens, 0);
|
|
152
|
+
const requestTotal = cacheRead + cacheWrite + fresh;
|
|
153
|
+
const dollarsSpent = measured.reduce((s2, p) => s2 + (isKnown(p.dollarsSpent) ? p.dollarsSpent.value : 0), 0);
|
|
154
|
+
const dollarsSaved = measured.reduce((s2, p) => s2 + (isKnown(p.dollarsSavedVsNoCache) ? p.dollarsSavedVsNoCache.value : 0), 0);
|
|
104
155
|
return Object.freeze({
|
|
105
|
-
totalIterations:
|
|
156
|
+
totalIterations: total,
|
|
106
157
|
applyMarkersIterations: apply,
|
|
107
158
|
noMarkersIterations: skip,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
159
|
+
measuredIterations: n,
|
|
160
|
+
unmeasuredIterations: total - n,
|
|
161
|
+
cacheReadTokensTotal: claimOf(cacheRead),
|
|
162
|
+
cacheWriteTokensTotal: claimOf(cacheWrite),
|
|
163
|
+
freshInputTokensTotal: claimOf(fresh),
|
|
164
|
+
// A measured turn with zero prompt tokens is not a thing; guard it
|
|
165
|
+
// anyway rather than divide by zero and hand back NaN as a rate.
|
|
166
|
+
hitRate: n === 0
|
|
167
|
+
? unknown(why)
|
|
168
|
+
: requestTotal === 0
|
|
169
|
+
? unknown(`the ${n} measured call(s) reported 0 prompt tokens in total, so a hit rate has no denominator`)
|
|
170
|
+
: known(cacheRead / requestTotal, evidence),
|
|
171
|
+
estimatedDollarsSpent: claimOf(dollarsSpent),
|
|
172
|
+
estimatedDollarsSavedVsNoCache: claimOf(dollarsSaved),
|
|
114
173
|
perIter: Object.freeze([...perIter]),
|
|
115
174
|
});
|
|
116
175
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cacheRecorder.js","sourceRoot":"","sources":["../../../src/cache/cacheRecorder.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cacheRecorder.js","sourceRoot":"","sources":["../../../src/cache/cacheRecorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
* Strategies registered as of v2.6:
|
|
10
10
|
* - NoOp (wildcard '*' fallback) — always available, registered by
|
|
11
11
|
* the registry module itself
|
|
12
|
-
* - AnthropicCacheStrategy ('anthropic', 'browser-anthropic')
|
|
12
|
+
* - AnthropicCacheStrategy ('anthropic', 'browser-anthropic') — the one
|
|
13
|
+
* end-to-end strategy: the adapter sends markers AND reads cache usage
|
|
14
|
+
* - OpenAICacheStrategy ('openai', 'browser-openai') — pass-through
|
|
15
|
+
* (OpenAI auto-caches); reports `not-applicable` metrics until the
|
|
16
|
+
* adapter lifts `prompt_tokens_details.cached_tokens` onto the port
|
|
17
|
+
* - BedrockCacheStrategy ('bedrock') — `enabled: false`; the Bedrock
|
|
18
|
+
* adapter implements neither half of the cache contract
|
|
13
19
|
*
|
|
14
|
-
* Future strategies
|
|
15
|
-
* -
|
|
16
|
-
* - BedrockCacheStrategy
|
|
17
|
-
* - GeminiCacheStrategy (v2.7+, async handle-based)
|
|
20
|
+
* Future strategies:
|
|
21
|
+
* - GeminiCacheStrategy (async handle-based)
|
|
18
22
|
*
|
|
19
23
|
* Public types (re-exported for consumers):
|
|
20
24
|
* - CachePolicy, CacheMarker, CacheStrategy, CacheCapabilities,
|
|
@@ -23,11 +27,12 @@
|
|
|
23
27
|
import './strategies/AnthropicCacheStrategy.js';
|
|
24
28
|
import './strategies/OpenAICacheStrategy.js';
|
|
25
29
|
import './strategies/BedrockCacheStrategy.js';
|
|
26
|
-
export type { CachePolicy, CachePolicyContext, CacheMarker, CacheStrategy, CacheStrategyContext, CacheCapabilities, CacheMetrics, } from './types.js';
|
|
30
|
+
export type { CachePolicy, CachePolicyContext, CacheMarker, CacheStrategy, CacheStrategyContext, CacheCapabilities, CacheMetrics, CacheUsage, } from './types.js';
|
|
31
|
+
export { known, unknown, notApplicable, isKnown, valueOr, describeClaim, type Claim, } from '../lib/claim/claim.js';
|
|
27
32
|
export { getDefaultCacheStrategy, registerCacheStrategy, listRegisteredStrategies, } from './strategyRegistry.js';
|
|
28
33
|
export { NoOpCacheStrategy } from './strategies/NoOpCacheStrategy.js';
|
|
29
34
|
export { AnthropicCacheStrategy } from './strategies/AnthropicCacheStrategy.js';
|
|
30
35
|
export { OpenAICacheStrategy } from './strategies/OpenAICacheStrategy.js';
|
|
31
36
|
export { BedrockCacheStrategy } from './strategies/BedrockCacheStrategy.js';
|
|
32
37
|
export { cacheRecorder } from './cacheRecorder.js';
|
|
33
|
-
export type { CacheRecorderOptions, CacheRecorderHandle } from './cacheRecorder.js';
|
|
38
|
+
export type { CacheRecorderOptions, CacheRecorderHandle, CacheReportSummary, PerIterEntry, } from './cacheRecorder.js';
|
package/dist/esm/cache/index.js
CHANGED
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
* Strategies registered as of v2.6:
|
|
10
10
|
* - NoOp (wildcard '*' fallback) — always available, registered by
|
|
11
11
|
* the registry module itself
|
|
12
|
-
* - AnthropicCacheStrategy ('anthropic', 'browser-anthropic')
|
|
12
|
+
* - AnthropicCacheStrategy ('anthropic', 'browser-anthropic') — the one
|
|
13
|
+
* end-to-end strategy: the adapter sends markers AND reads cache usage
|
|
14
|
+
* - OpenAICacheStrategy ('openai', 'browser-openai') — pass-through
|
|
15
|
+
* (OpenAI auto-caches); reports `not-applicable` metrics until the
|
|
16
|
+
* adapter lifts `prompt_tokens_details.cached_tokens` onto the port
|
|
17
|
+
* - BedrockCacheStrategy ('bedrock') — `enabled: false`; the Bedrock
|
|
18
|
+
* adapter implements neither half of the cache contract
|
|
13
19
|
*
|
|
14
|
-
* Future strategies
|
|
15
|
-
* -
|
|
16
|
-
* - BedrockCacheStrategy
|
|
17
|
-
* - GeminiCacheStrategy (v2.7+, async handle-based)
|
|
20
|
+
* Future strategies:
|
|
21
|
+
* - GeminiCacheStrategy (async handle-based)
|
|
18
22
|
*
|
|
19
23
|
* Public types (re-exported for consumers):
|
|
20
24
|
* - CachePolicy, CacheMarker, CacheStrategy, CacheCapabilities,
|
|
@@ -24,6 +28,10 @@
|
|
|
24
28
|
import './strategies/AnthropicCacheStrategy.js';
|
|
25
29
|
import './strategies/OpenAICacheStrategy.js';
|
|
26
30
|
import './strategies/BedrockCacheStrategy.js';
|
|
31
|
+
// The honesty primitive the meter is typed in (9.59.0). Re-exported
|
|
32
|
+
// reference-equal from `src/lib/claim/` — the SAME symbols
|
|
33
|
+
// `agentfootprint/maps` exports, so `known` from either door is one function.
|
|
34
|
+
export { known, unknown, notApplicable, isKnown, valueOr, describeClaim, } from '../lib/claim/claim.js';
|
|
27
35
|
// Strategy registry
|
|
28
36
|
export { getDefaultCacheStrategy, registerCacheStrategy, listRegisteredStrategies, } from './strategyRegistry.js';
|
|
29
37
|
// Built-in strategy classes (for consumers who want explicit overrides)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,4DAA4D;AAC5D,OAAO,wCAAwC,CAAC;AAChD,OAAO,qCAAqC,CAAC;AAC7C,OAAO,sCAAsC,CAAC;AAc9C,oEAAoE;AACpE,2DAA2D;AAC3D,8EAA8E;AAC9E,OAAO,EACL,KAAK,EACL,OAAO,EACP,aAAa,EACb,OAAO,EACP,OAAO,EACP,aAAa,GAEd,MAAM,uBAAuB,CAAC;AAE/B,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,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one reader of PORT-shaped usage for the cache meter.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: pure function, one job. Given the `usage` the framework put on
|
|
5
|
+
* `agentfootprint.stream.llm_end`, answer what is actually known
|
|
6
|
+
* about cache traffic on that call.
|
|
7
|
+
* Role: the honest half of the 9.59.0 meter fix. Before it, three
|
|
8
|
+
* strategies each parsed RAW WIRE field names (`cache_read_input_tokens`,
|
|
9
|
+
* `prompt_tokens_details.cached_tokens`) off a value that has never
|
|
10
|
+
* carried them — the framework normalises the wire at the ADAPTER
|
|
11
|
+
* ring (`readCacheUsage`) and only ever hands a strategy the port
|
|
12
|
+
* shape. Every field read `undefined`, `?? 0` turned that into a
|
|
13
|
+
* zero, the guard tripped, and a 20-call turn that hit cache on
|
|
14
|
+
* every call reported hitRate 0.
|
|
15
|
+
*
|
|
16
|
+
* The distinction this function exists to preserve: an adapter sets
|
|
17
|
+
* `cacheRead` / `cacheWrite` ONLY when the provider reported a number, so
|
|
18
|
+
* • both absent → nobody measured → `unknown`
|
|
19
|
+
* • either present → measured → `known` (a present 0 is a real 0)
|
|
20
|
+
* That is exactly the line `Claim<T>` was written to hold.
|
|
21
|
+
*/
|
|
22
|
+
import { type Claim } from '../lib/claim/claim.js';
|
|
23
|
+
import type { CacheMetrics, CacheUsage } from './types.js';
|
|
24
|
+
/**
|
|
25
|
+
* Read cache metrics off port usage.
|
|
26
|
+
*
|
|
27
|
+
* @param usage the `usage` field of `agentfootprint.stream.llm_end`
|
|
28
|
+
* @param who the adapter named in the evidence sentence, e.g. `'the Anthropic adapter'`
|
|
29
|
+
*/
|
|
30
|
+
export declare function readPortCacheUsage(usage: CacheUsage | undefined, who: string): Claim<CacheMetrics>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one reader of PORT-shaped usage for the cache meter.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: pure function, one job. Given the `usage` the framework put on
|
|
5
|
+
* `agentfootprint.stream.llm_end`, answer what is actually known
|
|
6
|
+
* about cache traffic on that call.
|
|
7
|
+
* Role: the honest half of the 9.59.0 meter fix. Before it, three
|
|
8
|
+
* strategies each parsed RAW WIRE field names (`cache_read_input_tokens`,
|
|
9
|
+
* `prompt_tokens_details.cached_tokens`) off a value that has never
|
|
10
|
+
* carried them — the framework normalises the wire at the ADAPTER
|
|
11
|
+
* ring (`readCacheUsage`) and only ever hands a strategy the port
|
|
12
|
+
* shape. Every field read `undefined`, `?? 0` turned that into a
|
|
13
|
+
* zero, the guard tripped, and a 20-call turn that hit cache on
|
|
14
|
+
* every call reported hitRate 0.
|
|
15
|
+
*
|
|
16
|
+
* The distinction this function exists to preserve: an adapter sets
|
|
17
|
+
* `cacheRead` / `cacheWrite` ONLY when the provider reported a number, so
|
|
18
|
+
* • both absent → nobody measured → `unknown`
|
|
19
|
+
* • either present → measured → `known` (a present 0 is a real 0)
|
|
20
|
+
* That is exactly the line `Claim<T>` was written to hold.
|
|
21
|
+
*/
|
|
22
|
+
import { known, unknown } from '../lib/claim/claim.js';
|
|
23
|
+
/**
|
|
24
|
+
* Read cache metrics off port usage.
|
|
25
|
+
*
|
|
26
|
+
* @param usage the `usage` field of `agentfootprint.stream.llm_end`
|
|
27
|
+
* @param who the adapter named in the evidence sentence, e.g. `'the Anthropic adapter'`
|
|
28
|
+
*/
|
|
29
|
+
export function readPortCacheUsage(usage, who) {
|
|
30
|
+
if (usage === undefined || usage === null || typeof usage !== 'object') {
|
|
31
|
+
return unknown('the llm_end event carried no usage payload, so nothing about cache traffic was measured', 'agentfootprint.stream.llm_end');
|
|
32
|
+
}
|
|
33
|
+
const { cacheRead, cacheWrite, input } = usage;
|
|
34
|
+
if (cacheRead === undefined && cacheWrite === undefined) {
|
|
35
|
+
return unknown(`${who} reported no cache fields on this call — that is "nobody measured", not "no cache traffic"`, 'agentfootprint.stream.llm_end usage (port shape)');
|
|
36
|
+
}
|
|
37
|
+
return known({
|
|
38
|
+
cacheReadTokens: cacheRead ?? 0,
|
|
39
|
+
cacheWriteTokens: cacheWrite ?? 0,
|
|
40
|
+
freshInputTokens: typeof input === 'number' ? input : 0,
|
|
41
|
+
}, `${who} reported cache token counts on the port usage`);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=portUsage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portUsage.js","sourceRoot":"","sources":["../../../src/cache/portUsage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,KAAK,EAAE,OAAO,EAAc,MAAM,uBAAuB,CAAC;AAGnE;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAA6B,EAC7B,GAAW;IAEX,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvE,OAAO,OAAO,CACZ,yFAAyF,EACzF,+BAA+B,CAChC,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC/C,IAAI,SAAS,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QACxD,OAAO,OAAO,CACZ,GAAG,GAAG,4FAA4F,EAClG,kDAAkD,CACnD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CACV;QACE,eAAe,EAAE,SAAS,IAAI,CAAC;QAC/B,gBAAgB,EAAE,UAAU,IAAI,CAAC;QACjC,gBAAgB,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACxD,EACD,GAAG,GAAG,gDAAgD,CACvD,CAAC;AACJ,CAAC"}
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
*
|
|
16
16
|
* What this strategy DOES vs DOESN'T do:
|
|
17
17
|
* - DOES: clamp markers, attach to LLMRequest.cacheMarkers,
|
|
18
|
-
*
|
|
18
|
+
* read cache metrics off the PORT usage the framework hands it
|
|
19
|
+
* (`{ input, output, cacheRead?, cacheWrite? }`) — never off the raw
|
|
20
|
+
* Anthropic wire, which the adapter has already normalised
|
|
19
21
|
* - DOES NOT: rewrite the wire body. The provider
|
|
20
22
|
* (BrowserAnthropicProvider) reads `cacheMarkers` and applies
|
|
21
23
|
* `cache_control` blocks during body construction. Separation of
|
|
@@ -25,7 +27,8 @@
|
|
|
25
27
|
* Auto-registers in the strategy registry on module import for
|
|
26
28
|
* provider names: 'anthropic', 'browser-anthropic'.
|
|
27
29
|
*/
|
|
28
|
-
import type { CacheCapabilities, CacheMarker, CacheMetrics, CacheStrategy, CacheStrategyContext } from '../types.js';
|
|
30
|
+
import type { CacheCapabilities, CacheMarker, CacheMetrics, CacheStrategy, CacheStrategyContext, CacheUsage } from '../types.js';
|
|
31
|
+
import type { Claim } from '../../lib/claim/claim.js';
|
|
29
32
|
import type { LLMRequest } from '../../adapters/types.js';
|
|
30
33
|
export declare class AnthropicCacheStrategy implements CacheStrategy {
|
|
31
34
|
readonly providerName = "anthropic";
|
|
@@ -34,5 +37,5 @@ export declare class AnthropicCacheStrategy implements CacheStrategy {
|
|
|
34
37
|
readonly request: LLMRequest;
|
|
35
38
|
readonly markersApplied: readonly CacheMarker[];
|
|
36
39
|
}>;
|
|
37
|
-
extractMetrics(usage:
|
|
40
|
+
extractMetrics(usage: CacheUsage | undefined): Claim<CacheMetrics>;
|
|
38
41
|
}
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
*
|
|
16
16
|
* What this strategy DOES vs DOESN'T do:
|
|
17
17
|
* - DOES: clamp markers, attach to LLMRequest.cacheMarkers,
|
|
18
|
-
*
|
|
18
|
+
* read cache metrics off the PORT usage the framework hands it
|
|
19
|
+
* (`{ input, output, cacheRead?, cacheWrite? }`) — never off the raw
|
|
20
|
+
* Anthropic wire, which the adapter has already normalised
|
|
19
21
|
* - DOES NOT: rewrite the wire body. The provider
|
|
20
22
|
* (BrowserAnthropicProvider) reads `cacheMarkers` and applies
|
|
21
23
|
* `cache_control` blocks during body construction. Separation of
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
* Auto-registers in the strategy registry on module import for
|
|
26
28
|
* provider names: 'anthropic', 'browser-anthropic'.
|
|
27
29
|
*/
|
|
30
|
+
import { readPortCacheUsage } from '../portUsage.js';
|
|
28
31
|
import { registerCacheStrategy } from '../strategyRegistry.js';
|
|
29
32
|
/** Anthropic enforces 4 cache breakpoints per request. */
|
|
30
33
|
const ANTHROPIC_MAX_MARKERS = 4;
|
|
@@ -62,22 +65,7 @@ export class AnthropicCacheStrategy {
|
|
|
62
65
|
return { request, markersApplied };
|
|
63
66
|
}
|
|
64
67
|
extractMetrics(usage) {
|
|
65
|
-
|
|
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
|
-
};
|
|
68
|
+
return readPortCacheUsage(usage, 'the Anthropic adapter');
|
|
81
69
|
}
|
|
82
70
|
}
|
|
83
71
|
// Auto-register on module import. Both 'anthropic' (server-side) and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnthropicCacheStrategy.js","sourceRoot":"","sources":["../../../../src/cache/strategies/AnthropicCacheStrategy.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"AnthropicCacheStrategy.js","sourceRoot":"","sources":["../../../../src/cache/strategies/AnthropicCacheStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAWH,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,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,KAA6B;QAC1C,OAAO,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC;IAC5D,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"}
|