@agimon-ai/po-log 0.0.0-alpha.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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/apiContracts.cjs +164 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +159 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/extensions/server.cjs +1034 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +1006 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +1542 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index-CTZMUfTZ.d.mts +185 -0
- package/dist/index-CTZMUfTZ.d.mts.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.mts +36 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/metrics-DywCpDJ_.mjs +508 -0
- package/dist/metrics-DywCpDJ_.mjs.map +1 -0
- package/dist/metrics-jCZe13Xg.cjs +573 -0
- package/dist/metrics-jCZe13Xg.cjs.map +1 -0
- package/dist/metrics.cjs +13 -0
- package/dist/metrics.d.mts +2 -0
- package/dist/metrics.mjs +2 -0
- package/dist/metricsSource-DXZLawQu.mjs +130 -0
- package/dist/metricsSource-DXZLawQu.mjs.map +1 -0
- package/dist/metricsSource-DdwQNzoY.cjs +135 -0
- package/dist/metricsSource-DdwQNzoY.cjs.map +1 -0
- package/dist/metricsSource-cZnErdv4.d.mts +51 -0
- package/dist/metricsSource-cZnErdv4.d.mts.map +1 -0
- package/dist/metricsSource.cjs +3 -0
- package/dist/metricsSource.d.mts +18 -0
- package/dist/metricsSource.d.mts.map +1 -0
- package/dist/metricsSource.mjs +2 -0
- package/llms.txt +7 -0
- package/package.json +140 -0
- package/src/prompts/po-debug-agent/SKILL.md +28 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
//#region src/services/findings/index.ts
|
|
2
|
+
/** Below this, the prompt prefix is churning and cache writes are being wasted. */
|
|
3
|
+
const LOW_CACHE_HIT_RATIO = .3;
|
|
4
|
+
/** Cache hit rate is meaningless on a session that has barely sent anything. */
|
|
5
|
+
const MIN_CACHE_SAMPLE_TOKENS = 5e4;
|
|
6
|
+
/** A tool is "expensive" only well clear of the session's typical turn. */
|
|
7
|
+
const EXPENSIVE_TOOL_RATIO = 1.5;
|
|
8
|
+
const MIN_TOOL_SAMPLES = 3;
|
|
9
|
+
const FAILING_TOOL_RATIO = .25;
|
|
10
|
+
const MIN_TOOL_CALLS = 3;
|
|
11
|
+
/** Sustained input this large per turn means context is carried, not scoped. */
|
|
12
|
+
const HEAVY_TURN_INPUT_TOKENS = 12e4;
|
|
13
|
+
const MIN_TURNS_FOR_AVERAGE = 3;
|
|
14
|
+
/** Tool calls per turn above this reads as search thrash rather than progress. */
|
|
15
|
+
const BUSY_TURN_TOOL_CALLS = 8;
|
|
16
|
+
const RATE_LIMITED = "rate_limited";
|
|
17
|
+
/** Kept short because the finding's subject column already says "provider". */
|
|
18
|
+
const SHORT_CATEGORY = {
|
|
19
|
+
[RATE_LIMITED]: "rate limited",
|
|
20
|
+
provider_unavailable: "unavailable"
|
|
21
|
+
};
|
|
22
|
+
const SEVERITY_RANK = {
|
|
23
|
+
critical: 0,
|
|
24
|
+
warning: 1,
|
|
25
|
+
info: 2
|
|
26
|
+
};
|
|
27
|
+
const PERCENT = 100;
|
|
28
|
+
const MILLION = 1e6;
|
|
29
|
+
const THOUSAND = 1e3;
|
|
30
|
+
const COMPACT_DECIMALS = 1;
|
|
31
|
+
const HEALTHY_FINDING = {
|
|
32
|
+
id: "healthy",
|
|
33
|
+
severity: "info",
|
|
34
|
+
subject: "session",
|
|
35
|
+
detail: "no failures, no outliers",
|
|
36
|
+
action: "nothing to act on yet"
|
|
37
|
+
};
|
|
38
|
+
function compact(value) {
|
|
39
|
+
if (value >= MILLION) return `${(value / MILLION).toFixed(COMPACT_DECIMALS)}M`;
|
|
40
|
+
if (value >= THOUSAND) return `${Math.round(value / THOUSAND)}k`;
|
|
41
|
+
return String(Math.round(value));
|
|
42
|
+
}
|
|
43
|
+
function percent(value) {
|
|
44
|
+
return `${Math.round(value * PERCENT)}%`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Median rather than mean: one pathological tool would drag a mean up far
|
|
48
|
+
* enough to hide itself from the comparison that is meant to catch it.
|
|
49
|
+
*/
|
|
50
|
+
function medianTokens(tools) {
|
|
51
|
+
const sampled = tools.map((tool) => tool.p90Tokens).filter((tokens) => tokens !== void 0).sort((left, right) => left - right);
|
|
52
|
+
if (sampled.length === 0) return 0;
|
|
53
|
+
const middle = Math.floor(sampled.length / 2);
|
|
54
|
+
const lower = sampled[middle - 1] ?? 0;
|
|
55
|
+
const upper = sampled[middle] ?? 0;
|
|
56
|
+
return sampled.length % 2 === 0 ? (lower + upper) / 2 : upper;
|
|
57
|
+
}
|
|
58
|
+
function cacheFinding(snapshot) {
|
|
59
|
+
const { cacheRead, cacheWrite, input } = snapshot.tokens;
|
|
60
|
+
const cacheable = cacheRead + input;
|
|
61
|
+
if (cacheable < MIN_CACHE_SAMPLE_TOKENS) return void 0;
|
|
62
|
+
const hitRatio = cacheRead / cacheable;
|
|
63
|
+
if (hitRatio >= LOW_CACHE_HIT_RATIO) return void 0;
|
|
64
|
+
return {
|
|
65
|
+
id: "low-cache-hit",
|
|
66
|
+
severity: "warning",
|
|
67
|
+
subject: "cache",
|
|
68
|
+
detail: `${percent(hitRatio)} hit · ${compact(cacheWrite)} written`,
|
|
69
|
+
action: "pin stable context first"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function expensiveToolFinding(snapshot) {
|
|
73
|
+
const median = medianTokens(snapshot.toolCost);
|
|
74
|
+
if (median <= 0) return void 0;
|
|
75
|
+
const worst = snapshot.toolCost.find((tool) => tool.samples >= MIN_TOOL_SAMPLES && (tool.p90Tokens ?? 0) >= median * EXPENSIVE_TOOL_RATIO);
|
|
76
|
+
if (!worst?.p90Tokens) return void 0;
|
|
77
|
+
return {
|
|
78
|
+
id: "expensive-tool",
|
|
79
|
+
severity: "warning",
|
|
80
|
+
subject: worst.name,
|
|
81
|
+
detail: `${compact(worst.p90Tokens)} p90 · ${Math.round(worst.p90Tokens / median)}× median`,
|
|
82
|
+
action: "narrow what it pulls in"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function failingToolFindings(snapshot) {
|
|
86
|
+
return snapshot.toolCost.filter((tool) => tool.calls >= MIN_TOOL_CALLS && tool.failed / tool.calls >= FAILING_TOOL_RATIO).sort((left, right) => right.failed - left.failed).map((tool) => ({
|
|
87
|
+
id: `failing-tool:${tool.name}`,
|
|
88
|
+
severity: "critical",
|
|
89
|
+
subject: tool.name,
|
|
90
|
+
detail: `${tool.failed} of ${tool.calls} failed`,
|
|
91
|
+
action: "retries burn tokens"
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
function abortedTurnFinding(snapshot) {
|
|
95
|
+
if (snapshot.abortedTurns + snapshot.failedTurns === 0) return void 0;
|
|
96
|
+
return {
|
|
97
|
+
id: "redone-turns",
|
|
98
|
+
severity: "critical",
|
|
99
|
+
subject: "turns",
|
|
100
|
+
detail: `${snapshot.abortedTurns} aborted · ${snapshot.failedTurns} failed`,
|
|
101
|
+
action: "paid for, then thrown away"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function providerFinding(snapshot) {
|
|
105
|
+
const category = snapshot.failureCategories.find((entry) => entry.name in SHORT_CATEGORY);
|
|
106
|
+
if (!category) return void 0;
|
|
107
|
+
return {
|
|
108
|
+
id: `provider:${category.name}`,
|
|
109
|
+
severity: "warning",
|
|
110
|
+
subject: "provider",
|
|
111
|
+
detail: `${category.count} × ${SHORT_CATEGORY[category.name] ?? category.name}`,
|
|
112
|
+
action: category.name === RATE_LIMITED ? "back off or drop concurrency" : "retry or switch provider"
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function contextFinding(snapshot) {
|
|
116
|
+
if (snapshot.turns < MIN_TURNS_FOR_AVERAGE) return void 0;
|
|
117
|
+
const perTurn = snapshot.tokens.input / snapshot.turns;
|
|
118
|
+
if (perTurn < HEAVY_TURN_INPUT_TOKENS) return void 0;
|
|
119
|
+
return {
|
|
120
|
+
id: "heavy-context",
|
|
121
|
+
severity: "warning",
|
|
122
|
+
subject: "context",
|
|
123
|
+
detail: `${compact(perTurn)} input tok/turn`,
|
|
124
|
+
action: "compact or split the task"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function toolChurnFinding(snapshot) {
|
|
128
|
+
if (snapshot.turns < MIN_TURNS_FOR_AVERAGE) return void 0;
|
|
129
|
+
const perTurn = snapshot.toolCalls / snapshot.turns;
|
|
130
|
+
if (perTurn < BUSY_TURN_TOOL_CALLS) return void 0;
|
|
131
|
+
return {
|
|
132
|
+
id: "tool-churn",
|
|
133
|
+
severity: "info",
|
|
134
|
+
subject: "tools",
|
|
135
|
+
detail: `${perTurn.toFixed(1)} calls/turn`,
|
|
136
|
+
action: "give it a narrower target"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Ranked most-actionable first. Severity leads because a failing tool costs
|
|
141
|
+
* more than an inefficient one, and the panel is read top-down under a
|
|
142
|
+
* height limit.
|
|
143
|
+
*/
|
|
144
|
+
function deriveFindings(snapshot) {
|
|
145
|
+
const findings = [
|
|
146
|
+
...failingToolFindings(snapshot),
|
|
147
|
+
abortedTurnFinding(snapshot),
|
|
148
|
+
providerFinding(snapshot),
|
|
149
|
+
cacheFinding(snapshot),
|
|
150
|
+
expensiveToolFinding(snapshot),
|
|
151
|
+
contextFinding(snapshot),
|
|
152
|
+
toolChurnFinding(snapshot)
|
|
153
|
+
].filter((finding) => finding !== void 0);
|
|
154
|
+
if (findings.length === 0) return snapshot.events > 0 ? [HEALTHY_FINDING] : [];
|
|
155
|
+
return findings.sort((left, right) => SEVERITY_RANK[left.severity] - SEVERITY_RANK[right.severity]);
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/services/metrics/index.ts
|
|
159
|
+
/**
|
|
160
|
+
* Session-local aggregation of the log records the Pi telemetry extension
|
|
161
|
+
* already emits.
|
|
162
|
+
*
|
|
163
|
+
* The aggregator is fed `(recordName, attributes)` pairs rather than Pi events
|
|
164
|
+
* so it can only ever see what the sink sees: no extra instrumentation is added
|
|
165
|
+
* for metrics, and nothing here depends on a live sink. Everything is scoped to
|
|
166
|
+
* the current process, so there is no persistence and no query API.
|
|
167
|
+
*/
|
|
168
|
+
/** Record names the aggregator derives numbers from. */
|
|
169
|
+
const TOOL_RESULT_RECORD = "pi.tool_result";
|
|
170
|
+
const API_ERROR_RECORD = "pi.api_error";
|
|
171
|
+
const TURN_FINISHED_RECORD = "pi.turn.finished";
|
|
172
|
+
const TURN_FAILED_RECORD = "pi.turn.failed";
|
|
173
|
+
const TOOL_TOKEN_SAMPLE_RECORD = "pi.tool_token_sample";
|
|
174
|
+
const UNKNOWN_TOOL = "unknown";
|
|
175
|
+
const TOOL_ERROR_CODE = "tool";
|
|
176
|
+
const RECORD_PREFIX = "pi.";
|
|
177
|
+
const DEFAULT_MAX_RECENT_ERRORS = 8;
|
|
178
|
+
/** Maximum entries retained in each cardinality-bearing metrics map, including overflow. */
|
|
179
|
+
const MAX_RETAINED_METRIC_NAMES = 128;
|
|
180
|
+
/** Snapshot row used to account for names that arrive after a metrics map reaches its bound. */
|
|
181
|
+
const METRIC_OVERFLOW_NAME = "__overflow__";
|
|
182
|
+
const P95_QUANTILE = .95;
|
|
183
|
+
const P90_QUANTILE = .9;
|
|
184
|
+
const ABORTED_OUTCOME = "aborted";
|
|
185
|
+
/**
|
|
186
|
+
* Failure taxonomy, mirroring log-sink's AgentIssueCategory closely enough that
|
|
187
|
+
* a session-local category reads the same as the one the sink would report for
|
|
188
|
+
* the same record. Rate limiting and provider outages are split out because
|
|
189
|
+
* they call for different action: back off versus retry elsewhere.
|
|
190
|
+
*/
|
|
191
|
+
const RATE_LIMIT_STATUS = 429;
|
|
192
|
+
const SERVER_ERROR_STATUS = 500;
|
|
193
|
+
const CATEGORY_TOOL_FAILURE = "tool_failure";
|
|
194
|
+
const CATEGORY_RATE_LIMITED = "rate_limited";
|
|
195
|
+
const CATEGORY_PROVIDER_UNAVAILABLE = "provider_unavailable";
|
|
196
|
+
const CATEGORY_API_ERROR = "api_error";
|
|
197
|
+
const CATEGORY_ABORTED = "aborted";
|
|
198
|
+
const CATEGORY_TURN_FAILED = "turn_failed";
|
|
199
|
+
const CATEGORY_LOG_ERROR = "log_error";
|
|
200
|
+
/**
|
|
201
|
+
* Latency is kept as a fixed logarithmic histogram rather than a sample array,
|
|
202
|
+
* so a long session cannot grow the retained state without bound. Each bucket
|
|
203
|
+
* is 10% wider than the last, which keeps the reported p95 within about 10% of
|
|
204
|
+
* the true value while capping the state at one small map per tool.
|
|
205
|
+
*/
|
|
206
|
+
const LATENCY_BUCKET_GROWTH = 1.1;
|
|
207
|
+
const LATENCY_BUCKET_LOG_BASE = Math.log(LATENCY_BUCKET_GROWTH);
|
|
208
|
+
const MINIMUM_SAMPLE_MS = 1;
|
|
209
|
+
/** Bucket ceiling per tool: covers 1ms to roughly an hour at 10% resolution. */
|
|
210
|
+
const MAX_RETAINED_STATE_PER_TOOL = 160;
|
|
211
|
+
/**
|
|
212
|
+
* Token distributions reuse the same histogram at a higher ceiling: a turn's
|
|
213
|
+
* total can run to millions once cache reads are counted, which the latency
|
|
214
|
+
* ceiling would saturate and report as a flat maximum.
|
|
215
|
+
*/
|
|
216
|
+
const MAX_RETAINED_TOKEN_BUCKETS = 200;
|
|
217
|
+
function emptySampleState() {
|
|
218
|
+
return {
|
|
219
|
+
samples: 0,
|
|
220
|
+
buckets: /* @__PURE__ */ new Map()
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function readNumber(value) {
|
|
224
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
225
|
+
}
|
|
226
|
+
function readString(value) {
|
|
227
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
228
|
+
}
|
|
229
|
+
function safeMetricName(value) {
|
|
230
|
+
return value.replace(/[^a-zA-Z0-9@/._:-]/g, "_").slice(0, 128) || "unknown";
|
|
231
|
+
}
|
|
232
|
+
/** Keeps an existing key, admits a new one when space remains, or selects the overflow row. */
|
|
233
|
+
function boundedMetricName(map, name, maxNames) {
|
|
234
|
+
if (map.has(name)) return name;
|
|
235
|
+
return map.size < maxNames - 1 ? name : METRIC_OVERFLOW_NAME;
|
|
236
|
+
}
|
|
237
|
+
function incrementBounded(map, name, maxNames) {
|
|
238
|
+
const boundedName = boundedMetricName(map, name, maxNames);
|
|
239
|
+
map.set(boundedName, (map.get(boundedName) ?? 0) + 1);
|
|
240
|
+
}
|
|
241
|
+
function boundedState(map, name, maxNames, create) {
|
|
242
|
+
const boundedName = boundedMetricName(map, name, maxNames);
|
|
243
|
+
const state = map.get(boundedName) ?? create();
|
|
244
|
+
map.set(boundedName, state);
|
|
245
|
+
return state;
|
|
246
|
+
}
|
|
247
|
+
function bucketIndex(value, cap) {
|
|
248
|
+
const index = Math.ceil(Math.log(Math.max(value, MINIMUM_SAMPLE_MS)) / LATENCY_BUCKET_LOG_BASE);
|
|
249
|
+
return Math.min(Math.max(index, 0), cap - 1);
|
|
250
|
+
}
|
|
251
|
+
function bucketUpperBound(index) {
|
|
252
|
+
return LATENCY_BUCKET_GROWTH ** index;
|
|
253
|
+
}
|
|
254
|
+
function observe(state, value, cap) {
|
|
255
|
+
const bucket = bucketIndex(value, cap);
|
|
256
|
+
state.buckets.set(bucket, (state.buckets.get(bucket) ?? 0) + 1);
|
|
257
|
+
state.samples += 1;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The upper edge of the first bucket whose cumulative count crosses the
|
|
261
|
+
* quantile. Reporting the edge rather than a bucket midpoint keeps the answer
|
|
262
|
+
* an upper bound, so a single 250ms call never reports faster than 250ms.
|
|
263
|
+
*/
|
|
264
|
+
function bucketedQuantile(state, quantile) {
|
|
265
|
+
if (state.samples === 0) return void 0;
|
|
266
|
+
const target = state.samples * quantile;
|
|
267
|
+
const indexes = [...state.buckets.keys()].sort((left, right) => left - right);
|
|
268
|
+
let cumulative = 0;
|
|
269
|
+
let selected = indexes[0];
|
|
270
|
+
for (const index of indexes) {
|
|
271
|
+
cumulative += state.buckets.get(index) ?? 0;
|
|
272
|
+
selected = index;
|
|
273
|
+
if (cumulative >= target) break;
|
|
274
|
+
}
|
|
275
|
+
return Math.round(bucketUpperBound(selected));
|
|
276
|
+
}
|
|
277
|
+
function bucketedP95(state) {
|
|
278
|
+
return bucketedQuantile(state, P95_QUANTILE);
|
|
279
|
+
}
|
|
280
|
+
/** Categorises an HTTP failure so the action it implies is distinguishable. */
|
|
281
|
+
function apiErrorCategory(status) {
|
|
282
|
+
if (status === RATE_LIMIT_STATUS) return CATEGORY_RATE_LIMITED;
|
|
283
|
+
if (status >= SERVER_ERROR_STATUS) return CATEGORY_PROVIDER_UNAVAILABLE;
|
|
284
|
+
return CATEGORY_API_ERROR;
|
|
285
|
+
}
|
|
286
|
+
/** Strips the `pi.` prefix so an error entry reads as the event it came from. */
|
|
287
|
+
function errorEventName(recordName) {
|
|
288
|
+
return recordName.startsWith(RECORD_PREFIX) ? recordName.slice(3) : recordName;
|
|
289
|
+
}
|
|
290
|
+
var LogMetricsAggregator = class {
|
|
291
|
+
events = 0;
|
|
292
|
+
errors = 0;
|
|
293
|
+
toolCalls = 0;
|
|
294
|
+
failedToolCalls = 0;
|
|
295
|
+
turns = 0;
|
|
296
|
+
abortedTurns = 0;
|
|
297
|
+
failedTurns = 0;
|
|
298
|
+
cost = 0;
|
|
299
|
+
tokens = {
|
|
300
|
+
input: 0,
|
|
301
|
+
output: 0,
|
|
302
|
+
total: 0,
|
|
303
|
+
cacheRead: 0,
|
|
304
|
+
cacheWrite: 0
|
|
305
|
+
};
|
|
306
|
+
eventCounts = /* @__PURE__ */ new Map();
|
|
307
|
+
packageCounts = /* @__PURE__ */ new Map();
|
|
308
|
+
/** Shared admission registry keeps the union of every tool-related map within one bound. */
|
|
309
|
+
toolNames = /* @__PURE__ */ new Map();
|
|
310
|
+
tools = /* @__PURE__ */ new Map();
|
|
311
|
+
toolTokens = /* @__PURE__ */ new Map();
|
|
312
|
+
toolFailures = /* @__PURE__ */ new Map();
|
|
313
|
+
failureCategories = /* @__PURE__ */ new Map();
|
|
314
|
+
operations = /* @__PURE__ */ new Map();
|
|
315
|
+
sanitizedFailures = 0;
|
|
316
|
+
errorLog = [];
|
|
317
|
+
now;
|
|
318
|
+
maxRecentErrors;
|
|
319
|
+
maxMetricNames;
|
|
320
|
+
constructor(options = {}) {
|
|
321
|
+
this.now = options.now ?? Date.now;
|
|
322
|
+
this.maxRecentErrors = options.maxRecentErrors ?? DEFAULT_MAX_RECENT_ERRORS;
|
|
323
|
+
const requestedMaxNames = options.maxMetricNames ?? 128;
|
|
324
|
+
this.maxMetricNames = Number.isFinite(requestedMaxNames) ? Math.min(128, Math.max(1, Math.floor(requestedMaxNames))) : 128;
|
|
325
|
+
}
|
|
326
|
+
record(name, attributes) {
|
|
327
|
+
const metricName = safeMetricName(name);
|
|
328
|
+
this.events += 1;
|
|
329
|
+
incrementBounded(this.eventCounts, metricName, this.maxMetricNames);
|
|
330
|
+
const packageName = readString(attributes["telemetry.package"]);
|
|
331
|
+
if (packageName && !packageName.startsWith("/") && !packageName.includes("\\")) incrementBounded(this.packageCounts, safeMetricName(packageName), this.maxMetricNames);
|
|
332
|
+
this.recordOperationDuration(metricName, attributes);
|
|
333
|
+
this.recordSanitizedFailure(metricName, attributes);
|
|
334
|
+
if (metricName === "pi.tool_result") this.recordToolResult(metricName, attributes);
|
|
335
|
+
else if (metricName === "pi.api_error") this.recordApiError(metricName, attributes);
|
|
336
|
+
else if (metricName === "pi.turn.finished") this.recordTurnUsage(attributes);
|
|
337
|
+
else if (metricName === "pi.turn.failed") this.recordTurnFailure(attributes);
|
|
338
|
+
else if (metricName === "pi.tool_token_sample") this.recordToolTokenSample(attributes);
|
|
339
|
+
}
|
|
340
|
+
snapshot() {
|
|
341
|
+
const toolLatency = [...this.tools.entries()].map(([name, state]) => {
|
|
342
|
+
const p95Ms = bucketedP95(state);
|
|
343
|
+
return {
|
|
344
|
+
name,
|
|
345
|
+
calls: state.calls,
|
|
346
|
+
retainedStateSize: state.buckets.size,
|
|
347
|
+
...p95Ms === void 0 ? {} : { p95Ms }
|
|
348
|
+
};
|
|
349
|
+
}).sort((left, right) => (right.p95Ms ?? -1) - (left.p95Ms ?? -1));
|
|
350
|
+
const eventVolume = [...this.eventCounts.entries()].map(([name, count]) => ({
|
|
351
|
+
name,
|
|
352
|
+
count
|
|
353
|
+
})).sort((left, right) => right.count - left.count);
|
|
354
|
+
const packageEvents = [...this.packageCounts.entries()].map(([name, count]) => ({
|
|
355
|
+
name,
|
|
356
|
+
count
|
|
357
|
+
})).sort((left, right) => right.count - left.count);
|
|
358
|
+
const operationDuration = [...this.operations.entries()].map(([name, state]) => {
|
|
359
|
+
const p95Ms = bucketedP95(state);
|
|
360
|
+
return {
|
|
361
|
+
name,
|
|
362
|
+
calls: state.calls,
|
|
363
|
+
retainedStateSize: state.buckets.size,
|
|
364
|
+
...p95Ms === void 0 ? {} : { p95Ms }
|
|
365
|
+
};
|
|
366
|
+
}).sort((left, right) => (right.p95Ms ?? -1) - (left.p95Ms ?? -1));
|
|
367
|
+
return {
|
|
368
|
+
events: this.events,
|
|
369
|
+
errors: this.errors,
|
|
370
|
+
toolCalls: this.toolCalls,
|
|
371
|
+
failedToolCalls: this.failedToolCalls,
|
|
372
|
+
turns: this.turns,
|
|
373
|
+
abortedTurns: this.abortedTurns,
|
|
374
|
+
failedTurns: this.failedTurns,
|
|
375
|
+
tokens: { ...this.tokens },
|
|
376
|
+
cost: this.cost,
|
|
377
|
+
toolLatency,
|
|
378
|
+
toolCost: this.toolCostRows(),
|
|
379
|
+
failureCategories: [...this.failureCategories.entries()].map(([name, count]) => ({
|
|
380
|
+
name,
|
|
381
|
+
count
|
|
382
|
+
})).sort((left, right) => right.count - left.count),
|
|
383
|
+
eventVolume,
|
|
384
|
+
packageEvents,
|
|
385
|
+
sanitizedFailures: this.sanitizedFailures,
|
|
386
|
+
operationDuration,
|
|
387
|
+
recentErrors: this.errorLog.map((entry) => ({ ...entry }))
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Ranked by cost per call rather than call count: a tool invoked twice into a
|
|
392
|
+
* 300k-token turn is the one worth changing, and a frequency ranking buries
|
|
393
|
+
* it under the cheap tools that dominate any transcript.
|
|
394
|
+
*/
|
|
395
|
+
toolCostRows() {
|
|
396
|
+
return [.../* @__PURE__ */ new Set([...this.tools.keys(), ...this.toolTokens.keys()])].map((name) => {
|
|
397
|
+
const tokens = this.toolTokens.get(name);
|
|
398
|
+
const p90Tokens = tokens ? bucketedQuantile(tokens, P90_QUANTILE) : void 0;
|
|
399
|
+
return {
|
|
400
|
+
name,
|
|
401
|
+
calls: this.tools.get(name)?.calls ?? 0,
|
|
402
|
+
failed: this.toolFailures.get(name) ?? 0,
|
|
403
|
+
samples: tokens?.samples ?? 0,
|
|
404
|
+
...p90Tokens === void 0 ? {} : { p90Tokens }
|
|
405
|
+
};
|
|
406
|
+
}).sort((left, right) => (right.p90Tokens ?? -1) - (left.p90Tokens ?? -1));
|
|
407
|
+
}
|
|
408
|
+
boundedToolName(name) {
|
|
409
|
+
const boundedName = boundedMetricName(this.toolNames, name, this.maxMetricNames);
|
|
410
|
+
this.toolNames.set(boundedName, true);
|
|
411
|
+
return boundedName;
|
|
412
|
+
}
|
|
413
|
+
countCategory(category) {
|
|
414
|
+
incrementBounded(this.failureCategories, category, this.maxMetricNames);
|
|
415
|
+
}
|
|
416
|
+
recordOperationDuration(recordName, attributes) {
|
|
417
|
+
const duration = Object.entries(attributes).find(([key, value]) => key !== "tool.duration_ms" && (key === "duration_ms" || key.endsWith(".duration_ms")) && readNumber(value) !== void 0);
|
|
418
|
+
if (!duration) return;
|
|
419
|
+
const operationName = safeMetricName(readString(attributes["operation.name"]) ?? recordName);
|
|
420
|
+
const state = boundedState(this.operations, operationName, this.maxMetricNames, () => ({
|
|
421
|
+
calls: 0,
|
|
422
|
+
samples: 0,
|
|
423
|
+
buckets: /* @__PURE__ */ new Map()
|
|
424
|
+
}));
|
|
425
|
+
state.calls += 1;
|
|
426
|
+
const durationMs = readNumber(duration[1]);
|
|
427
|
+
if (durationMs === void 0) return;
|
|
428
|
+
observe(state, durationMs, 160);
|
|
429
|
+
}
|
|
430
|
+
recordSanitizedFailure(recordName, attributes) {
|
|
431
|
+
const outcome = readString(attributes.outcome);
|
|
432
|
+
if (!(attributes["tool.result.error"] === true || recordName === "pi.api_error" || outcome === "error" || outcome === "failed" || outcome === "failure")) return;
|
|
433
|
+
this.sanitizedFailures += 1;
|
|
434
|
+
if (recordName === "pi.tool_result" || recordName === "pi.api_error") return;
|
|
435
|
+
if (recordName !== "pi.turn.failed") this.countCategory(CATEGORY_LOG_ERROR);
|
|
436
|
+
const code = readString(attributes["error.code"]) ?? outcome ?? "error";
|
|
437
|
+
this.pushError(recordName, `${readString(attributes["error.type"]) ?? "operation"} failed`, safeMetricName(code));
|
|
438
|
+
}
|
|
439
|
+
recordToolResult(recordName, attributes) {
|
|
440
|
+
const toolName = readString(attributes["tool.name"]) ?? UNKNOWN_TOOL;
|
|
441
|
+
const boundedToolName = this.boundedToolName(toolName);
|
|
442
|
+
const state = boundedState(this.tools, boundedToolName, this.maxMetricNames, () => ({
|
|
443
|
+
calls: 0,
|
|
444
|
+
samples: 0,
|
|
445
|
+
buckets: /* @__PURE__ */ new Map()
|
|
446
|
+
}));
|
|
447
|
+
state.calls += 1;
|
|
448
|
+
this.toolCalls += 1;
|
|
449
|
+
const durationMs = readNumber(attributes["tool.duration_ms"]);
|
|
450
|
+
if (durationMs !== void 0) observe(state, durationMs, 160);
|
|
451
|
+
if (attributes["tool.result.error"] === true) {
|
|
452
|
+
this.failedToolCalls += 1;
|
|
453
|
+
incrementBounded(this.toolFailures, boundedToolName, this.maxMetricNames);
|
|
454
|
+
this.countCategory(CATEGORY_TOOL_FAILURE);
|
|
455
|
+
this.pushError(recordName, `${boundedToolName}: tool call failed`, TOOL_ERROR_CODE);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Attributes a turn's token total to every tool that ran in it. The Pi
|
|
460
|
+
* extension emits one sample per tool result carrying the whole turn's usage,
|
|
461
|
+
* so this builds a per-tool distribution of "how expensive is a turn that
|
|
462
|
+
* uses this tool" — see LogMetricsToolCost for what that does and does not
|
|
463
|
+
* claim.
|
|
464
|
+
*/
|
|
465
|
+
recordToolTokenSample(attributes) {
|
|
466
|
+
const totalTokens = readNumber(attributes["gen_ai.usage.total_tokens"]);
|
|
467
|
+
if (totalTokens === void 0 || totalTokens <= 0) return;
|
|
468
|
+
const toolName = readString(attributes["tool.name"]) ?? UNKNOWN_TOOL;
|
|
469
|
+
const boundedToolName = this.boundedToolName(toolName);
|
|
470
|
+
observe(boundedState(this.toolTokens, boundedToolName, this.maxMetricNames, emptySampleState), totalTokens, 200);
|
|
471
|
+
}
|
|
472
|
+
recordApiError(recordName, attributes) {
|
|
473
|
+
this.errors += 1;
|
|
474
|
+
const statusCode = readNumber(attributes["http.response.status_code"]);
|
|
475
|
+
const status = String(attributes["http.response.status_code"]);
|
|
476
|
+
this.countCategory(statusCode === void 0 ? CATEGORY_API_ERROR : apiErrorCategory(statusCode));
|
|
477
|
+
this.pushError(recordName, `provider responded ${status}`, status);
|
|
478
|
+
}
|
|
479
|
+
recordTurnUsage(attributes) {
|
|
480
|
+
this.turns += 1;
|
|
481
|
+
this.tokens.input += readNumber(attributes["gen_ai.usage.input_tokens"]) ?? 0;
|
|
482
|
+
this.tokens.output += readNumber(attributes["gen_ai.usage.output_tokens"]) ?? 0;
|
|
483
|
+
this.tokens.total += readNumber(attributes["gen_ai.usage.total_tokens"]) ?? 0;
|
|
484
|
+
this.tokens.cacheRead += readNumber(attributes["gen_ai.usage.cache_read_tokens"]) ?? 0;
|
|
485
|
+
this.tokens.cacheWrite += readNumber(attributes["gen_ai.usage.cache_write_tokens"]) ?? 0;
|
|
486
|
+
this.cost += readNumber(attributes["gen_ai.usage.cost"]) ?? 0;
|
|
487
|
+
}
|
|
488
|
+
/** A turn the user has to redo: the most expensive failure mode there is. */
|
|
489
|
+
recordTurnFailure(attributes) {
|
|
490
|
+
const aborted = readString(attributes.outcome) === ABORTED_OUTCOME;
|
|
491
|
+
if (aborted) this.abortedTurns += 1;
|
|
492
|
+
else this.failedTurns += 1;
|
|
493
|
+
this.countCategory(aborted ? CATEGORY_ABORTED : CATEGORY_TURN_FAILED);
|
|
494
|
+
}
|
|
495
|
+
pushError(recordName, message, code) {
|
|
496
|
+
this.errorLog.unshift({
|
|
497
|
+
at: this.now(),
|
|
498
|
+
event: errorEventName(recordName),
|
|
499
|
+
message,
|
|
500
|
+
code
|
|
501
|
+
});
|
|
502
|
+
if (this.errorLog.length > this.maxRecentErrors) this.errorLog.length = this.maxRecentErrors;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
//#endregion
|
|
506
|
+
Object.defineProperty(exports, "API_ERROR_RECORD", {
|
|
507
|
+
enumerable: true,
|
|
508
|
+
get: function() {
|
|
509
|
+
return API_ERROR_RECORD;
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
Object.defineProperty(exports, "LogMetricsAggregator", {
|
|
513
|
+
enumerable: true,
|
|
514
|
+
get: function() {
|
|
515
|
+
return LogMetricsAggregator;
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
Object.defineProperty(exports, "MAX_RETAINED_METRIC_NAMES", {
|
|
519
|
+
enumerable: true,
|
|
520
|
+
get: function() {
|
|
521
|
+
return MAX_RETAINED_METRIC_NAMES;
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
Object.defineProperty(exports, "MAX_RETAINED_STATE_PER_TOOL", {
|
|
525
|
+
enumerable: true,
|
|
526
|
+
get: function() {
|
|
527
|
+
return MAX_RETAINED_STATE_PER_TOOL;
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
Object.defineProperty(exports, "MAX_RETAINED_TOKEN_BUCKETS", {
|
|
531
|
+
enumerable: true,
|
|
532
|
+
get: function() {
|
|
533
|
+
return MAX_RETAINED_TOKEN_BUCKETS;
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
Object.defineProperty(exports, "METRIC_OVERFLOW_NAME", {
|
|
537
|
+
enumerable: true,
|
|
538
|
+
get: function() {
|
|
539
|
+
return METRIC_OVERFLOW_NAME;
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
Object.defineProperty(exports, "TOOL_RESULT_RECORD", {
|
|
543
|
+
enumerable: true,
|
|
544
|
+
get: function() {
|
|
545
|
+
return TOOL_RESULT_RECORD;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
Object.defineProperty(exports, "TOOL_TOKEN_SAMPLE_RECORD", {
|
|
549
|
+
enumerable: true,
|
|
550
|
+
get: function() {
|
|
551
|
+
return TOOL_TOKEN_SAMPLE_RECORD;
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
Object.defineProperty(exports, "TURN_FAILED_RECORD", {
|
|
555
|
+
enumerable: true,
|
|
556
|
+
get: function() {
|
|
557
|
+
return TURN_FAILED_RECORD;
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
Object.defineProperty(exports, "TURN_FINISHED_RECORD", {
|
|
561
|
+
enumerable: true,
|
|
562
|
+
get: function() {
|
|
563
|
+
return TURN_FINISHED_RECORD;
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
Object.defineProperty(exports, "deriveFindings", {
|
|
567
|
+
enumerable: true,
|
|
568
|
+
get: function() {
|
|
569
|
+
return deriveFindings;
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
//# sourceMappingURL=metrics-jCZe13Xg.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-jCZe13Xg.cjs","names":[],"sources":["../src/services/findings/index.ts","../src/services/metrics/index.ts"],"sourcesContent":["/**\n * Ranked, actionable findings derived from a session snapshot.\n *\n * Every finding restates numbers the aggregator already holds and pairs them\n * with the one change that moves them, because a counter the user cannot act on\n * is screen space spent for nothing. Nothing here queries the sink: the panel\n * has to stay useful when no sink is connected, which is exactly the situation\n * where a user has no other way to see any of this.\n *\n * Thresholds are deliberately conservative. A finding that fires on a healthy\n * session teaches the user to ignore the panel, which is worse than an empty\n * one.\n */\n\nimport type { LogMetricsSnapshot, LogMetricsToolCost } from '../metrics';\n\nexport type LogMetricsFindingSeverity = 'critical' | 'warning' | 'info';\n\nexport interface LogMetricsFinding {\n /** Stable identifier, so a finding can be asserted without matching prose. */\n id: string;\n severity: LogMetricsFindingSeverity;\n /** What the finding is about: a tool name, or a facet like `cache`. */\n subject: string;\n /** The evidence, already formatted. */\n detail: string;\n /** The change that moves the number. */\n action: string;\n}\n\n/** Below this, the prompt prefix is churning and cache writes are being wasted. */\nconst LOW_CACHE_HIT_RATIO = 0.3;\n/** Cache hit rate is meaningless on a session that has barely sent anything. */\nconst MIN_CACHE_SAMPLE_TOKENS = 50_000;\n/** A tool is \"expensive\" only well clear of the session's typical turn. */\nconst EXPENSIVE_TOOL_RATIO = 1.5;\nconst MIN_TOOL_SAMPLES = 3;\nconst FAILING_TOOL_RATIO = 0.25;\nconst MIN_TOOL_CALLS = 3;\n/** Sustained input this large per turn means context is carried, not scoped. */\nconst HEAVY_TURN_INPUT_TOKENS = 120_000;\nconst MIN_TURNS_FOR_AVERAGE = 3;\n/** Tool calls per turn above this reads as search thrash rather than progress. */\nconst BUSY_TURN_TOOL_CALLS = 8;\n\nconst RATE_LIMITED = 'rate_limited';\n/** Kept short because the finding's subject column already says \"provider\". */\nconst SHORT_CATEGORY: Record<string, string> = {\n [RATE_LIMITED]: 'rate limited',\n provider_unavailable: 'unavailable',\n};\n\nconst SEVERITY_RANK: Record<LogMetricsFindingSeverity, number> = { critical: 0, warning: 1, info: 2 };\nconst PERCENT = 100;\nconst MILLION = 1_000_000;\nconst THOUSAND = 1000;\nconst COMPACT_DECIMALS = 1;\nconst HEALTHY_FINDING: LogMetricsFinding = {\n id: 'healthy',\n severity: 'info',\n subject: 'session',\n detail: 'no failures, no outliers',\n action: 'nothing to act on yet',\n};\n\nfunction compact(value: number): string {\n if (value >= MILLION) return `${(value / MILLION).toFixed(COMPACT_DECIMALS)}M`;\n if (value >= THOUSAND) return `${Math.round(value / THOUSAND)}k`;\n return String(Math.round(value));\n}\n\nfunction percent(value: number): string {\n return `${Math.round(value * PERCENT)}%`;\n}\n\n/**\n * Median rather than mean: one pathological tool would drag a mean up far\n * enough to hide itself from the comparison that is meant to catch it.\n */\nfunction medianTokens(tools: readonly LogMetricsToolCost[]): number {\n const sampled = tools\n .map((tool) => tool.p90Tokens)\n .filter((tokens): tokens is number => tokens !== undefined)\n .sort((left, right) => left - right);\n if (sampled.length === 0) return 0;\n const middle = Math.floor(sampled.length / 2);\n const lower = sampled[middle - 1] ?? 0;\n const upper = sampled[middle] ?? 0;\n return sampled.length % 2 === 0 ? (lower + upper) / 2 : upper;\n}\n\nfunction cacheFinding(snapshot: LogMetricsSnapshot): LogMetricsFinding | undefined {\n const { cacheRead, cacheWrite, input } = snapshot.tokens;\n const cacheable = cacheRead + input;\n if (cacheable < MIN_CACHE_SAMPLE_TOKENS) return undefined;\n\n const hitRatio = cacheRead / cacheable;\n if (hitRatio >= LOW_CACHE_HIT_RATIO) return undefined;\n return {\n id: 'low-cache-hit',\n severity: 'warning',\n subject: 'cache',\n detail: `${percent(hitRatio)} hit · ${compact(cacheWrite)} written`,\n action: 'pin stable context first',\n };\n}\n\nfunction expensiveToolFinding(snapshot: LogMetricsSnapshot): LogMetricsFinding | undefined {\n const median = medianTokens(snapshot.toolCost);\n if (median <= 0) return undefined;\n\n const worst = snapshot.toolCost.find(\n (tool) => tool.samples >= MIN_TOOL_SAMPLES && (tool.p90Tokens ?? 0) >= median * EXPENSIVE_TOOL_RATIO,\n );\n if (!worst?.p90Tokens) return undefined;\n return {\n id: 'expensive-tool',\n severity: 'warning',\n subject: worst.name,\n detail: `${compact(worst.p90Tokens)} p90 · ${Math.round(worst.p90Tokens / median)}× median`,\n action: 'narrow what it pulls in',\n };\n}\n\nfunction failingToolFindings(snapshot: LogMetricsSnapshot): LogMetricsFinding[] {\n return snapshot.toolCost\n .filter((tool) => tool.calls >= MIN_TOOL_CALLS && tool.failed / tool.calls >= FAILING_TOOL_RATIO)\n .sort((left, right) => right.failed - left.failed)\n .map((tool) => ({\n id: `failing-tool:${tool.name}`,\n severity: 'critical' as const,\n subject: tool.name,\n detail: `${tool.failed} of ${tool.calls} failed`,\n action: 'retries burn tokens',\n }));\n}\n\nfunction abortedTurnFinding(snapshot: LogMetricsSnapshot): LogMetricsFinding | undefined {\n const redone = snapshot.abortedTurns + snapshot.failedTurns;\n if (redone === 0) return undefined;\n return {\n id: 'redone-turns',\n severity: 'critical',\n subject: 'turns',\n detail: `${snapshot.abortedTurns} aborted · ${snapshot.failedTurns} failed`,\n action: 'paid for, then thrown away',\n };\n}\n\nfunction providerFinding(snapshot: LogMetricsSnapshot): LogMetricsFinding | undefined {\n const category = snapshot.failureCategories.find((entry) => entry.name in SHORT_CATEGORY);\n if (!category) return undefined;\n return {\n id: `provider:${category.name}`,\n severity: 'warning',\n subject: 'provider',\n detail: `${category.count} × ${SHORT_CATEGORY[category.name] ?? category.name}`,\n action: category.name === RATE_LIMITED ? 'back off or drop concurrency' : 'retry or switch provider',\n };\n}\n\nfunction contextFinding(snapshot: LogMetricsSnapshot): LogMetricsFinding | undefined {\n if (snapshot.turns < MIN_TURNS_FOR_AVERAGE) return undefined;\n\n const perTurn = snapshot.tokens.input / snapshot.turns;\n if (perTurn < HEAVY_TURN_INPUT_TOKENS) return undefined;\n return {\n id: 'heavy-context',\n severity: 'warning',\n subject: 'context',\n detail: `${compact(perTurn)} input tok/turn`,\n action: 'compact or split the task',\n };\n}\n\nfunction toolChurnFinding(snapshot: LogMetricsSnapshot): LogMetricsFinding | undefined {\n if (snapshot.turns < MIN_TURNS_FOR_AVERAGE) return undefined;\n\n const perTurn = snapshot.toolCalls / snapshot.turns;\n if (perTurn < BUSY_TURN_TOOL_CALLS) return undefined;\n return {\n id: 'tool-churn',\n severity: 'info',\n subject: 'tools',\n detail: `${perTurn.toFixed(1)} calls/turn`,\n action: 'give it a narrower target',\n };\n}\n\n/**\n * Ranked most-actionable first. Severity leads because a failing tool costs\n * more than an inefficient one, and the panel is read top-down under a\n * height limit.\n */\nexport function deriveFindings(snapshot: LogMetricsSnapshot): LogMetricsFinding[] {\n const findings = [\n ...failingToolFindings(snapshot),\n abortedTurnFinding(snapshot),\n providerFinding(snapshot),\n cacheFinding(snapshot),\n expensiveToolFinding(snapshot),\n contextFinding(snapshot),\n toolChurnFinding(snapshot),\n ].filter((finding): finding is LogMetricsFinding => finding !== undefined);\n\n if (findings.length === 0) return snapshot.events > 0 ? [HEALTHY_FINDING] : [];\n return findings.sort((left, right) => SEVERITY_RANK[left.severity] - SEVERITY_RANK[right.severity]);\n}\n","/**\n * Session-local aggregation of the log records the Pi telemetry extension\n * already emits.\n *\n * The aggregator is fed `(recordName, attributes)` pairs rather than Pi events\n * so it can only ever see what the sink sees: no extra instrumentation is added\n * for metrics, and nothing here depends on a live sink. Everything is scoped to\n * the current process, so there is no persistence and no query API.\n */\n\n/** Record names the aggregator derives numbers from. */\nexport const TOOL_RESULT_RECORD = 'pi.tool_result';\nexport const API_ERROR_RECORD = 'pi.api_error';\nexport const TURN_FINISHED_RECORD = 'pi.turn.finished';\nexport const TURN_FAILED_RECORD = 'pi.turn.failed';\nexport const TOOL_TOKEN_SAMPLE_RECORD = 'pi.tool_token_sample';\n\nconst UNKNOWN_TOOL = 'unknown';\nconst TOOL_ERROR_CODE = 'tool';\nconst RECORD_PREFIX = 'pi.';\nconst DEFAULT_MAX_RECENT_ERRORS = 8;\n/** Maximum entries retained in each cardinality-bearing metrics map, including overflow. */\nexport const MAX_RETAINED_METRIC_NAMES = 128;\n/** Snapshot row used to account for names that arrive after a metrics map reaches its bound. */\nexport const METRIC_OVERFLOW_NAME = '__overflow__';\nconst P95_QUANTILE = 0.95;\nconst P90_QUANTILE = 0.9;\nconst ABORTED_OUTCOME = 'aborted';\n\n/**\n * Failure taxonomy, mirroring log-sink's AgentIssueCategory closely enough that\n * a session-local category reads the same as the one the sink would report for\n * the same record. Rate limiting and provider outages are split out because\n * they call for different action: back off versus retry elsewhere.\n */\nconst RATE_LIMIT_STATUS = 429;\nconst SERVER_ERROR_STATUS = 500;\nconst CATEGORY_TOOL_FAILURE = 'tool_failure';\nconst CATEGORY_RATE_LIMITED = 'rate_limited';\nconst CATEGORY_PROVIDER_UNAVAILABLE = 'provider_unavailable';\nconst CATEGORY_API_ERROR = 'api_error';\nconst CATEGORY_ABORTED = 'aborted';\nconst CATEGORY_TURN_FAILED = 'turn_failed';\nconst CATEGORY_LOG_ERROR = 'log_error';\n\n/**\n * Latency is kept as a fixed logarithmic histogram rather than a sample array,\n * so a long session cannot grow the retained state without bound. Each bucket\n * is 10% wider than the last, which keeps the reported p95 within about 10% of\n * the true value while capping the state at one small map per tool.\n */\nconst LATENCY_BUCKET_GROWTH = 1.1;\nconst LATENCY_BUCKET_LOG_BASE = Math.log(LATENCY_BUCKET_GROWTH);\nconst MINIMUM_SAMPLE_MS = 1;\n\n/** Bucket ceiling per tool: covers 1ms to roughly an hour at 10% resolution. */\nexport const MAX_RETAINED_STATE_PER_TOOL = 160;\n\n/**\n * Token distributions reuse the same histogram at a higher ceiling: a turn's\n * total can run to millions once cache reads are counted, which the latency\n * ceiling would saturate and report as a flat maximum.\n */\nexport const MAX_RETAINED_TOKEN_BUCKETS = 200;\n\nexport interface LogMetricsTokenTotals {\n input: number;\n output: number;\n total: number;\n /**\n * Cached prompt tokens billed at the read rate. Held separately from `input`\n * because the ratio between them is the session's cache hit rate, which is\n * the single largest cost lever a user controls.\n */\n cacheRead: number;\n cacheWrite: number;\n}\n\nexport interface LogMetricsToolLatency {\n name: string;\n calls: number;\n p95Ms?: number;\n /** Buckets currently held for this tool, bounded by MAX_RETAINED_STATE_PER_TOOL. */\n retainedStateSize: number;\n}\n\nexport interface LogMetricsEventCount {\n name: string;\n count: number;\n}\n\nexport interface LogMetricsOperationDuration {\n name: string;\n calls: number;\n p95Ms?: number;\n retainedStateSize: number;\n}\n\nexport interface LogMetricsError {\n at: number;\n event: string;\n message: string;\n code: string;\n}\n\n/**\n * What a tool costs, rather than how long it takes.\n *\n * `p90Tokens` is the 90th percentile of the *turn* total for turns this tool\n * took part in, which is the only attribution the Pi extension emits\n * (`token_attribution: 'toolCallTurn'`). It answers \"when this tool runs, how\n * expensive is the turn\", not \"this tool consumed N tokens\" — a turn with\n * several tool calls attributes its total to each of them.\n */\nexport interface LogMetricsToolCost {\n name: string;\n calls: number;\n failed: number;\n /** Turns carrying usage that this tool took part in, i.e. the p90 sample count. */\n samples: number;\n p90Tokens?: number;\n}\n\nexport interface LogMetricsSnapshot {\n events: number;\n errors: number;\n toolCalls: number;\n failedToolCalls: number;\n /** Turns that reported usage, i.e. the denominator for per-turn averages. */\n turns: number;\n abortedTurns: number;\n failedTurns: number;\n tokens: LogMetricsTokenTotals;\n cost: number;\n toolLatency: LogMetricsToolLatency[];\n toolCost: LogMetricsToolCost[];\n failureCategories: LogMetricsEventCount[];\n eventVolume: LogMetricsEventCount[];\n packageEvents: LogMetricsEventCount[];\n sanitizedFailures: number;\n operationDuration: LogMetricsOperationDuration[];\n recentErrors: LogMetricsError[];\n}\n\nexport interface LogMetricsAggregatorOptions {\n now?: () => number;\n maxRecentErrors?: number;\n /** Per-map cardinality bound for tests, capped at MAX_RETAINED_METRIC_NAMES. */\n maxMetricNames?: number;\n}\n\n/** The slice of the aggregator the telemetry extension needs. */\nexport interface LogMetricsRecorder {\n record(name: string, attributes: Record<string, unknown>): void;\n}\n\ninterface ToolLatencyState {\n calls: number;\n samples: number;\n buckets: Map<number, number>;\n}\n\ninterface SampleState {\n samples: number;\n buckets: Map<number, number>;\n}\n\nfunction emptySampleState(): SampleState {\n return { samples: 0, buckets: new Map<number, number>() };\n}\n\nfunction readNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction safeMetricName(value: string): string {\n const normalized = value.replace(/[^a-zA-Z0-9@/._:-]/g, '_').slice(0, 128);\n return normalized || 'unknown';\n}\n\n/** Keeps an existing key, admits a new one when space remains, or selects the overflow row. */\nfunction boundedMetricName<T>(map: Map<string, T>, name: string, maxNames: number): string {\n if (map.has(name)) return name;\n return map.size < maxNames - 1 ? name : METRIC_OVERFLOW_NAME;\n}\n\nfunction incrementBounded(map: Map<string, number>, name: string, maxNames: number): void {\n const boundedName = boundedMetricName(map, name, maxNames);\n map.set(boundedName, (map.get(boundedName) ?? 0) + 1);\n}\n\nfunction boundedState<T>(map: Map<string, T>, name: string, maxNames: number, create: () => T): T {\n const boundedName = boundedMetricName(map, name, maxNames);\n const state = map.get(boundedName) ?? create();\n map.set(boundedName, state);\n return state;\n}\n\nfunction bucketIndex(value: number, cap: number): number {\n const index = Math.ceil(Math.log(Math.max(value, MINIMUM_SAMPLE_MS)) / LATENCY_BUCKET_LOG_BASE);\n return Math.min(Math.max(index, 0), cap - 1);\n}\n\nfunction bucketUpperBound(index: number): number {\n return LATENCY_BUCKET_GROWTH ** index;\n}\n\nfunction observe(state: SampleState, value: number, cap: number): void {\n const bucket = bucketIndex(value, cap);\n state.buckets.set(bucket, (state.buckets.get(bucket) ?? 0) + 1);\n state.samples += 1;\n}\n\n/**\n * The upper edge of the first bucket whose cumulative count crosses the\n * quantile. Reporting the edge rather than a bucket midpoint keeps the answer\n * an upper bound, so a single 250ms call never reports faster than 250ms.\n */\nfunction bucketedQuantile(state: SampleState, quantile: number): number | undefined {\n if (state.samples === 0) return undefined;\n\n const target = state.samples * quantile;\n const indexes = [...state.buckets.keys()].sort((left, right) => left - right);\n let cumulative = 0;\n let selected = indexes[0] as number;\n for (const index of indexes) {\n cumulative += state.buckets.get(index) ?? 0;\n selected = index;\n if (cumulative >= target) break;\n }\n return Math.round(bucketUpperBound(selected));\n}\n\nfunction bucketedP95(state: SampleState): number | undefined {\n return bucketedQuantile(state, P95_QUANTILE);\n}\n\n/** Categorises an HTTP failure so the action it implies is distinguishable. */\nfunction apiErrorCategory(status: number): string {\n if (status === RATE_LIMIT_STATUS) return CATEGORY_RATE_LIMITED;\n if (status >= SERVER_ERROR_STATUS) return CATEGORY_PROVIDER_UNAVAILABLE;\n return CATEGORY_API_ERROR;\n}\n\n/** Strips the `pi.` prefix so an error entry reads as the event it came from. */\nfunction errorEventName(recordName: string): string {\n return recordName.startsWith(RECORD_PREFIX) ? recordName.slice(RECORD_PREFIX.length) : recordName;\n}\n\nexport class LogMetricsAggregator implements LogMetricsRecorder {\n private events = 0;\n private errors = 0;\n private toolCalls = 0;\n private failedToolCalls = 0;\n private turns = 0;\n private abortedTurns = 0;\n private failedTurns = 0;\n private cost = 0;\n private readonly tokens: LogMetricsTokenTotals = { input: 0, output: 0, total: 0, cacheRead: 0, cacheWrite: 0 };\n private readonly eventCounts = new Map<string, number>();\n private readonly packageCounts = new Map<string, number>();\n /** Shared admission registry keeps the union of every tool-related map within one bound. */\n private readonly toolNames = new Map<string, true>();\n private readonly tools = new Map<string, ToolLatencyState>();\n private readonly toolTokens = new Map<string, SampleState>();\n private readonly toolFailures = new Map<string, number>();\n private readonly failureCategories = new Map<string, number>();\n private readonly operations = new Map<string, ToolLatencyState>();\n private sanitizedFailures = 0;\n private readonly errorLog: LogMetricsError[] = [];\n private readonly now: () => number;\n private readonly maxRecentErrors: number;\n private readonly maxMetricNames: number;\n\n constructor(options: LogMetricsAggregatorOptions = {}) {\n this.now = options.now ?? Date.now;\n this.maxRecentErrors = options.maxRecentErrors ?? DEFAULT_MAX_RECENT_ERRORS;\n const requestedMaxNames = options.maxMetricNames ?? MAX_RETAINED_METRIC_NAMES;\n this.maxMetricNames = Number.isFinite(requestedMaxNames)\n ? Math.min(MAX_RETAINED_METRIC_NAMES, Math.max(1, Math.floor(requestedMaxNames)))\n : MAX_RETAINED_METRIC_NAMES;\n }\n\n record(name: string, attributes: Record<string, unknown>): void {\n const metricName = safeMetricName(name);\n this.events += 1;\n incrementBounded(this.eventCounts, metricName, this.maxMetricNames);\n const packageName = readString(attributes['telemetry.package']);\n if (packageName && !packageName.startsWith('/') && !packageName.includes('\\\\')) {\n incrementBounded(this.packageCounts, safeMetricName(packageName), this.maxMetricNames);\n }\n this.recordOperationDuration(metricName, attributes);\n this.recordSanitizedFailure(metricName, attributes);\n\n if (metricName === TOOL_RESULT_RECORD) this.recordToolResult(metricName, attributes);\n else if (metricName === API_ERROR_RECORD) this.recordApiError(metricName, attributes);\n else if (metricName === TURN_FINISHED_RECORD) this.recordTurnUsage(attributes);\n else if (metricName === TURN_FAILED_RECORD) this.recordTurnFailure(attributes);\n else if (metricName === TOOL_TOKEN_SAMPLE_RECORD) this.recordToolTokenSample(attributes);\n }\n\n snapshot(): LogMetricsSnapshot {\n const toolLatency = [...this.tools.entries()]\n .map(([name, state]) => {\n const p95Ms = bucketedP95(state);\n return {\n name,\n calls: state.calls,\n retainedStateSize: state.buckets.size,\n ...(p95Ms === undefined ? {} : { p95Ms }),\n };\n })\n .sort((left, right) => (right.p95Ms ?? -1) - (left.p95Ms ?? -1));\n\n const eventVolume = [...this.eventCounts.entries()]\n .map(([name, count]) => ({ name, count }))\n .sort((left, right) => right.count - left.count);\n const packageEvents = [...this.packageCounts.entries()]\n .map(([name, count]) => ({ name, count }))\n .sort((left, right) => right.count - left.count);\n const operationDuration = [...this.operations.entries()]\n .map(([name, state]) => {\n const p95Ms = bucketedP95(state);\n return {\n name,\n calls: state.calls,\n retainedStateSize: state.buckets.size,\n ...(p95Ms === undefined ? {} : { p95Ms }),\n };\n })\n .sort((left, right) => (right.p95Ms ?? -1) - (left.p95Ms ?? -1));\n\n return {\n events: this.events,\n errors: this.errors,\n toolCalls: this.toolCalls,\n failedToolCalls: this.failedToolCalls,\n turns: this.turns,\n abortedTurns: this.abortedTurns,\n failedTurns: this.failedTurns,\n tokens: { ...this.tokens },\n cost: this.cost,\n toolLatency,\n toolCost: this.toolCostRows(),\n failureCategories: [...this.failureCategories.entries()]\n .map(([name, count]) => ({ name, count }))\n .sort((left, right) => right.count - left.count),\n eventVolume,\n packageEvents,\n sanitizedFailures: this.sanitizedFailures,\n operationDuration,\n recentErrors: this.errorLog.map((entry) => ({ ...entry })),\n };\n }\n\n /**\n * Ranked by cost per call rather than call count: a tool invoked twice into a\n * 300k-token turn is the one worth changing, and a frequency ranking buries\n * it under the cheap tools that dominate any transcript.\n */\n private toolCostRows(): LogMetricsToolCost[] {\n const names = new Set([...this.tools.keys(), ...this.toolTokens.keys()]);\n return [...names]\n .map((name) => {\n const tokens = this.toolTokens.get(name);\n const p90Tokens = tokens ? bucketedQuantile(tokens, P90_QUANTILE) : undefined;\n return {\n name,\n calls: this.tools.get(name)?.calls ?? 0,\n failed: this.toolFailures.get(name) ?? 0,\n samples: tokens?.samples ?? 0,\n ...(p90Tokens === undefined ? {} : { p90Tokens }),\n };\n })\n .sort((left, right) => (right.p90Tokens ?? -1) - (left.p90Tokens ?? -1));\n }\n\n private boundedToolName(name: string): string {\n const boundedName = boundedMetricName(this.toolNames, name, this.maxMetricNames);\n this.toolNames.set(boundedName, true);\n return boundedName;\n }\n\n private countCategory(category: string): void {\n incrementBounded(this.failureCategories, category, this.maxMetricNames);\n }\n\n private recordOperationDuration(recordName: string, attributes: Record<string, unknown>): void {\n const duration = Object.entries(attributes).find(\n ([key, value]) =>\n key !== 'tool.duration_ms' &&\n (key === 'duration_ms' || key.endsWith('.duration_ms')) &&\n readNumber(value) !== undefined,\n );\n if (!duration) return;\n const operationName = safeMetricName(readString(attributes['operation.name']) ?? recordName);\n const state = boundedState(this.operations, operationName, this.maxMetricNames, () => ({\n calls: 0,\n samples: 0,\n buckets: new Map<number, number>(),\n }));\n state.calls += 1;\n const durationMs = readNumber(duration[1]);\n if (durationMs === undefined) return;\n observe(state, durationMs, MAX_RETAINED_STATE_PER_TOOL);\n }\n\n private recordSanitizedFailure(recordName: string, attributes: Record<string, unknown>): void {\n const outcome = readString(attributes.outcome);\n const failed =\n attributes['tool.result.error'] === true ||\n recordName === API_ERROR_RECORD ||\n outcome === 'error' ||\n outcome === 'failed' ||\n outcome === 'failure';\n if (!failed) return;\n this.sanitizedFailures += 1;\n if (recordName === TOOL_RESULT_RECORD || recordName === API_ERROR_RECORD) return;\n // Turn failures carry their own category, applied by recordTurnFailure.\n if (recordName !== TURN_FAILED_RECORD) this.countCategory(CATEGORY_LOG_ERROR);\n const code = readString(attributes['error.code']) ?? outcome ?? 'error';\n this.pushError(recordName, `${readString(attributes['error.type']) ?? 'operation'} failed`, safeMetricName(code));\n }\n\n private recordToolResult(recordName: string, attributes: Record<string, unknown>): void {\n const toolName = readString(attributes['tool.name']) ?? UNKNOWN_TOOL;\n const boundedToolName = this.boundedToolName(toolName);\n const state = boundedState(this.tools, boundedToolName, this.maxMetricNames, () => ({\n calls: 0,\n samples: 0,\n buckets: new Map<number, number>(),\n }));\n state.calls += 1;\n this.toolCalls += 1;\n\n // A call whose start was never seen carries no duration, so it counts as a\n // call but contributes no latency sample.\n const durationMs = readNumber(attributes['tool.duration_ms']);\n if (durationMs !== undefined) observe(state, durationMs, MAX_RETAINED_STATE_PER_TOOL);\n\n if (attributes['tool.result.error'] === true) {\n this.failedToolCalls += 1;\n incrementBounded(this.toolFailures, boundedToolName, this.maxMetricNames);\n this.countCategory(CATEGORY_TOOL_FAILURE);\n // Redaction is on by default, so the tool's own output is unavailable and\n // the tool name is the only detail this entry can carry.\n this.pushError(recordName, `${boundedToolName}: tool call failed`, TOOL_ERROR_CODE);\n }\n }\n\n /**\n * Attributes a turn's token total to every tool that ran in it. The Pi\n * extension emits one sample per tool result carrying the whole turn's usage,\n * so this builds a per-tool distribution of \"how expensive is a turn that\n * uses this tool\" — see LogMetricsToolCost for what that does and does not\n * claim.\n */\n private recordToolTokenSample(attributes: Record<string, unknown>): void {\n const totalTokens = readNumber(attributes['gen_ai.usage.total_tokens']);\n if (totalTokens === undefined || totalTokens <= 0) return;\n const toolName = readString(attributes['tool.name']) ?? UNKNOWN_TOOL;\n const boundedToolName = this.boundedToolName(toolName);\n const state = boundedState(this.toolTokens, boundedToolName, this.maxMetricNames, emptySampleState);\n observe(state, totalTokens, MAX_RETAINED_TOKEN_BUCKETS);\n }\n\n private recordApiError(recordName: string, attributes: Record<string, unknown>): void {\n this.errors += 1;\n const statusCode = readNumber(attributes['http.response.status_code']);\n const status = String(attributes['http.response.status_code']);\n this.countCategory(statusCode === undefined ? CATEGORY_API_ERROR : apiErrorCategory(statusCode));\n this.pushError(recordName, `provider responded ${status}`, status);\n }\n\n private recordTurnUsage(attributes: Record<string, unknown>): void {\n this.turns += 1;\n this.tokens.input += readNumber(attributes['gen_ai.usage.input_tokens']) ?? 0;\n this.tokens.output += readNumber(attributes['gen_ai.usage.output_tokens']) ?? 0;\n this.tokens.total += readNumber(attributes['gen_ai.usage.total_tokens']) ?? 0;\n this.tokens.cacheRead += readNumber(attributes['gen_ai.usage.cache_read_tokens']) ?? 0;\n this.tokens.cacheWrite += readNumber(attributes['gen_ai.usage.cache_write_tokens']) ?? 0;\n this.cost += readNumber(attributes['gen_ai.usage.cost']) ?? 0;\n }\n\n /** A turn the user has to redo: the most expensive failure mode there is. */\n private recordTurnFailure(attributes: Record<string, unknown>): void {\n const aborted = readString(attributes.outcome) === ABORTED_OUTCOME;\n if (aborted) this.abortedTurns += 1;\n else this.failedTurns += 1;\n this.countCategory(aborted ? CATEGORY_ABORTED : CATEGORY_TURN_FAILED);\n }\n\n private pushError(recordName: string, message: string, code: string): void {\n this.errorLog.unshift({ at: this.now(), event: errorEventName(recordName), message, code });\n if (this.errorLog.length > this.maxRecentErrors) this.errorLog.length = this.maxRecentErrors;\n }\n}\n"],"mappings":"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"}
|
package/dist/metrics.cjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_metrics = require("./metrics-jCZe13Xg.cjs");
|
|
3
|
+
exports.API_ERROR_RECORD = require_metrics.API_ERROR_RECORD;
|
|
4
|
+
exports.LogMetricsAggregator = require_metrics.LogMetricsAggregator;
|
|
5
|
+
exports.MAX_RETAINED_METRIC_NAMES = require_metrics.MAX_RETAINED_METRIC_NAMES;
|
|
6
|
+
exports.MAX_RETAINED_STATE_PER_TOOL = require_metrics.MAX_RETAINED_STATE_PER_TOOL;
|
|
7
|
+
exports.MAX_RETAINED_TOKEN_BUCKETS = require_metrics.MAX_RETAINED_TOKEN_BUCKETS;
|
|
8
|
+
exports.METRIC_OVERFLOW_NAME = require_metrics.METRIC_OVERFLOW_NAME;
|
|
9
|
+
exports.TOOL_RESULT_RECORD = require_metrics.TOOL_RESULT_RECORD;
|
|
10
|
+
exports.TOOL_TOKEN_SAMPLE_RECORD = require_metrics.TOOL_TOKEN_SAMPLE_RECORD;
|
|
11
|
+
exports.TURN_FAILED_RECORD = require_metrics.TURN_FAILED_RECORD;
|
|
12
|
+
exports.TURN_FINISHED_RECORD = require_metrics.TURN_FINISHED_RECORD;
|
|
13
|
+
exports.deriveFindings = require_metrics.deriveFindings;
|