@agimon-ai/doompi-log 0.0.1-alpha.39 → 0.0.1-alpha.40

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/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/metrics.cjs"),t=require("./services/findings.cjs"),n=require("./tui/logMetricsOverlay.cjs"),r=require("./adapters/pi/extension.cjs");exports.API_ERROR_RECORD=e.API_ERROR_RECORD,exports.LogMetricsAggregator=e.LogMetricsAggregator,exports.LogMetricsOverlayComponent=n.LogMetricsOverlayComponent,exports.MAX_RETAINED_STATE_PER_TOOL=e.MAX_RETAINED_STATE_PER_TOOL,exports.MAX_RETAINED_TOKEN_BUCKETS=e.MAX_RETAINED_TOKEN_BUCKETS,exports.TOOL_RESULT_RECORD=e.TOOL_RESULT_RECORD,exports.TOOL_TOKEN_SAMPLE_RECORD=e.TOOL_TOKEN_SAMPLE_RECORD,exports.TURN_FAILED_RECORD=e.TURN_FAILED_RECORD,exports.TURN_FINISHED_RECORD=e.TURN_FINISHED_RECORD,exports.deriveFindings=t.deriveFindings,exports.installDoomLogRuntime=r.installDoomLogRuntime,exports.openLogMetricsOverlay=r.openLogMetricsOverlay,exports.registerLogMetricsLeaderBinding=r.registerLogMetricsLeaderBinding;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/metrics.cjs"),t=require("./services/findings.cjs"),n=require("./tui/logMetricsOverlay.cjs"),r=require("./adapters/pi/extension.cjs");exports.API_ERROR_RECORD=e.API_ERROR_RECORD,exports.LogMetricsAggregator=e.LogMetricsAggregator,exports.LogMetricsOverlayComponent=n.LogMetricsOverlayComponent,exports.MAX_RETAINED_METRIC_NAMES=e.MAX_RETAINED_METRIC_NAMES,exports.MAX_RETAINED_STATE_PER_TOOL=e.MAX_RETAINED_STATE_PER_TOOL,exports.MAX_RETAINED_TOKEN_BUCKETS=e.MAX_RETAINED_TOKEN_BUCKETS,exports.METRIC_OVERFLOW_NAME=e.METRIC_OVERFLOW_NAME,exports.TOOL_RESULT_RECORD=e.TOOL_RESULT_RECORD,exports.TOOL_TOKEN_SAMPLE_RECORD=e.TOOL_TOKEN_SAMPLE_RECORD,exports.TURN_FAILED_RECORD=e.TURN_FAILED_RECORD,exports.TURN_FINISHED_RECORD=e.TURN_FINISHED_RECORD,exports.deriveFindings=t.deriveFindings,exports.installDoomLogRuntime=r.installDoomLogRuntime,exports.openLogMetricsOverlay=r.openLogMetricsOverlay,exports.registerLogMetricsLeaderBinding=r.registerLogMetricsLeaderBinding;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD } from "./services/metrics.cjs";
1
+ import { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD } from "./services/metrics.cjs";
2
2
  import { LogMetricsOverlayComponent, LogMetricsView, SinkStatus } from "./tui/logMetricsOverlay.cjs";
3
3
  import { PiTelemetryExtensionOptions, installDoomLogRuntime, openLogMetricsOverlay, registerLogMetricsLeaderBinding } from "./adapters/pi/extension.cjs";
4
4
  import { LogMetricsFinding, LogMetricsFindingSeverity, deriveFindings } from "./services/findings.cjs";
5
- export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, LogMetricsOverlayComponent, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, type LogMetricsView, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, type PiTelemetryExtensionOptions, type SinkStatus, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings, installDoomLogRuntime, openLogMetricsOverlay, registerLogMetricsLeaderBinding };
5
+ export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, LogMetricsOverlayComponent, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, type LogMetricsView, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, type PiTelemetryExtensionOptions, type SinkStatus, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings, installDoomLogRuntime, openLogMetricsOverlay, registerLogMetricsLeaderBinding };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD } from "./services/metrics.mjs";
1
+ import { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD } from "./services/metrics.mjs";
2
2
  import { LogMetricsOverlayComponent, LogMetricsView, SinkStatus } from "./tui/logMetricsOverlay.mjs";
3
3
  import { PiTelemetryExtensionOptions, installDoomLogRuntime, openLogMetricsOverlay, registerLogMetricsLeaderBinding } from "./adapters/pi/extension.mjs";
4
4
  import { LogMetricsFinding, LogMetricsFindingSeverity, deriveFindings } from "./services/findings.mjs";
5
- export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, LogMetricsOverlayComponent, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, type LogMetricsView, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, type PiTelemetryExtensionOptions, type SinkStatus, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings, installDoomLogRuntime, openLogMetricsOverlay, registerLogMetricsLeaderBinding };
5
+ export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, LogMetricsOverlayComponent, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, type LogMetricsView, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, type PiTelemetryExtensionOptions, type SinkStatus, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings, installDoomLogRuntime, openLogMetricsOverlay, registerLogMetricsLeaderBinding };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{API_ERROR_RECORD as e,LogMetricsAggregator as t,MAX_RETAINED_STATE_PER_TOOL as n,MAX_RETAINED_TOKEN_BUCKETS as r,TOOL_RESULT_RECORD as i,TOOL_TOKEN_SAMPLE_RECORD as a,TURN_FAILED_RECORD as o,TURN_FINISHED_RECORD as s}from"./services/metrics.mjs";import{deriveFindings as c}from"./services/findings.mjs";import{LogMetricsOverlayComponent as l}from"./tui/logMetricsOverlay.mjs";import{installDoomLogRuntime as u,openLogMetricsOverlay as d,registerLogMetricsLeaderBinding as f}from"./adapters/pi/extension.mjs";export{e as API_ERROR_RECORD,t as LogMetricsAggregator,l as LogMetricsOverlayComponent,n as MAX_RETAINED_STATE_PER_TOOL,r as MAX_RETAINED_TOKEN_BUCKETS,i as TOOL_RESULT_RECORD,a as TOOL_TOKEN_SAMPLE_RECORD,o as TURN_FAILED_RECORD,s as TURN_FINISHED_RECORD,c as deriveFindings,u as installDoomLogRuntime,d as openLogMetricsOverlay,f as registerLogMetricsLeaderBinding};
1
+ import{API_ERROR_RECORD as e,LogMetricsAggregator as t,MAX_RETAINED_METRIC_NAMES as n,MAX_RETAINED_STATE_PER_TOOL as r,MAX_RETAINED_TOKEN_BUCKETS as i,METRIC_OVERFLOW_NAME as a,TOOL_RESULT_RECORD as o,TOOL_TOKEN_SAMPLE_RECORD as s,TURN_FAILED_RECORD as c,TURN_FINISHED_RECORD as l}from"./services/metrics.mjs";import{deriveFindings as u}from"./services/findings.mjs";import{LogMetricsOverlayComponent as d}from"./tui/logMetricsOverlay.mjs";import{installDoomLogRuntime as f,openLogMetricsOverlay as p,registerLogMetricsLeaderBinding as m}from"./adapters/pi/extension.mjs";export{e as API_ERROR_RECORD,t as LogMetricsAggregator,d as LogMetricsOverlayComponent,n as MAX_RETAINED_METRIC_NAMES,r as MAX_RETAINED_STATE_PER_TOOL,i as MAX_RETAINED_TOKEN_BUCKETS,a as METRIC_OVERFLOW_NAME,o as TOOL_RESULT_RECORD,s as TOOL_TOKEN_SAMPLE_RECORD,c as TURN_FAILED_RECORD,l as TURN_FINISHED_RECORD,u as deriveFindings,f as installDoomLogRuntime,p as openLogMetricsOverlay,m as registerLogMetricsLeaderBinding};
package/dist/metrics.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/metrics.cjs"),t=require("./services/findings.cjs");exports.API_ERROR_RECORD=e.API_ERROR_RECORD,exports.LogMetricsAggregator=e.LogMetricsAggregator,exports.MAX_RETAINED_STATE_PER_TOOL=e.MAX_RETAINED_STATE_PER_TOOL,exports.MAX_RETAINED_TOKEN_BUCKETS=e.MAX_RETAINED_TOKEN_BUCKETS,exports.TOOL_RESULT_RECORD=e.TOOL_RESULT_RECORD,exports.TOOL_TOKEN_SAMPLE_RECORD=e.TOOL_TOKEN_SAMPLE_RECORD,exports.TURN_FAILED_RECORD=e.TURN_FAILED_RECORD,exports.TURN_FINISHED_RECORD=e.TURN_FINISHED_RECORD,exports.deriveFindings=t.deriveFindings;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/metrics.cjs"),t=require("./services/findings.cjs");exports.API_ERROR_RECORD=e.API_ERROR_RECORD,exports.LogMetricsAggregator=e.LogMetricsAggregator,exports.MAX_RETAINED_METRIC_NAMES=e.MAX_RETAINED_METRIC_NAMES,exports.MAX_RETAINED_STATE_PER_TOOL=e.MAX_RETAINED_STATE_PER_TOOL,exports.MAX_RETAINED_TOKEN_BUCKETS=e.MAX_RETAINED_TOKEN_BUCKETS,exports.METRIC_OVERFLOW_NAME=e.METRIC_OVERFLOW_NAME,exports.TOOL_RESULT_RECORD=e.TOOL_RESULT_RECORD,exports.TOOL_TOKEN_SAMPLE_RECORD=e.TOOL_TOKEN_SAMPLE_RECORD,exports.TURN_FAILED_RECORD=e.TURN_FAILED_RECORD,exports.TURN_FINISHED_RECORD=e.TURN_FINISHED_RECORD,exports.deriveFindings=t.deriveFindings;
@@ -1,3 +1,3 @@
1
- import { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD } from "./services/metrics.cjs";
1
+ import { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD } from "./services/metrics.cjs";
2
2
  import { LogMetricsFinding, LogMetricsFindingSeverity, deriveFindings } from "./services/findings.cjs";
3
- export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings };
3
+ export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings };
@@ -1,3 +1,3 @@
1
- import { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD } from "./services/metrics.mjs";
1
+ import { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD } from "./services/metrics.mjs";
2
2
  import { LogMetricsFinding, LogMetricsFindingSeverity, deriveFindings } from "./services/findings.mjs";
3
- export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings };
3
+ export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings };
package/dist/metrics.mjs CHANGED
@@ -1 +1 @@
1
- import{API_ERROR_RECORD as e,LogMetricsAggregator as t,MAX_RETAINED_STATE_PER_TOOL as n,MAX_RETAINED_TOKEN_BUCKETS as r,TOOL_RESULT_RECORD as i,TOOL_TOKEN_SAMPLE_RECORD as a,TURN_FAILED_RECORD as o,TURN_FINISHED_RECORD as s}from"./services/metrics.mjs";import{deriveFindings as c}from"./services/findings.mjs";export{e as API_ERROR_RECORD,t as LogMetricsAggregator,n as MAX_RETAINED_STATE_PER_TOOL,r as MAX_RETAINED_TOKEN_BUCKETS,i as TOOL_RESULT_RECORD,a as TOOL_TOKEN_SAMPLE_RECORD,o as TURN_FAILED_RECORD,s as TURN_FINISHED_RECORD,c as deriveFindings};
1
+ import{API_ERROR_RECORD as e,LogMetricsAggregator as t,MAX_RETAINED_METRIC_NAMES as n,MAX_RETAINED_STATE_PER_TOOL as r,MAX_RETAINED_TOKEN_BUCKETS as i,METRIC_OVERFLOW_NAME as a,TOOL_RESULT_RECORD as o,TOOL_TOKEN_SAMPLE_RECORD as s,TURN_FAILED_RECORD as c,TURN_FINISHED_RECORD as l}from"./services/metrics.mjs";import{deriveFindings as u}from"./services/findings.mjs";export{e as API_ERROR_RECORD,t as LogMetricsAggregator,n as MAX_RETAINED_METRIC_NAMES,r as MAX_RETAINED_STATE_PER_TOOL,i as MAX_RETAINED_TOKEN_BUCKETS,a as METRIC_OVERFLOW_NAME,o as TOOL_RESULT_RECORD,s as TOOL_TOKEN_SAMPLE_RECORD,c as TURN_FAILED_RECORD,l as TURN_FINISHED_RECORD,u as deriveFindings};
@@ -1,2 +1,2 @@
1
- const e=`unknown`,t=`api_error`,n=1.1,r=Math.log(n);function i(){return{samples:0,buckets:new Map}}function a(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function o(e){return typeof e==`string`&&e.length>0?e:void 0}function s(e){return e.replace(/[^a-zA-Z0-9@/._:-]/g,`_`).slice(0,128)||`unknown`}function c(e,t){let n=Math.ceil(Math.log(Math.max(e,1))/r);return Math.min(Math.max(n,0),t-1)}function l(e){return n**e}function u(e,t,n){let r=c(t,n);e.buckets.set(r,(e.buckets.get(r)??0)+1),e.samples+=1}function d(e,t){if(e.samples===0)return;let n=e.samples*t,r=[...e.buckets.keys()].sort((e,t)=>e-t),i=0,a=r[0];for(let t of r)if(i+=e.buckets.get(t)??0,a=t,i>=n)break;return Math.round(l(a))}function f(e){return d(e,.95)}function p(e){return e===429?`rate_limited`:e>=500?`provider_unavailable`:t}function m(e){return e.startsWith(`pi.`)?e.slice(3):e}var h=class{events=0;errors=0;toolCalls=0;failedToolCalls=0;turns=0;abortedTurns=0;failedTurns=0;cost=0;tokens={input:0,output:0,total:0,cacheRead:0,cacheWrite:0};eventCounts=new Map;packageCounts=new Map;tools=new Map;toolTokens=new Map;toolFailures=new Map;failureCategories=new Map;operations=new Map;sanitizedFailures=0;errorLog=[];now;maxRecentErrors;constructor(e={}){this.now=e.now??Date.now,this.maxRecentErrors=e.maxRecentErrors??8}record(e,t){let n=s(e);this.events+=1,this.eventCounts.set(n,(this.eventCounts.get(n)??0)+1);let r=o(t[`telemetry.package`]);if(r&&!r.startsWith(`/`)&&!r.includes(`\\`)){let e=s(r);this.packageCounts.set(e,(this.packageCounts.get(e)??0)+1)}this.recordOperationDuration(n,t),this.recordSanitizedFailure(n,t),n===`pi.tool_result`?this.recordToolResult(n,t):n===`pi.api_error`?this.recordApiError(n,t):n===`pi.turn.finished`?this.recordTurnUsage(t):n===`pi.turn.failed`?this.recordTurnFailure(t):n===`pi.tool_token_sample`&&this.recordToolTokenSample(t)}snapshot(){let e=[...this.tools.entries()].map(([e,t])=>{let n=f(t);return{name:e,calls:t.calls,retainedStateSize:t.buckets.size,...n===void 0?{}:{p95Ms:n}}}).sort((e,t)=>(t.p95Ms??-1)-(e.p95Ms??-1)),t=[...this.eventCounts.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),n=[...this.packageCounts.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),r=[...this.operations.entries()].map(([e,t])=>{let n=f(t);return{name:e,calls:t.calls,retainedStateSize:t.buckets.size,...n===void 0?{}:{p95Ms:n}}}).sort((e,t)=>(t.p95Ms??-1)-(e.p95Ms??-1));return{events:this.events,errors:this.errors,toolCalls:this.toolCalls,failedToolCalls:this.failedToolCalls,turns:this.turns,abortedTurns:this.abortedTurns,failedTurns:this.failedTurns,tokens:{...this.tokens},cost:this.cost,toolLatency:e,toolCost:this.toolCostRows(),failureCategories:[...this.failureCategories.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),eventVolume:t,packageEvents:n,sanitizedFailures:this.sanitizedFailures,operationDuration:r,recentErrors:this.errorLog.map(e=>({...e}))}}toolCostRows(){return[...new Set([...this.tools.keys(),...this.toolTokens.keys()])].map(e=>{let t=this.toolTokens.get(e),n=t?d(t,.9):void 0;return{name:e,calls:this.tools.get(e)?.calls??0,failed:this.toolFailures.get(e)??0,samples:t?.samples??0,...n===void 0?{}:{p90Tokens:n}}}).sort((e,t)=>(t.p90Tokens??-1)-(e.p90Tokens??-1))}countCategory(e){this.failureCategories.set(e,(this.failureCategories.get(e)??0)+1)}recordOperationDuration(e,t){let n=Object.entries(t).find(([e,t])=>e!==`tool.duration_ms`&&(e===`duration_ms`||e.endsWith(`.duration_ms`))&&a(t)!==void 0);if(!n)return;let r=s(o(t[`operation.name`])??e),i=this.operations.get(r)??{calls:0,samples:0,buckets:new Map};this.operations.set(r,i),i.calls+=1;let c=a(n[1]);c!==void 0&&u(i,c,160)}recordSanitizedFailure(e,t){let n=o(t.outcome);if(t[`tool.result.error`]!==!0&&e!==`pi.api_error`&&n!==`error`&&n!==`failed`&&n!==`failure`||(this.sanitizedFailures+=1,e===`pi.tool_result`||e===`pi.api_error`))return;e!==`pi.turn.failed`&&this.countCategory(`log_error`);let r=o(t[`error.code`])??n??`error`;this.pushError(e,`${o(t[`error.type`])??`operation`} failed`,s(r))}recordToolResult(t,n){let r=o(n[`tool.name`])??e,i=this.tools.get(r)??{calls:0,samples:0,buckets:new Map};this.tools.set(r,i),i.calls+=1,this.toolCalls+=1;let s=a(n[`tool.duration_ms`]);s!==void 0&&u(i,s,160),n[`tool.result.error`]===!0&&(this.failedToolCalls+=1,this.toolFailures.set(r,(this.toolFailures.get(r)??0)+1),this.countCategory(`tool_failure`),this.pushError(t,`${r}: tool call failed`,`tool`))}recordToolTokenSample(t){let n=a(t[`gen_ai.usage.total_tokens`]);if(n===void 0||n<=0)return;let r=o(t[`tool.name`])??e,s=this.toolTokens.get(r)??i();this.toolTokens.set(r,s),u(s,n,200)}recordApiError(e,n){this.errors+=1;let r=a(n[`http.response.status_code`]),i=String(n[`http.response.status_code`]);this.countCategory(r===void 0?t:p(r)),this.pushError(e,`provider responded ${i}`,i)}recordTurnUsage(e){this.turns+=1,this.tokens.input+=a(e[`gen_ai.usage.input_tokens`])??0,this.tokens.output+=a(e[`gen_ai.usage.output_tokens`])??0,this.tokens.total+=a(e[`gen_ai.usage.total_tokens`])??0,this.tokens.cacheRead+=a(e[`gen_ai.usage.cache_read_tokens`])??0,this.tokens.cacheWrite+=a(e[`gen_ai.usage.cache_write_tokens`])??0,this.cost+=a(e[`gen_ai.usage.cost`])??0}recordTurnFailure(e){let t=o(e.outcome)===`aborted`;t?this.abortedTurns+=1:this.failedTurns+=1,this.countCategory(t?`aborted`:`turn_failed`)}pushError(e,t,n){this.errorLog.unshift({at:this.now(),event:m(e),message:t,code:n}),this.errorLog.length>this.maxRecentErrors&&(this.errorLog.length=this.maxRecentErrors)}};exports.API_ERROR_RECORD=`pi.api_error`,exports.LogMetricsAggregator=h,exports.MAX_RETAINED_STATE_PER_TOOL=160,exports.MAX_RETAINED_TOKEN_BUCKETS=200,exports.TOOL_RESULT_RECORD=`pi.tool_result`,exports.TOOL_TOKEN_SAMPLE_RECORD=`pi.tool_token_sample`,exports.TURN_FAILED_RECORD=`pi.turn.failed`,exports.TURN_FINISHED_RECORD=`pi.turn.finished`;
1
+ const e=`unknown`,t=`__overflow__`,n=`api_error`,r=1.1,i=Math.log(r);function a(){return{samples:0,buckets:new Map}}function o(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function s(e){return typeof e==`string`&&e.length>0?e:void 0}function c(e){return e.replace(/[^a-zA-Z0-9@/._:-]/g,`_`).slice(0,128)||`unknown`}function l(e,n,r){return e.has(n)||e.size<r-1?n:t}function u(e,t,n){let r=l(e,t,n);e.set(r,(e.get(r)??0)+1)}function d(e,t,n,r){let i=l(e,t,n),a=e.get(i)??r();return e.set(i,a),a}function f(e,t){let n=Math.ceil(Math.log(Math.max(e,1))/i);return Math.min(Math.max(n,0),t-1)}function p(e){return r**e}function m(e,t,n){let r=f(t,n);e.buckets.set(r,(e.buckets.get(r)??0)+1),e.samples+=1}function h(e,t){if(e.samples===0)return;let n=e.samples*t,r=[...e.buckets.keys()].sort((e,t)=>e-t),i=0,a=r[0];for(let t of r)if(i+=e.buckets.get(t)??0,a=t,i>=n)break;return Math.round(p(a))}function g(e){return h(e,.95)}function _(e){return e===429?`rate_limited`:e>=500?`provider_unavailable`:n}function v(e){return e.startsWith(`pi.`)?e.slice(3):e}var y=class{events=0;errors=0;toolCalls=0;failedToolCalls=0;turns=0;abortedTurns=0;failedTurns=0;cost=0;tokens={input:0,output:0,total:0,cacheRead:0,cacheWrite:0};eventCounts=new Map;packageCounts=new Map;toolNames=new Map;tools=new Map;toolTokens=new Map;toolFailures=new Map;failureCategories=new Map;operations=new Map;sanitizedFailures=0;errorLog=[];now;maxRecentErrors;maxMetricNames;constructor(e={}){this.now=e.now??Date.now,this.maxRecentErrors=e.maxRecentErrors??8;let t=e.maxMetricNames??128;this.maxMetricNames=Number.isFinite(t)?Math.min(128,Math.max(1,Math.floor(t))):128}record(e,t){let n=c(e);this.events+=1,u(this.eventCounts,n,this.maxMetricNames);let r=s(t[`telemetry.package`]);r&&!r.startsWith(`/`)&&!r.includes(`\\`)&&u(this.packageCounts,c(r),this.maxMetricNames),this.recordOperationDuration(n,t),this.recordSanitizedFailure(n,t),n===`pi.tool_result`?this.recordToolResult(n,t):n===`pi.api_error`?this.recordApiError(n,t):n===`pi.turn.finished`?this.recordTurnUsage(t):n===`pi.turn.failed`?this.recordTurnFailure(t):n===`pi.tool_token_sample`&&this.recordToolTokenSample(t)}snapshot(){let e=[...this.tools.entries()].map(([e,t])=>{let n=g(t);return{name:e,calls:t.calls,retainedStateSize:t.buckets.size,...n===void 0?{}:{p95Ms:n}}}).sort((e,t)=>(t.p95Ms??-1)-(e.p95Ms??-1)),t=[...this.eventCounts.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),n=[...this.packageCounts.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),r=[...this.operations.entries()].map(([e,t])=>{let n=g(t);return{name:e,calls:t.calls,retainedStateSize:t.buckets.size,...n===void 0?{}:{p95Ms:n}}}).sort((e,t)=>(t.p95Ms??-1)-(e.p95Ms??-1));return{events:this.events,errors:this.errors,toolCalls:this.toolCalls,failedToolCalls:this.failedToolCalls,turns:this.turns,abortedTurns:this.abortedTurns,failedTurns:this.failedTurns,tokens:{...this.tokens},cost:this.cost,toolLatency:e,toolCost:this.toolCostRows(),failureCategories:[...this.failureCategories.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),eventVolume:t,packageEvents:n,sanitizedFailures:this.sanitizedFailures,operationDuration:r,recentErrors:this.errorLog.map(e=>({...e}))}}toolCostRows(){return[...new Set([...this.tools.keys(),...this.toolTokens.keys()])].map(e=>{let t=this.toolTokens.get(e),n=t?h(t,.9):void 0;return{name:e,calls:this.tools.get(e)?.calls??0,failed:this.toolFailures.get(e)??0,samples:t?.samples??0,...n===void 0?{}:{p90Tokens:n}}}).sort((e,t)=>(t.p90Tokens??-1)-(e.p90Tokens??-1))}boundedToolName(e){let t=l(this.toolNames,e,this.maxMetricNames);return this.toolNames.set(t,!0),t}countCategory(e){u(this.failureCategories,e,this.maxMetricNames)}recordOperationDuration(e,t){let n=Object.entries(t).find(([e,t])=>e!==`tool.duration_ms`&&(e===`duration_ms`||e.endsWith(`.duration_ms`))&&o(t)!==void 0);if(!n)return;let r=c(s(t[`operation.name`])??e),i=d(this.operations,r,this.maxMetricNames,()=>({calls:0,samples:0,buckets:new Map}));i.calls+=1;let a=o(n[1]);a!==void 0&&m(i,a,160)}recordSanitizedFailure(e,t){let n=s(t.outcome);if(t[`tool.result.error`]!==!0&&e!==`pi.api_error`&&n!==`error`&&n!==`failed`&&n!==`failure`||(this.sanitizedFailures+=1,e===`pi.tool_result`||e===`pi.api_error`))return;e!==`pi.turn.failed`&&this.countCategory(`log_error`);let r=s(t[`error.code`])??n??`error`;this.pushError(e,`${s(t[`error.type`])??`operation`} failed`,c(r))}recordToolResult(t,n){let r=s(n[`tool.name`])??e,i=this.boundedToolName(r),a=d(this.tools,i,this.maxMetricNames,()=>({calls:0,samples:0,buckets:new Map}));a.calls+=1,this.toolCalls+=1;let c=o(n[`tool.duration_ms`]);c!==void 0&&m(a,c,160),n[`tool.result.error`]===!0&&(this.failedToolCalls+=1,u(this.toolFailures,i,this.maxMetricNames),this.countCategory(`tool_failure`),this.pushError(t,`${i}: tool call failed`,`tool`))}recordToolTokenSample(t){let n=o(t[`gen_ai.usage.total_tokens`]);if(n===void 0||n<=0)return;let r=s(t[`tool.name`])??e,i=this.boundedToolName(r);m(d(this.toolTokens,i,this.maxMetricNames,a),n,200)}recordApiError(e,t){this.errors+=1;let r=o(t[`http.response.status_code`]),i=String(t[`http.response.status_code`]);this.countCategory(r===void 0?n:_(r)),this.pushError(e,`provider responded ${i}`,i)}recordTurnUsage(e){this.turns+=1,this.tokens.input+=o(e[`gen_ai.usage.input_tokens`])??0,this.tokens.output+=o(e[`gen_ai.usage.output_tokens`])??0,this.tokens.total+=o(e[`gen_ai.usage.total_tokens`])??0,this.tokens.cacheRead+=o(e[`gen_ai.usage.cache_read_tokens`])??0,this.tokens.cacheWrite+=o(e[`gen_ai.usage.cache_write_tokens`])??0,this.cost+=o(e[`gen_ai.usage.cost`])??0}recordTurnFailure(e){let t=s(e.outcome)===`aborted`;t?this.abortedTurns+=1:this.failedTurns+=1,this.countCategory(t?`aborted`:`turn_failed`)}pushError(e,t,n){this.errorLog.unshift({at:this.now(),event:v(e),message:t,code:n}),this.errorLog.length>this.maxRecentErrors&&(this.errorLog.length=this.maxRecentErrors)}};exports.API_ERROR_RECORD=`pi.api_error`,exports.LogMetricsAggregator=y,exports.MAX_RETAINED_METRIC_NAMES=128,exports.MAX_RETAINED_STATE_PER_TOOL=160,exports.MAX_RETAINED_TOKEN_BUCKETS=200,exports.METRIC_OVERFLOW_NAME=t,exports.TOOL_RESULT_RECORD=`pi.tool_result`,exports.TOOL_TOKEN_SAMPLE_RECORD=`pi.tool_token_sample`,exports.TURN_FAILED_RECORD=`pi.turn.failed`,exports.TURN_FINISHED_RECORD=`pi.turn.finished`;
2
2
  //# sourceMappingURL=metrics.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.cjs","names":[],"sources":["../../src/services/metrics.ts"],"sourcesContent":["/**\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;\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}\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\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 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\n constructor(options: LogMetricsAggregatorOptions = {}) {\n this.now = options.now ?? Date.now;\n this.maxRecentErrors = options.maxRecentErrors ?? DEFAULT_MAX_RECENT_ERRORS;\n }\n\n record(name: string, attributes: Record<string, unknown>): void {\n const metricName = safeMetricName(name);\n this.events += 1;\n this.eventCounts.set(metricName, (this.eventCounts.get(metricName) ?? 0) + 1);\n const packageName = readString(attributes['telemetry.package']);\n if (packageName && !packageName.startsWith('/') && !packageName.includes('\\\\')) {\n const safePackageName = safeMetricName(packageName);\n this.packageCounts.set(safePackageName, (this.packageCounts.get(safePackageName) ?? 0) + 1);\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 countCategory(category: string): void {\n this.failureCategories.set(category, (this.failureCategories.get(category) ?? 0) + 1);\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 = this.operations.get(operationName) ?? { calls: 0, samples: 0, buckets: new Map<number, number>() };\n this.operations.set(operationName, state);\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 state = this.tools.get(toolName) ?? { calls: 0, samples: 0, buckets: new Map<number, number>() };\n this.tools.set(toolName, state);\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 this.toolFailures.set(toolName, (this.toolFailures.get(toolName) ?? 0) + 1);\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, `${toolName}: 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 state = this.toolTokens.get(toolName) ?? emptySampleState();\n this.toolTokens.set(toolName, state);\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":"AAWA,MAMM,EAAe,UAmBf,EAAqB,YAWrB,EAAwB,IACxB,EAA0B,KAAK,IAAI,CAAqB,EAiH9D,SAAS,GAAgC,CACvC,MAAO,CAAE,QAAS,EAAG,QAAS,IAAI,GAAsB,CAC1D,CAEA,SAAS,EAAW,EAAoC,CACtD,OAAO,OAAO,GAAU,UAAY,OAAO,SAAS,CAAK,EAAI,EAAQ,IAAA,EACvE,CAEA,SAAS,EAAW,EAAoC,CACtD,OAAO,OAAO,GAAU,UAAY,EAAM,OAAS,EAAI,EAAQ,IAAA,EACjE,CAEA,SAAS,EAAe,EAAuB,CAE7C,OADmB,EAAM,QAAQ,sBAAuB,GAAG,CAAC,CAAC,MAAM,EAAG,GACtD,GAAK,SACvB,CAEA,SAAS,EAAY,EAAe,EAAqB,CACvD,IAAM,EAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,EAAO,CAAiB,CAAC,EAAI,CAAuB,EAC9F,OAAO,KAAK,IAAI,KAAK,IAAI,EAAO,CAAC,EAAG,EAAM,CAAC,CAC7C,CAEA,SAAS,EAAiB,EAAuB,CAC/C,OAAO,GAAyB,CAClC,CAEA,SAAS,EAAQ,EAAoB,EAAe,EAAmB,CACrE,IAAM,EAAS,EAAY,EAAO,CAAG,EACrC,EAAM,QAAQ,IAAI,GAAS,EAAM,QAAQ,IAAI,CAAM,GAAK,GAAK,CAAC,EAC9D,EAAM,SAAW,CACnB,CAOA,SAAS,EAAiB,EAAoB,EAAsC,CAClF,GAAI,EAAM,UAAY,EAAG,OAEzB,IAAM,EAAS,EAAM,QAAU,EACzB,EAAU,CAAC,GAAG,EAAM,QAAQ,KAAK,CAAC,CAAC,CAAC,MAAM,EAAM,IAAU,EAAO,CAAK,EACxE,EAAa,EACb,EAAW,EAAQ,GACvB,IAAK,IAAM,KAAS,EAGlB,GAFA,GAAc,EAAM,QAAQ,IAAI,CAAK,GAAK,EAC1C,EAAW,EACP,GAAc,EAAQ,MAE5B,OAAO,KAAK,MAAM,EAAiB,CAAQ,CAAC,CAC9C,CAEA,SAAS,EAAY,EAAwC,CAC3D,OAAO,EAAiB,EAAO,GAAY,CAC7C,CAGA,SAAS,EAAiB,EAAwB,CAGhD,OAFI,IAAW,IAA0B,eACrC,GAAU,IAA4B,uBACnC,CACT,CAGA,SAAS,EAAe,EAA4B,CAClD,OAAO,EAAW,WAAW,KAAa,EAAI,EAAW,MAAM,CAAoB,EAAI,CACzF,CAEA,IAAa,EAAb,KAAgE,CAC9D,OAAiB,EACjB,OAAiB,EACjB,UAAoB,EACpB,gBAA0B,EAC1B,MAAgB,EAChB,aAAuB,EACvB,YAAsB,EACtB,KAAe,EACf,OAAiD,CAAE,MAAO,EAAG,OAAQ,EAAG,MAAO,EAAG,UAAW,EAAG,WAAY,CAAE,EAC9G,YAA+B,IAAI,IACnC,cAAiC,IAAI,IACrC,MAAyB,IAAI,IAC7B,WAA8B,IAAI,IAClC,aAAgC,IAAI,IACpC,kBAAqC,IAAI,IACzC,WAA8B,IAAI,IAClC,kBAA4B,EAC5B,SAA+C,CAAC,EAChD,IACA,gBAEA,YAAY,EAAuC,CAAC,EAAG,CACrD,KAAK,IAAM,EAAQ,KAAO,KAAK,IAC/B,KAAK,gBAAkB,EAAQ,iBAAmB,CACpD,CAEA,OAAO,EAAc,EAA2C,CAC9D,IAAM,EAAa,EAAe,CAAI,EACtC,KAAK,QAAU,EACf,KAAK,YAAY,IAAI,GAAa,KAAK,YAAY,IAAI,CAAU,GAAK,GAAK,CAAC,EAC5E,IAAM,EAAc,EAAW,EAAW,oBAAoB,EAC9D,GAAI,GAAe,CAAC,EAAY,WAAW,GAAG,GAAK,CAAC,EAAY,SAAS,IAAI,EAAG,CAC9E,IAAM,EAAkB,EAAe,CAAW,EAClD,KAAK,cAAc,IAAI,GAAkB,KAAK,cAAc,IAAI,CAAe,GAAK,GAAK,CAAC,CAC5F,CACA,KAAK,wBAAwB,EAAY,CAAU,EACnD,KAAK,uBAAuB,EAAY,CAAU,EAE9C,IAAA,iBAAmC,KAAK,iBAAiB,EAAY,CAAU,EAC1E,IAAA,eAAiC,KAAK,eAAe,EAAY,CAAU,EAC3E,IAAA,mBAAqC,KAAK,gBAAgB,CAAU,EACpE,IAAA,iBAAmC,KAAK,kBAAkB,CAAU,EACpE,IAAA,wBAAyC,KAAK,sBAAsB,CAAU,CACzF,CAEA,UAA+B,CAC7B,IAAM,EAAc,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC,CAAC,CAC1C,KAAK,CAAC,EAAM,KAAW,CACtB,IAAM,EAAQ,EAAY,CAAK,EAC/B,MAAO,CACL,OACA,MAAO,EAAM,MACb,kBAAmB,EAAM,QAAQ,KACjC,GAAI,IAAU,IAAA,GAAY,CAAC,EAAI,CAAE,OAAM,CACzC,CACF,CAAC,CAAC,CACD,MAAM,EAAM,KAAW,EAAM,OAAS,KAAO,EAAK,OAAS,GAAG,EAE3D,EAAc,CAAC,GAAG,KAAK,YAAY,QAAQ,CAAC,CAAC,CAChD,KAAK,CAAC,EAAM,MAAY,CAAE,OAAM,OAAM,EAAE,CAAC,CACzC,MAAM,EAAM,IAAU,EAAM,MAAQ,EAAK,KAAK,EAC3C,EAAgB,CAAC,GAAG,KAAK,cAAc,QAAQ,CAAC,CAAC,CACpD,KAAK,CAAC,EAAM,MAAY,CAAE,OAAM,OAAM,EAAE,CAAC,CACzC,MAAM,EAAM,IAAU,EAAM,MAAQ,EAAK,KAAK,EAC3C,EAAoB,CAAC,GAAG,KAAK,WAAW,QAAQ,CAAC,CAAC,CACrD,KAAK,CAAC,EAAM,KAAW,CACtB,IAAM,EAAQ,EAAY,CAAK,EAC/B,MAAO,CACL,OACA,MAAO,EAAM,MACb,kBAAmB,EAAM,QAAQ,KACjC,GAAI,IAAU,IAAA,GAAY,CAAC,EAAI,CAAE,OAAM,CACzC,CACF,CAAC,CAAC,CACD,MAAM,EAAM,KAAW,EAAM,OAAS,KAAO,EAAK,OAAS,GAAG,EAEjE,MAAO,CACL,OAAQ,KAAK,OACb,OAAQ,KAAK,OACb,UAAW,KAAK,UAChB,gBAAiB,KAAK,gBACtB,MAAO,KAAK,MACZ,aAAc,KAAK,aACnB,YAAa,KAAK,YAClB,OAAQ,CAAE,GAAG,KAAK,MAAO,EACzB,KAAM,KAAK,KACX,cACA,SAAU,KAAK,aAAa,EAC5B,kBAAmB,CAAC,GAAG,KAAK,kBAAkB,QAAQ,CAAC,CAAC,CACrD,KAAK,CAAC,EAAM,MAAY,CAAE,OAAM,OAAM,EAAE,CAAC,CACzC,MAAM,EAAM,IAAU,EAAM,MAAQ,EAAK,KAAK,EACjD,cACA,gBACA,kBAAmB,KAAK,kBACxB,oBACA,aAAc,KAAK,SAAS,IAAK,IAAW,CAAE,GAAG,CAAM,EAAE,CAC3D,CACF,CAOA,cAA6C,CAE3C,MAAO,CAAC,GAAG,IADO,IAAI,CAAC,GAAG,KAAK,MAAM,KAAK,EAAG,GAAG,KAAK,WAAW,KAAK,CAAC,CACvD,CAAC,CAAC,CACd,IAAK,GAAS,CACb,IAAM,EAAS,KAAK,WAAW,IAAI,CAAI,EACjC,EAAY,EAAS,EAAiB,EAAQ,EAAY,EAAI,IAAA,GACpE,MAAO,CACL,OACA,MAAO,KAAK,MAAM,IAAI,CAAI,CAAC,EAAE,OAAS,EACtC,OAAQ,KAAK,aAAa,IAAI,CAAI,GAAK,EACvC,QAAS,GAAQ,SAAW,EAC5B,GAAI,IAAc,IAAA,GAAY,CAAC,EAAI,CAAE,WAAU,CACjD,CACF,CAAC,CAAC,CACD,MAAM,EAAM,KAAW,EAAM,WAAa,KAAO,EAAK,WAAa,GAAG,CAC3E,CAEA,cAAsB,EAAwB,CAC5C,KAAK,kBAAkB,IAAI,GAAW,KAAK,kBAAkB,IAAI,CAAQ,GAAK,GAAK,CAAC,CACtF,CAEA,wBAAgC,EAAoB,EAA2C,CAC7F,IAAM,EAAW,OAAO,QAAQ,CAAU,CAAC,CAAC,MACzC,CAAC,EAAK,KACL,IAAQ,qBACP,IAAQ,eAAiB,EAAI,SAAS,cAAc,IACrD,EAAW,CAAK,IAAM,IAAA,EAC1B,EACA,GAAI,CAAC,EAAU,OACf,IAAM,EAAgB,EAAe,EAAW,EAAW,iBAAiB,GAAK,CAAU,EACrF,EAAQ,KAAK,WAAW,IAAI,CAAa,GAAK,CAAE,MAAO,EAAG,QAAS,EAAG,QAAS,IAAI,GAAsB,EAC/G,KAAK,WAAW,IAAI,EAAe,CAAK,EACxC,EAAM,OAAS,EACf,IAAM,EAAa,EAAW,EAAS,EAAE,EACrC,IAAe,IAAA,IACnB,EAAQ,EAAO,EAAA,GAAuC,CACxD,CAEA,uBAA+B,EAAoB,EAA2C,CAC5F,IAAM,EAAU,EAAW,EAAW,OAAO,EAS7C,GAPE,EAAW,uBAAyB,IACpC,IAAA,gBACA,IAAY,SACZ,IAAY,UACZ,IAAY,YAEd,KAAK,mBAAqB,EACtB,IAAA,kBAAqC,IAAA,gBAAiC,OAEtE,IAAA,kBAAmC,KAAK,cAAc,WAAkB,EAC5E,IAAM,EAAO,EAAW,EAAW,aAAa,GAAK,GAAW,QAChE,KAAK,UAAU,EAAY,GAAG,EAAW,EAAW,aAAa,GAAK,YAAY,SAAU,EAAe,CAAI,CAAC,CAClH,CAEA,iBAAyB,EAAoB,EAA2C,CACtF,IAAM,EAAW,EAAW,EAAW,YAAY,GAAK,EAClD,EAAQ,KAAK,MAAM,IAAI,CAAQ,GAAK,CAAE,MAAO,EAAG,QAAS,EAAG,QAAS,IAAI,GAAsB,EACrG,KAAK,MAAM,IAAI,EAAU,CAAK,EAC9B,EAAM,OAAS,EACf,KAAK,WAAa,EAIlB,IAAM,EAAa,EAAW,EAAW,mBAAmB,EACxD,IAAe,IAAA,IAAW,EAAQ,EAAO,EAAA,GAAuC,EAEhF,EAAW,uBAAyB,KACtC,KAAK,iBAAmB,EACxB,KAAK,aAAa,IAAI,GAAW,KAAK,aAAa,IAAI,CAAQ,GAAK,GAAK,CAAC,EAC1E,KAAK,cAAc,cAAqB,EAGxC,KAAK,UAAU,EAAY,GAAG,EAAS,oBAAqB,MAAe,EAE/E,CASA,sBAA8B,EAA2C,CACvE,IAAM,EAAc,EAAW,EAAW,4BAA4B,EACtE,GAAI,IAAgB,IAAA,IAAa,GAAe,EAAG,OACnD,IAAM,EAAW,EAAW,EAAW,YAAY,GAAK,EAClD,EAAQ,KAAK,WAAW,IAAI,CAAQ,GAAK,EAAiB,EAChE,KAAK,WAAW,IAAI,EAAU,CAAK,EACnC,EAAQ,EAAO,EAAA,GAAuC,CACxD,CAEA,eAAuB,EAAoB,EAA2C,CACpF,KAAK,QAAU,EACf,IAAM,EAAa,EAAW,EAAW,4BAA4B,EAC/D,EAAS,OAAO,EAAW,4BAA4B,EAC7D,KAAK,cAAc,IAAe,IAAA,GAAY,EAAqB,EAAiB,CAAU,CAAC,EAC/F,KAAK,UAAU,EAAY,sBAAsB,IAAU,CAAM,CACnE,CAEA,gBAAwB,EAA2C,CACjE,KAAK,OAAS,EACd,KAAK,OAAO,OAAS,EAAW,EAAW,4BAA4B,GAAK,EAC5E,KAAK,OAAO,QAAU,EAAW,EAAW,6BAA6B,GAAK,EAC9E,KAAK,OAAO,OAAS,EAAW,EAAW,4BAA4B,GAAK,EAC5E,KAAK,OAAO,WAAa,EAAW,EAAW,iCAAiC,GAAK,EACrF,KAAK,OAAO,YAAc,EAAW,EAAW,kCAAkC,GAAK,EACvF,KAAK,MAAQ,EAAW,EAAW,oBAAoB,GAAK,CAC9D,CAGA,kBAA0B,EAA2C,CACnE,IAAM,EAAU,EAAW,EAAW,OAAO,IAAM,UAC/C,EAAS,KAAK,cAAgB,EAC7B,KAAK,aAAe,EACzB,KAAK,cAAc,EAAU,UAAmB,aAAoB,CACtE,CAEA,UAAkB,EAAoB,EAAiB,EAAoB,CACzE,KAAK,SAAS,QAAQ,CAAE,GAAI,KAAK,IAAI,EAAG,MAAO,EAAe,CAAU,EAAG,UAAS,MAAK,CAAC,EACtF,KAAK,SAAS,OAAS,KAAK,kBAAiB,KAAK,SAAS,OAAS,KAAK,gBAC/E,CACF"}
1
+ {"version":3,"file":"metrics.cjs","names":[],"sources":["../../src/services/metrics.ts"],"sourcesContent":["/**\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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"}
@@ -14,6 +14,10 @@ declare const API_ERROR_RECORD = "pi.api_error";
14
14
  declare const TURN_FINISHED_RECORD = "pi.turn.finished";
15
15
  declare const TURN_FAILED_RECORD = "pi.turn.failed";
16
16
  declare const TOOL_TOKEN_SAMPLE_RECORD = "pi.tool_token_sample";
17
+ /** Maximum entries retained in each cardinality-bearing metrics map, including overflow. */
18
+ declare const MAX_RETAINED_METRIC_NAMES = 128;
19
+ /** Snapshot row used to account for names that arrive after a metrics map reaches its bound. */
20
+ declare const METRIC_OVERFLOW_NAME = "__overflow__";
17
21
  /** Bucket ceiling per tool: covers 1ms to roughly an hour at 10% resolution. */
18
22
  declare const MAX_RETAINED_STATE_PER_TOOL = 160;
19
23
  /**
@@ -97,6 +101,8 @@ interface LogMetricsSnapshot {
97
101
  interface LogMetricsAggregatorOptions {
98
102
  now?: () => number;
99
103
  maxRecentErrors?: number;
104
+ /** Per-map cardinality bound for tests, capped at MAX_RETAINED_METRIC_NAMES. */
105
+ maxMetricNames?: number;
100
106
  }
101
107
  /** The slice of the aggregator the telemetry extension needs. */
102
108
  interface LogMetricsRecorder {
@@ -114,6 +120,8 @@ declare class LogMetricsAggregator implements LogMetricsRecorder {
114
120
  private readonly tokens;
115
121
  private readonly eventCounts;
116
122
  private readonly packageCounts;
123
+ /** Shared admission registry keeps the union of every tool-related map within one bound. */
124
+ private readonly toolNames;
117
125
  private readonly tools;
118
126
  private readonly toolTokens;
119
127
  private readonly toolFailures;
@@ -123,6 +131,7 @@ declare class LogMetricsAggregator implements LogMetricsRecorder {
123
131
  private readonly errorLog;
124
132
  private readonly now;
125
133
  private readonly maxRecentErrors;
134
+ private readonly maxMetricNames;
126
135
  constructor(options?: LogMetricsAggregatorOptions);
127
136
  record(name: string, attributes: Record<string, unknown>): void;
128
137
  snapshot(): LogMetricsSnapshot;
@@ -132,6 +141,7 @@ declare class LogMetricsAggregator implements LogMetricsRecorder {
132
141
  * it under the cheap tools that dominate any transcript.
133
142
  */
134
143
  private toolCostRows;
144
+ private boundedToolName;
135
145
  private countCategory;
136
146
  private recordOperationDuration;
137
147
  private recordSanitizedFailure;
@@ -151,5 +161,5 @@ declare class LogMetricsAggregator implements LogMetricsRecorder {
151
161
  private pushError;
152
162
  }
153
163
  //#endregion
154
- export { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD };
164
+ export { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD };
155
165
  //# sourceMappingURL=metrics.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.cts","names":[],"sources":["../../src/services/metrics.ts"],"mappings":";;;;;;;;;;;cAWa;cACA;cACA;cACA;cACA;;cAqCA;;;;;;cAOA;UAEI;EACf;EACA;EACA;;;;;;EAMA;EACA;;UAGe;EACf;EACA;EACA;;EAEA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;;;;;;;;;;UAYe;EACf;EACA;EACA;;EAEA;EACA;;UAGe;EACf;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA,QAAQ;EACR;EACA,aAAa;EACb,UAAU;EACV,mBAAmB;EACnB,aAAa;EACb,eAAe;EACf;EACA,mBAAmB;EACnB,cAAc;;UAGC;EACf;EACA;;;UAIe;EACf,OAAO,cAAc,YAAY;;cAkFtB,gCAAgC;UACnC;UACA;UACA;UACA;UACA;UACA;UACA;UACA;mBACS;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;mBACS;mBACA;mBACA;EAEjB,YAAY,UAAS;EAKrB,OAAO,cAAc,YAAY;EAmBjC,YAAY;;;;;;UA2DJ;UAiBA;UAIA;UAiBA;UAiBA;;;;;;;;UA6BA;UASA;UAQA;;UAWA;UAOA"}
1
+ {"version":3,"file":"metrics.d.cts","names":[],"sources":["../../src/services/metrics.ts"],"mappings":";;;;;;;;;;;cAWa;cACA;cACA;cACA;cACA;;cAOA;;cAEA;;cAgCA;;;;;;cAOA;UAEI;EACf;EACA;EACA;;;;;;EAMA;EACA;;UAGe;EACf;EACA;EACA;;EAEA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;;;;;;;;;;UAYe;EACf;EACA;EACA;;EAEA;EACA;;UAGe;EACf;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA,QAAQ;EACR;EACA,aAAa;EACb,UAAU;EACV,mBAAmB;EACnB,aAAa;EACb,eAAe;EACf;EACA,mBAAmB;EACnB,cAAc;;UAGC;EACf;EACA;;EAEA;;;UAIe;EACf,OAAO,cAAc,YAAY;;cAoGtB,gCAAgC;UACnC;UACA;UACA;UACA;UACA;UACA;UACA;UACA;mBACS;mBACA;mBACA;;mBAEA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;mBACS;mBACA;mBACA;mBACA;EAEjB,YAAY,UAAS;EASrB,OAAO,cAAc,YAAY;EAkBjC,YAAY;;;;;;UA2DJ;UAiBA;UAMA;UAIA;UAoBA;UAiBA;;;;;;;;UAiCA;UASA;UAQA;;UAWA;UAOA"}
@@ -14,6 +14,10 @@ declare const API_ERROR_RECORD = "pi.api_error";
14
14
  declare const TURN_FINISHED_RECORD = "pi.turn.finished";
15
15
  declare const TURN_FAILED_RECORD = "pi.turn.failed";
16
16
  declare const TOOL_TOKEN_SAMPLE_RECORD = "pi.tool_token_sample";
17
+ /** Maximum entries retained in each cardinality-bearing metrics map, including overflow. */
18
+ declare const MAX_RETAINED_METRIC_NAMES = 128;
19
+ /** Snapshot row used to account for names that arrive after a metrics map reaches its bound. */
20
+ declare const METRIC_OVERFLOW_NAME = "__overflow__";
17
21
  /** Bucket ceiling per tool: covers 1ms to roughly an hour at 10% resolution. */
18
22
  declare const MAX_RETAINED_STATE_PER_TOOL = 160;
19
23
  /**
@@ -97,6 +101,8 @@ interface LogMetricsSnapshot {
97
101
  interface LogMetricsAggregatorOptions {
98
102
  now?: () => number;
99
103
  maxRecentErrors?: number;
104
+ /** Per-map cardinality bound for tests, capped at MAX_RETAINED_METRIC_NAMES. */
105
+ maxMetricNames?: number;
100
106
  }
101
107
  /** The slice of the aggregator the telemetry extension needs. */
102
108
  interface LogMetricsRecorder {
@@ -114,6 +120,8 @@ declare class LogMetricsAggregator implements LogMetricsRecorder {
114
120
  private readonly tokens;
115
121
  private readonly eventCounts;
116
122
  private readonly packageCounts;
123
+ /** Shared admission registry keeps the union of every tool-related map within one bound. */
124
+ private readonly toolNames;
117
125
  private readonly tools;
118
126
  private readonly toolTokens;
119
127
  private readonly toolFailures;
@@ -123,6 +131,7 @@ declare class LogMetricsAggregator implements LogMetricsRecorder {
123
131
  private readonly errorLog;
124
132
  private readonly now;
125
133
  private readonly maxRecentErrors;
134
+ private readonly maxMetricNames;
126
135
  constructor(options?: LogMetricsAggregatorOptions);
127
136
  record(name: string, attributes: Record<string, unknown>): void;
128
137
  snapshot(): LogMetricsSnapshot;
@@ -132,6 +141,7 @@ declare class LogMetricsAggregator implements LogMetricsRecorder {
132
141
  * it under the cheap tools that dominate any transcript.
133
142
  */
134
143
  private toolCostRows;
144
+ private boundedToolName;
135
145
  private countCategory;
136
146
  private recordOperationDuration;
137
147
  private recordSanitizedFailure;
@@ -151,5 +161,5 @@ declare class LogMetricsAggregator implements LogMetricsRecorder {
151
161
  private pushError;
152
162
  }
153
163
  //#endregion
154
- export { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD };
164
+ export { API_ERROR_RECORD, LogMetricsAggregator, LogMetricsAggregatorOptions, LogMetricsError, LogMetricsEventCount, LogMetricsOperationDuration, LogMetricsRecorder, LogMetricsSnapshot, LogMetricsTokenTotals, LogMetricsToolCost, LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD };
155
165
  //# sourceMappingURL=metrics.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.mts","names":[],"sources":["../../src/services/metrics.ts"],"mappings":";;;;;;;;;;;cAWa;cACA;cACA;cACA;cACA;;cAqCA;;;;;;cAOA;UAEI;EACf;EACA;EACA;;;;;;EAMA;EACA;;UAGe;EACf;EACA;EACA;;EAEA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;;;;;;;;;;UAYe;EACf;EACA;EACA;;EAEA;EACA;;UAGe;EACf;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA,QAAQ;EACR;EACA,aAAa;EACb,UAAU;EACV,mBAAmB;EACnB,aAAa;EACb,eAAe;EACf;EACA,mBAAmB;EACnB,cAAc;;UAGC;EACf;EACA;;;UAIe;EACf,OAAO,cAAc,YAAY;;cAkFtB,gCAAgC;UACnC;UACA;UACA;UACA;UACA;UACA;UACA;UACA;mBACS;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;mBACS;mBACA;mBACA;EAEjB,YAAY,UAAS;EAKrB,OAAO,cAAc,YAAY;EAmBjC,YAAY;;;;;;UA2DJ;UAiBA;UAIA;UAiBA;UAiBA;;;;;;;;UA6BA;UASA;UAQA;;UAWA;UAOA"}
1
+ {"version":3,"file":"metrics.d.mts","names":[],"sources":["../../src/services/metrics.ts"],"mappings":";;;;;;;;;;;cAWa;cACA;cACA;cACA;cACA;;cAOA;;cAEA;;cAgCA;;;;;;cAOA;UAEI;EACf;EACA;EACA;;;;;;EAMA;EACA;;UAGe;EACf;EACA;EACA;;EAEA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;;;;;;;;;;UAYe;EACf;EACA;EACA;;EAEA;EACA;;UAGe;EACf;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA,QAAQ;EACR;EACA,aAAa;EACb,UAAU;EACV,mBAAmB;EACnB,aAAa;EACb,eAAe;EACf;EACA,mBAAmB;EACnB,cAAc;;UAGC;EACf;EACA;;EAEA;;;UAIe;EACf,OAAO,cAAc,YAAY;;cAoGtB,gCAAgC;UACnC;UACA;UACA;UACA;UACA;UACA;UACA;UACA;mBACS;mBACA;mBACA;;mBAEA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;mBACS;mBACA;mBACA;mBACA;EAEjB,YAAY,UAAS;EASrB,OAAO,cAAc,YAAY;EAkBjC,YAAY;;;;;;UA2DJ;UAiBA;UAMA;UAIA;UAoBA;UAiBA;;;;;;;;UAiCA;UASA;UAQA;;UAWA;UAOA"}
@@ -1,2 +1,2 @@
1
- const e=`pi.tool_result`,t=`pi.api_error`,n=`pi.turn.finished`,r=`pi.turn.failed`,i=`pi.tool_token_sample`,a=`unknown`,o=`api_error`,s=1.1,c=Math.log(s),l=160,u=200;function d(){return{samples:0,buckets:new Map}}function f(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function p(e){return typeof e==`string`&&e.length>0?e:void 0}function m(e){return e.replace(/[^a-zA-Z0-9@/._:-]/g,`_`).slice(0,128)||`unknown`}function h(e,t){let n=Math.ceil(Math.log(Math.max(e,1))/c);return Math.min(Math.max(n,0),t-1)}function g(e){return s**e}function _(e,t,n){let r=h(t,n);e.buckets.set(r,(e.buckets.get(r)??0)+1),e.samples+=1}function v(e,t){if(e.samples===0)return;let n=e.samples*t,r=[...e.buckets.keys()].sort((e,t)=>e-t),i=0,a=r[0];for(let t of r)if(i+=e.buckets.get(t)??0,a=t,i>=n)break;return Math.round(g(a))}function y(e){return v(e,.95)}function b(e){return e===429?`rate_limited`:e>=500?`provider_unavailable`:o}function x(e){return e.startsWith(`pi.`)?e.slice(3):e}var S=class{events=0;errors=0;toolCalls=0;failedToolCalls=0;turns=0;abortedTurns=0;failedTurns=0;cost=0;tokens={input:0,output:0,total:0,cacheRead:0,cacheWrite:0};eventCounts=new Map;packageCounts=new Map;tools=new Map;toolTokens=new Map;toolFailures=new Map;failureCategories=new Map;operations=new Map;sanitizedFailures=0;errorLog=[];now;maxRecentErrors;constructor(e={}){this.now=e.now??Date.now,this.maxRecentErrors=e.maxRecentErrors??8}record(e,t){let n=m(e);this.events+=1,this.eventCounts.set(n,(this.eventCounts.get(n)??0)+1);let r=p(t[`telemetry.package`]);if(r&&!r.startsWith(`/`)&&!r.includes(`\\`)){let e=m(r);this.packageCounts.set(e,(this.packageCounts.get(e)??0)+1)}this.recordOperationDuration(n,t),this.recordSanitizedFailure(n,t),n===`pi.tool_result`?this.recordToolResult(n,t):n===`pi.api_error`?this.recordApiError(n,t):n===`pi.turn.finished`?this.recordTurnUsage(t):n===`pi.turn.failed`?this.recordTurnFailure(t):n===`pi.tool_token_sample`&&this.recordToolTokenSample(t)}snapshot(){let e=[...this.tools.entries()].map(([e,t])=>{let n=y(t);return{name:e,calls:t.calls,retainedStateSize:t.buckets.size,...n===void 0?{}:{p95Ms:n}}}).sort((e,t)=>(t.p95Ms??-1)-(e.p95Ms??-1)),t=[...this.eventCounts.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),n=[...this.packageCounts.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),r=[...this.operations.entries()].map(([e,t])=>{let n=y(t);return{name:e,calls:t.calls,retainedStateSize:t.buckets.size,...n===void 0?{}:{p95Ms:n}}}).sort((e,t)=>(t.p95Ms??-1)-(e.p95Ms??-1));return{events:this.events,errors:this.errors,toolCalls:this.toolCalls,failedToolCalls:this.failedToolCalls,turns:this.turns,abortedTurns:this.abortedTurns,failedTurns:this.failedTurns,tokens:{...this.tokens},cost:this.cost,toolLatency:e,toolCost:this.toolCostRows(),failureCategories:[...this.failureCategories.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),eventVolume:t,packageEvents:n,sanitizedFailures:this.sanitizedFailures,operationDuration:r,recentErrors:this.errorLog.map(e=>({...e}))}}toolCostRows(){return[...new Set([...this.tools.keys(),...this.toolTokens.keys()])].map(e=>{let t=this.toolTokens.get(e),n=t?v(t,.9):void 0;return{name:e,calls:this.tools.get(e)?.calls??0,failed:this.toolFailures.get(e)??0,samples:t?.samples??0,...n===void 0?{}:{p90Tokens:n}}}).sort((e,t)=>(t.p90Tokens??-1)-(e.p90Tokens??-1))}countCategory(e){this.failureCategories.set(e,(this.failureCategories.get(e)??0)+1)}recordOperationDuration(e,t){let n=Object.entries(t).find(([e,t])=>e!==`tool.duration_ms`&&(e===`duration_ms`||e.endsWith(`.duration_ms`))&&f(t)!==void 0);if(!n)return;let r=m(p(t[`operation.name`])??e),i=this.operations.get(r)??{calls:0,samples:0,buckets:new Map};this.operations.set(r,i),i.calls+=1;let a=f(n[1]);a!==void 0&&_(i,a,160)}recordSanitizedFailure(e,t){let n=p(t.outcome);if(t[`tool.result.error`]!==!0&&e!==`pi.api_error`&&n!==`error`&&n!==`failed`&&n!==`failure`||(this.sanitizedFailures+=1,e===`pi.tool_result`||e===`pi.api_error`))return;e!==`pi.turn.failed`&&this.countCategory(`log_error`);let r=p(t[`error.code`])??n??`error`;this.pushError(e,`${p(t[`error.type`])??`operation`} failed`,m(r))}recordToolResult(e,t){let n=p(t[`tool.name`])??a,r=this.tools.get(n)??{calls:0,samples:0,buckets:new Map};this.tools.set(n,r),r.calls+=1,this.toolCalls+=1;let i=f(t[`tool.duration_ms`]);i!==void 0&&_(r,i,160),t[`tool.result.error`]===!0&&(this.failedToolCalls+=1,this.toolFailures.set(n,(this.toolFailures.get(n)??0)+1),this.countCategory(`tool_failure`),this.pushError(e,`${n}: tool call failed`,`tool`))}recordToolTokenSample(e){let t=f(e[`gen_ai.usage.total_tokens`]);if(t===void 0||t<=0)return;let n=p(e[`tool.name`])??a,r=this.toolTokens.get(n)??d();this.toolTokens.set(n,r),_(r,t,200)}recordApiError(e,t){this.errors+=1;let n=f(t[`http.response.status_code`]),r=String(t[`http.response.status_code`]);this.countCategory(n===void 0?o:b(n)),this.pushError(e,`provider responded ${r}`,r)}recordTurnUsage(e){this.turns+=1,this.tokens.input+=f(e[`gen_ai.usage.input_tokens`])??0,this.tokens.output+=f(e[`gen_ai.usage.output_tokens`])??0,this.tokens.total+=f(e[`gen_ai.usage.total_tokens`])??0,this.tokens.cacheRead+=f(e[`gen_ai.usage.cache_read_tokens`])??0,this.tokens.cacheWrite+=f(e[`gen_ai.usage.cache_write_tokens`])??0,this.cost+=f(e[`gen_ai.usage.cost`])??0}recordTurnFailure(e){let t=p(e.outcome)===`aborted`;t?this.abortedTurns+=1:this.failedTurns+=1,this.countCategory(t?`aborted`:`turn_failed`)}pushError(e,t,n){this.errorLog.unshift({at:this.now(),event:x(e),message:t,code:n}),this.errorLog.length>this.maxRecentErrors&&(this.errorLog.length=this.maxRecentErrors)}};export{t as API_ERROR_RECORD,S as LogMetricsAggregator,l as MAX_RETAINED_STATE_PER_TOOL,u as MAX_RETAINED_TOKEN_BUCKETS,e as TOOL_RESULT_RECORD,i as TOOL_TOKEN_SAMPLE_RECORD,r as TURN_FAILED_RECORD,n as TURN_FINISHED_RECORD};
1
+ const e=`pi.tool_result`,t=`pi.api_error`,n=`pi.turn.finished`,r=`pi.turn.failed`,i=`pi.tool_token_sample`,a=`unknown`,o=128,s=`__overflow__`,c=`api_error`,l=1.1,u=Math.log(l),d=160,f=200;function p(){return{samples:0,buckets:new Map}}function m(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function h(e){return typeof e==`string`&&e.length>0?e:void 0}function g(e){return e.replace(/[^a-zA-Z0-9@/._:-]/g,`_`).slice(0,128)||`unknown`}function _(e,t,n){return e.has(t)||e.size<n-1?t:s}function v(e,t,n){let r=_(e,t,n);e.set(r,(e.get(r)??0)+1)}function y(e,t,n,r){let i=_(e,t,n),a=e.get(i)??r();return e.set(i,a),a}function b(e,t){let n=Math.ceil(Math.log(Math.max(e,1))/u);return Math.min(Math.max(n,0),t-1)}function x(e){return l**e}function S(e,t,n){let r=b(t,n);e.buckets.set(r,(e.buckets.get(r)??0)+1),e.samples+=1}function C(e,t){if(e.samples===0)return;let n=e.samples*t,r=[...e.buckets.keys()].sort((e,t)=>e-t),i=0,a=r[0];for(let t of r)if(i+=e.buckets.get(t)??0,a=t,i>=n)break;return Math.round(x(a))}function w(e){return C(e,.95)}function T(e){return e===429?`rate_limited`:e>=500?`provider_unavailable`:c}function E(e){return e.startsWith(`pi.`)?e.slice(3):e}var D=class{events=0;errors=0;toolCalls=0;failedToolCalls=0;turns=0;abortedTurns=0;failedTurns=0;cost=0;tokens={input:0,output:0,total:0,cacheRead:0,cacheWrite:0};eventCounts=new Map;packageCounts=new Map;toolNames=new Map;tools=new Map;toolTokens=new Map;toolFailures=new Map;failureCategories=new Map;operations=new Map;sanitizedFailures=0;errorLog=[];now;maxRecentErrors;maxMetricNames;constructor(e={}){this.now=e.now??Date.now,this.maxRecentErrors=e.maxRecentErrors??8;let t=e.maxMetricNames??128;this.maxMetricNames=Number.isFinite(t)?Math.min(128,Math.max(1,Math.floor(t))):128}record(e,t){let n=g(e);this.events+=1,v(this.eventCounts,n,this.maxMetricNames);let r=h(t[`telemetry.package`]);r&&!r.startsWith(`/`)&&!r.includes(`\\`)&&v(this.packageCounts,g(r),this.maxMetricNames),this.recordOperationDuration(n,t),this.recordSanitizedFailure(n,t),n===`pi.tool_result`?this.recordToolResult(n,t):n===`pi.api_error`?this.recordApiError(n,t):n===`pi.turn.finished`?this.recordTurnUsage(t):n===`pi.turn.failed`?this.recordTurnFailure(t):n===`pi.tool_token_sample`&&this.recordToolTokenSample(t)}snapshot(){let e=[...this.tools.entries()].map(([e,t])=>{let n=w(t);return{name:e,calls:t.calls,retainedStateSize:t.buckets.size,...n===void 0?{}:{p95Ms:n}}}).sort((e,t)=>(t.p95Ms??-1)-(e.p95Ms??-1)),t=[...this.eventCounts.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),n=[...this.packageCounts.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),r=[...this.operations.entries()].map(([e,t])=>{let n=w(t);return{name:e,calls:t.calls,retainedStateSize:t.buckets.size,...n===void 0?{}:{p95Ms:n}}}).sort((e,t)=>(t.p95Ms??-1)-(e.p95Ms??-1));return{events:this.events,errors:this.errors,toolCalls:this.toolCalls,failedToolCalls:this.failedToolCalls,turns:this.turns,abortedTurns:this.abortedTurns,failedTurns:this.failedTurns,tokens:{...this.tokens},cost:this.cost,toolLatency:e,toolCost:this.toolCostRows(),failureCategories:[...this.failureCategories.entries()].map(([e,t])=>({name:e,count:t})).sort((e,t)=>t.count-e.count),eventVolume:t,packageEvents:n,sanitizedFailures:this.sanitizedFailures,operationDuration:r,recentErrors:this.errorLog.map(e=>({...e}))}}toolCostRows(){return[...new Set([...this.tools.keys(),...this.toolTokens.keys()])].map(e=>{let t=this.toolTokens.get(e),n=t?C(t,.9):void 0;return{name:e,calls:this.tools.get(e)?.calls??0,failed:this.toolFailures.get(e)??0,samples:t?.samples??0,...n===void 0?{}:{p90Tokens:n}}}).sort((e,t)=>(t.p90Tokens??-1)-(e.p90Tokens??-1))}boundedToolName(e){let t=_(this.toolNames,e,this.maxMetricNames);return this.toolNames.set(t,!0),t}countCategory(e){v(this.failureCategories,e,this.maxMetricNames)}recordOperationDuration(e,t){let n=Object.entries(t).find(([e,t])=>e!==`tool.duration_ms`&&(e===`duration_ms`||e.endsWith(`.duration_ms`))&&m(t)!==void 0);if(!n)return;let r=g(h(t[`operation.name`])??e),i=y(this.operations,r,this.maxMetricNames,()=>({calls:0,samples:0,buckets:new Map}));i.calls+=1;let a=m(n[1]);a!==void 0&&S(i,a,160)}recordSanitizedFailure(e,t){let n=h(t.outcome);if(t[`tool.result.error`]!==!0&&e!==`pi.api_error`&&n!==`error`&&n!==`failed`&&n!==`failure`||(this.sanitizedFailures+=1,e===`pi.tool_result`||e===`pi.api_error`))return;e!==`pi.turn.failed`&&this.countCategory(`log_error`);let r=h(t[`error.code`])??n??`error`;this.pushError(e,`${h(t[`error.type`])??`operation`} failed`,g(r))}recordToolResult(e,t){let n=h(t[`tool.name`])??a,r=this.boundedToolName(n),i=y(this.tools,r,this.maxMetricNames,()=>({calls:0,samples:0,buckets:new Map}));i.calls+=1,this.toolCalls+=1;let o=m(t[`tool.duration_ms`]);o!==void 0&&S(i,o,160),t[`tool.result.error`]===!0&&(this.failedToolCalls+=1,v(this.toolFailures,r,this.maxMetricNames),this.countCategory(`tool_failure`),this.pushError(e,`${r}: tool call failed`,`tool`))}recordToolTokenSample(e){let t=m(e[`gen_ai.usage.total_tokens`]);if(t===void 0||t<=0)return;let n=h(e[`tool.name`])??a,r=this.boundedToolName(n);S(y(this.toolTokens,r,this.maxMetricNames,p),t,200)}recordApiError(e,t){this.errors+=1;let n=m(t[`http.response.status_code`]),r=String(t[`http.response.status_code`]);this.countCategory(n===void 0?c:T(n)),this.pushError(e,`provider responded ${r}`,r)}recordTurnUsage(e){this.turns+=1,this.tokens.input+=m(e[`gen_ai.usage.input_tokens`])??0,this.tokens.output+=m(e[`gen_ai.usage.output_tokens`])??0,this.tokens.total+=m(e[`gen_ai.usage.total_tokens`])??0,this.tokens.cacheRead+=m(e[`gen_ai.usage.cache_read_tokens`])??0,this.tokens.cacheWrite+=m(e[`gen_ai.usage.cache_write_tokens`])??0,this.cost+=m(e[`gen_ai.usage.cost`])??0}recordTurnFailure(e){let t=h(e.outcome)===`aborted`;t?this.abortedTurns+=1:this.failedTurns+=1,this.countCategory(t?`aborted`:`turn_failed`)}pushError(e,t,n){this.errorLog.unshift({at:this.now(),event:E(e),message:t,code:n}),this.errorLog.length>this.maxRecentErrors&&(this.errorLog.length=this.maxRecentErrors)}};export{t as API_ERROR_RECORD,D as LogMetricsAggregator,o as MAX_RETAINED_METRIC_NAMES,d as MAX_RETAINED_STATE_PER_TOOL,f as MAX_RETAINED_TOKEN_BUCKETS,s as METRIC_OVERFLOW_NAME,e as TOOL_RESULT_RECORD,i as TOOL_TOKEN_SAMPLE_RECORD,r as TURN_FAILED_RECORD,n as TURN_FINISHED_RECORD};
2
2
  //# sourceMappingURL=metrics.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.mjs","names":[],"sources":["../../src/services/metrics.ts"],"sourcesContent":["/**\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;\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}\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\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 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\n constructor(options: LogMetricsAggregatorOptions = {}) {\n this.now = options.now ?? Date.now;\n this.maxRecentErrors = options.maxRecentErrors ?? DEFAULT_MAX_RECENT_ERRORS;\n }\n\n record(name: string, attributes: Record<string, unknown>): void {\n const metricName = safeMetricName(name);\n this.events += 1;\n this.eventCounts.set(metricName, (this.eventCounts.get(metricName) ?? 0) + 1);\n const packageName = readString(attributes['telemetry.package']);\n if (packageName && !packageName.startsWith('/') && !packageName.includes('\\\\')) {\n const safePackageName = safeMetricName(packageName);\n this.packageCounts.set(safePackageName, (this.packageCounts.get(safePackageName) ?? 0) + 1);\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 countCategory(category: string): void {\n this.failureCategories.set(category, (this.failureCategories.get(category) ?? 0) + 1);\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 = this.operations.get(operationName) ?? { calls: 0, samples: 0, buckets: new Map<number, number>() };\n this.operations.set(operationName, state);\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 state = this.tools.get(toolName) ?? { calls: 0, samples: 0, buckets: new Map<number, number>() };\n this.tools.set(toolName, state);\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 this.toolFailures.set(toolName, (this.toolFailures.get(toolName) ?? 0) + 1);\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, `${toolName}: 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 state = this.toolTokens.get(toolName) ?? emptySampleState();\n this.toolTokens.set(toolName, state);\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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"}
1
+ {"version":3,"file":"metrics.mjs","names":[],"sources":["../../src/services/metrics.ts"],"sourcesContent":["/**\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-log",
3
- "version": "0.0.1-alpha.39",
3
+ "version": "0.0.1-alpha.40",
4
4
  "description": "Pi session metrics, findings, sink status, and a Log Metrics overlay for agent observability.",
5
5
  "keywords": [
6
6
  "agent-observability",
@@ -68,8 +68,8 @@
68
68
  "dependencies": {
69
69
  "@agimon-ai/log-sink-mcp": "0.29.19",
70
70
  "@deepseek-ai/cordis": "4.0.1",
71
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.39",
72
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.39"
71
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.40",
72
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.40"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@earendil-works/pi-coding-agent": "0.84.3",
@@ -79,7 +79,7 @@
79
79
  "tsdown": "0.22.14",
80
80
  "typescript": "7.0.2",
81
81
  "vitest": "4.1.11",
82
- "@agimon-ai/doompi-ui": "0.0.1-alpha.39"
82
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.40"
83
83
  },
84
84
  "peerDependencies": {
85
85
  "@earendil-works/pi-coding-agent": "0.84.3",