@robota-sdk/agent-session-analytics 3.0.0-beta.79 → 3.0.0-beta.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,21 +1,24 @@
1
1
  # @robota-sdk/agent-session-analytics
2
2
 
3
- Session-log timing analysis and reporting for the Robota SDK.
3
+ Session-log timing and personal-usage analysis for the Robota SDK.
4
4
 
5
5
  Given persisted session records, it computes per-turn timing intervals (LLM wait vs. tool
6
- execution), aggregates across sessions, and renders text reports. Pure functions — no file I/O, no
7
- `process.*`, no CLI concerns; callers load records and write output.
6
+ execution), aggregates cross-session usage into complete local-calendar buckets, and renders text
7
+ reports. Pure functions — no file I/O, no `process.*`, no CLI concerns; callers load records and
8
+ write output.
8
9
 
9
10
  ## API
10
11
 
11
- | Export | Description |
12
- | --------------------------- | ---------------------------------------------------- |
13
- | `analyzeSession(record)` | Compute the timing report for one session record |
14
- | `aggregateReports(reports)` | Aggregate multiple single-session reports |
15
- | `computeTimingIntervals(h)` | Classify a history array into timing intervals |
16
- | `gapMs(from, to)` | Millisecond gap between two timestamps (string/Date) |
17
- | `formatSingleSession(r)` | Render a single-session report as text |
18
- | `formatAggregateReport(a)` | Render an aggregate report as text |
12
+ | Export | Description |
13
+ | ------------------------------------ | ----------------------------------------------------------------------------------- |
14
+ | `analyzeSession(record)` | Compute the timing report for one session record |
15
+ | `aggregateReports(reports)` | Aggregate multiple single-session reports |
16
+ | `computeTimingIntervals(h)` | Classify a history array into timing intervals |
17
+ | `gapMs(from, to)` | Millisecond gap between two timestamps (string/Date) |
18
+ | `formatSingleSession(r)` | Render a single-session report as text |
19
+ | `formatAggregateReport(a)` | Render an aggregate report as text |
20
+ | `summarizePersonalUsage(s, request)` | Build a deterministic 7- or 30-day personal-usage report from an immutable snapshot |
21
+ | `formatPersonalUsageReport(report)` | Render the personal-usage report as readable text |
19
22
 
20
23
  Operates on the canonical `IInteractiveSessionRecord` projection and `IHistoryEntry` — owns no
21
24
  duplicate record/history types.
@@ -1,5 +1,5 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e,t){return new Date(t).getTime()-new Date(e).getTime()}function t(e){return new Date(e).toISOString()}function n(n,r,i,a,o,s){return{kind:n,fromType:r,toType:i,fromTimestamp:t(a),toTimestamp:t(o),durationMs:e(a,o),turnIndex:s}}function r(e){let t=[],r=0,i=0;for(;i<e.length;){let a=e[i];if(!a){i++;continue}if(a.category===`chat`&&a.type===`user`){r++;let o=a.timestamp,s=!1,c=null,l=i+1;for(;l<e.length;){let i=e[l];if(!i){l++;continue}if(i.category===`chat`&&i.type===`user`)break;if(i.category===`event`&&i.type===`tool-start`){s?c!==null&&t.push(n(`llm_between_tools`,`tool-end`,`tool-start`,c,i.timestamp,r)):(t.push(n(`user_to_first_tool`,`user`,`tool-start`,o,i.timestamp,r)),s=!0),c=null,l++;continue}if(i.category===`event`&&i.type===`tool-end`){let a=e[l-1];a&&a.category===`event`&&a.type===`tool-start`&&t.push(n(`tool_exec`,`tool-start`,`tool-end`,a.timestamp,i.timestamp,r)),c=i.timestamp,l++;continue}if(i.category===`event`&&i.type===`tool-summary`){c!==null&&t.push(n(`llm_final_response`,`tool-end`,`tool-summary`,c,i.timestamp,r)),l++;continue}if(i.category===`chat`&&i.type===`assistant`){s?c!==null&&t.push(n(`llm_final_response`,`tool-end`,`assistant`,c,i.timestamp,r)):t.push(n(`user_to_assistant`,`user`,`assistant`,o,i.timestamp,r)),l++;break}l++}i=l}else i++}return t}function i(e){let t=e.filter(e=>e.kind===`user_to_first_tool`||e.kind===`user_to_assistant`||e.kind===`llm_between_tools`||e.kind===`llm_final_response`),n=e.filter(e=>e.kind===`tool_exec`),r=e.filter(e=>e.kind===`user_to_assistant`),i=e=>e.length?Math.round(e.reduce((e,t)=>e+t.durationMs,0)/e.length):0,a=e=>e.length?Math.max(...e.map(e=>e.durationMs)):0;return{llmWaitMs:{avg:i(t),max:a(t),total:t.reduce((e,t)=>e+t.durationMs,0),count:t.length},toolExecMs:{avg:i(n),max:a(n),median:(e=>{if(!e.length)return 0;let t=[...e].sort((e,t)=>e.durationMs-t.durationMs),n=Math.floor(t.length/2);return t.length%2==0?Math.round(((t[n-1]?.durationMs??0)+(t[n]?.durationMs??0))/2):t[n]?.durationMs??0})(n),count:n.length},userToAssistantMs:{avg:i(r),max:a(r),count:r.length}}}function a(e){let t=r(e.history??[]),n=t.filter(e=>e.durationMs>=1e4);return{sessionId:e.id,cwd:e.cwd,createdAt:e.createdAt,totalIntervals:t.length,intervals:t,slowIntervals:n,stats:i(t)}}function o(e){let t=e.flatMap(e=>e.intervals.filter(e=>e.kind===`user_to_first_tool`||e.kind===`user_to_assistant`||e.kind===`llm_between_tools`||e.kind===`llm_final_response`)),n=e.flatMap(e=>e.intervals.filter(e=>e.kind===`tool_exec`)),r=t.length?Math.round(t.reduce((e,t)=>e+t.durationMs,0)/t.length):0,i=n.length?Math.round(n.reduce((e,t)=>e+t.durationMs,0)/n.length):0,a=0,o=``,s=0,c=``;for(let t of e)for(let e of t.intervals)e.durationMs>a&&(a=e.durationMs,o=t.sessionId,s=e.turnIndex,c=e.kind);let l=e.map(e=>e.createdAt).sort();return{sessionCount:e.length,fromDate:l[0]??``,toDate:l[l.length-1]??``,avgLlmResponseMs:r,avgToolExecMs:i,maxSingleDelayMs:a,maxSingleDelaySession:o,maxSingleDelayTurn:s,maxSingleDelayKind:c}}function s(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:`${e}`}function c(e){let t=[];if(t.push(`Token usage — session ${e.sessionId}`),t.push(` total ${s(e.totalTokens)} (prompt ${s(e.promptTokens)} · completion ${s(e.completionTokens)})`),e.bySource.length===0)return t.push(` (no usage recorded)`),t.join(`
2
- `);t.push(` by source (most tokens first):`);for(let n of e.bySource)t.push(` ${n.label.padEnd(24)} ${String(n.percentage).padStart(5)}% ${s(n.totalTokens).padStart(7)} (${n.turns} turn${n.turns===1?``:`s`})`);return e.topConsumer&&t.push(` top consumer: ${e.topConsumer.label} (${e.topConsumer.percentage}%)`),t.join(`
3
- `)}function l(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${e}ms`}function u(e){return new Date(e).toLocaleString(`sv-SE`,{timeZoneName:`short`}).slice(0,16)}function d(e){return{user_to_first_tool:`user → tool-start`,user_to_assistant:`user → assistant`,tool_exec:`tool-start → tool-end`,llm_between_tools:`tool-end → tool-start`,llm_final_response:`tool-end → assistant/summary`}[e.kind]??e.kind}function f(e){let{stats:t}=e,n=t.llmWaitMs.total+t.toolExecMs.avg*t.toolExecMs.count;if(n===0)return`Verdict: No timing data available.`;let r=Math.round(t.llmWaitMs.total/n*100);return`Verdict: LLM API wait ${r}% | Code processing ${100-r}%`}function p(e){let t=[],{stats:n}=e;if(t.push(`Session: ${e.sessionId}`),t.push(`Created: ${u(e.createdAt)} | cwd: ${e.cwd}`),t.push(``),t.push(`Timing Summary:`),n.llmWaitMs.count>0&&t.push(` LLM API wait ${l(n.llmWaitMs.avg).padStart(8)} avg | ${l(n.llmWaitMs.max).padStart(8)} max`),n.toolExecMs.count>0&&t.push(` Tool execution ${l(n.toolExecMs.avg).padStart(8)} avg | ${l(n.toolExecMs.median).padStart(8)} median`),n.userToAssistantMs.count>0&&t.push(` user → assistant ${l(n.userToAssistantMs.avg).padStart(8)} avg`),n.llmWaitMs.count===0&&n.toolExecMs.count===0&&t.push(` (no intervals computed — session may have no history)`),e.slowIntervals.length>0){t.push(``),t.push(`Slow intervals (>10s):`);for(let n of e.slowIntervals)t.push(` turn ${n.turnIndex} ${d(n).padEnd(32)} ${l(n.durationMs).padStart(8)}`)}return t.push(``),t.push(f(e)),t.join(`
4
- `)}function m(e){let t=[];return t.push(`Analyzed ${e.sessionCount} sessions (${u(e.fromDate)} ~ ${u(e.toDate)})`),t.push(` Avg LLM response: ${l(e.avgLlmResponseMs)}`),t.push(` Avg tool exec: ${l(e.avgToolExecMs)}`),e.maxSingleDelayMs>0&&t.push(` Max single delay: ${l(e.maxSingleDelayMs)} (${e.maxSingleDelaySession}, turn ${e.maxSingleDelayTurn}, ${e.maxSingleDelayKind})`),t.join(`
5
- `)}const h={scope:`main`,label:`main thread`};function g(e){return`${e.scope}:${e.id??``}`}function _(e){return e.label??(e.id?`${e.scope} ${e.id}`:e.scope)}function v(e){return e.type===`usage-summary`&&typeof e.data==`object`&&e.data!==null}function y(e,t){return t<=0?0:Math.round(e/t*1e3)/10}function b(e){let t=new Map,n=0,r=0,i=0;for(let a of e.history??[]){if(!v(a))continue;let e=a.data,o=e.source??h,s=g(o),c=e.promptTokens??0,l=e.completionTokens??0,u=e.totalTokens;n+=c,r+=l,i+=u;let d=t.get(s);d?(d.promptTokens+=c,d.completionTokens+=l,d.totalTokens+=u,d.turns+=1):t.set(s,{key:s,source:o,label:_(o),promptTokens:c,completionTokens:l,totalTokens:u,turns:1,percentage:0})}let a=[...t.values()].map(e=>({...e,percentage:y(e.totalTokens,i)})).sort((e,t)=>t.totalTokens-e.totalTokens);return{sessionId:e.id,totalTokens:i,promptTokens:n,completionTokens:r,bySource:a,...a[0]?{topConsumer:a[0]}:{}}}exports.aggregateReports=o,exports.analyzeSession=a,exports.computeTimingIntervals=r,exports.formatAggregateReport=m,exports.formatSingleSession=p,exports.formatUsageReport=c,exports.gapMs=e,exports.summarizeUsageBySource=b;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@robota-sdk/agent-core");function t(e,t){return new Date(t).getTime()-new Date(e).getTime()}function n(e){return new Date(e).toISOString()}function r(e,r,i,a,o,s){return{kind:e,fromType:r,toType:i,fromTimestamp:n(a),toTimestamp:n(o),durationMs:t(a,o),turnIndex:s}}function i(e){let t=[],n=0,i=0;for(;i<e.length;){let a=e[i];if(!a){i++;continue}if(a.category===`chat`&&a.type===`user`){n++;let o=a.timestamp,s=!1,c=null,l=i+1;for(;l<e.length;){let i=e[l];if(!i){l++;continue}if(i.category===`chat`&&i.type===`user`)break;if(i.category===`event`&&i.type===`tool-start`){s?c!==null&&t.push(r(`llm_between_tools`,`tool-end`,`tool-start`,c,i.timestamp,n)):(t.push(r(`user_to_first_tool`,`user`,`tool-start`,o,i.timestamp,n)),s=!0),c=null,l++;continue}if(i.category===`event`&&i.type===`tool-end`){let a=e[l-1];a&&a.category===`event`&&a.type===`tool-start`&&t.push(r(`tool_exec`,`tool-start`,`tool-end`,a.timestamp,i.timestamp,n)),c=i.timestamp,l++;continue}if(i.category===`event`&&i.type===`tool-summary`){c!==null&&t.push(r(`llm_final_response`,`tool-end`,`tool-summary`,c,i.timestamp,n)),l++;continue}if(i.category===`chat`&&i.type===`assistant`){s?c!==null&&t.push(r(`llm_final_response`,`tool-end`,`assistant`,c,i.timestamp,n)):t.push(r(`user_to_assistant`,`user`,`assistant`,o,i.timestamp,n)),l++;break}l++}i=l}else i++}return t}function a(e){let t=e.filter(e=>e.kind===`user_to_first_tool`||e.kind===`user_to_assistant`||e.kind===`llm_between_tools`||e.kind===`llm_final_response`),n=e.filter(e=>e.kind===`tool_exec`),r=e.filter(e=>e.kind===`user_to_assistant`),i=e=>e.length?Math.round(e.reduce((e,t)=>e+t.durationMs,0)/e.length):0,a=e=>e.length?Math.max(...e.map(e=>e.durationMs)):0;return{llmWaitMs:{avg:i(t),max:a(t),total:t.reduce((e,t)=>e+t.durationMs,0),count:t.length},toolExecMs:{avg:i(n),max:a(n),median:(e=>{if(!e.length)return 0;let t=[...e].sort((e,t)=>e.durationMs-t.durationMs),n=Math.floor(t.length/2);return t.length%2==0?Math.round(((t[n-1]?.durationMs??0)+(t[n]?.durationMs??0))/2):t[n]?.durationMs??0})(n),count:n.length},userToAssistantMs:{avg:i(r),max:a(r),count:r.length}}}function o(e){let t=i(e.history??[]),n=t.filter(e=>e.durationMs>=1e4);return{sessionId:e.id,cwd:e.cwd,createdAt:e.createdAt,totalIntervals:t.length,intervals:t,slowIntervals:n,stats:a(t)}}function s(e){let t=e.flatMap(e=>e.intervals.filter(e=>e.kind===`user_to_first_tool`||e.kind===`user_to_assistant`||e.kind===`llm_between_tools`||e.kind===`llm_final_response`)),n=e.flatMap(e=>e.intervals.filter(e=>e.kind===`tool_exec`)),r=t.length?Math.round(t.reduce((e,t)=>e+t.durationMs,0)/t.length):0,i=n.length?Math.round(n.reduce((e,t)=>e+t.durationMs,0)/n.length):0,a=0,o=``,s=0,c=``;for(let t of e)for(let e of t.intervals)e.durationMs>a&&(a=e.durationMs,o=t.sessionId,s=e.turnIndex,c=e.kind);let l=e.map(e=>e.createdAt).sort();return{sessionCount:e.length,fromDate:l[0]??``,toDate:l[l.length-1]??``,avgLlmResponseMs:r,avgToolExecMs:i,maxSingleDelayMs:a,maxSingleDelaySession:o,maxSingleDelayTurn:s,maxSingleDelayKind:c}}function c(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:`${e}`}function l(e,t){return`${t?``:`~`}$${e.toFixed(4)}`}function u(e){let t=[];if(t.push(`Token usage — session ${e.sessionId}`),t.push(` total ${c(e.totalTokens)} (prompt ${c(e.promptTokens)} · completion ${c(e.completionTokens)})`),t.push(` cost ${l(e.costUsd,e.costExact)}`),e.bySource.length===0)t.push(` (no usage recorded)`);else{t.push(` by source (most tokens first):`);for(let n of e.bySource)t.push(` ${n.label.padEnd(24)} ${String(n.percentage).padStart(5)}% ${c(n.totalTokens).padStart(7)} ${l(n.costUsd,n.costExact).padStart(10)} (${n.turns} turn${n.turns===1?``:`s`})`);e.topConsumer&&t.push(` top consumer: ${e.topConsumer.label} (${e.topConsumer.percentage}%)`)}if(e.timeline.length>0){t.push(` trace (per turn, sub-turn spans):`);for(let n of e.timeline){t.push(` turn ${n.turnIndex} — ${n.label} · ${d(n.totalDurationMs)} across ${n.spans.length} op${n.spans.length===1?``:`s`}`);for(let e of n.spans)t.push(` ${e.op.padEnd(22)} ${d(e.durationMs).padStart(8)}`)}}return t.join(`
2
+ `)}function d(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${e}ms`}function f(e){return new Date(e).toLocaleString(`sv-SE`,{timeZoneName:`short`}).slice(0,16)}function p(e){return{user_to_first_tool:`user → tool-start`,user_to_assistant:`user → assistant`,tool_exec:`tool-start → tool-end`,llm_between_tools:`tool-end → tool-start`,llm_final_response:`tool-end → assistant/summary`}[e.kind]??e.kind}function m(e){let{stats:t}=e,n=t.llmWaitMs.total+t.toolExecMs.avg*t.toolExecMs.count;if(n===0)return`Verdict: No timing data available.`;let r=Math.round(t.llmWaitMs.total/n*100);return`Verdict: LLM API wait ${r}% | Code processing ${100-r}%`}function h(e){let t=[],{stats:n}=e;if(t.push(`Session: ${e.sessionId}`),t.push(`Created: ${f(e.createdAt)} | cwd: ${e.cwd}`),t.push(``),t.push(`Timing Summary:`),n.llmWaitMs.count>0&&t.push(` LLM API wait ${d(n.llmWaitMs.avg).padStart(8)} avg | ${d(n.llmWaitMs.max).padStart(8)} max`),n.toolExecMs.count>0&&t.push(` Tool execution ${d(n.toolExecMs.avg).padStart(8)} avg | ${d(n.toolExecMs.median).padStart(8)} median`),n.userToAssistantMs.count>0&&t.push(` user → assistant ${d(n.userToAssistantMs.avg).padStart(8)} avg`),n.llmWaitMs.count===0&&n.toolExecMs.count===0&&t.push(` (no intervals computed — session may have no history)`),e.slowIntervals.length>0){t.push(``),t.push(`Slow intervals (>10s):`);for(let n of e.slowIntervals)t.push(` turn ${n.turnIndex} ${p(n).padEnd(32)} ${d(n.durationMs).padStart(8)}`)}return t.push(``),t.push(m(e)),t.join(`
3
+ `)}function g(e){let t=[];return t.push(`Analyzed ${e.sessionCount} sessions (${f(e.fromDate)} ~ ${f(e.toDate)})`),t.push(` Avg LLM response: ${d(e.avgLlmResponseMs)}`),t.push(` Avg tool exec: ${d(e.avgToolExecMs)}`),e.maxSingleDelayMs>0&&t.push(` Max single delay: ${d(e.maxSingleDelayMs)} (${e.maxSingleDelaySession}, turn ${e.maxSingleDelayTurn}, ${e.maxSingleDelayKind})`),t.join(`
4
+ `)}const _={scope:`main`,label:`main thread`};function v(e){return`${e.scope}:${e.id??``}`}function y(e){return e.label??(e.id?`${e.scope} ${e.id}`:e.scope)}function ee(e){return e.type===`usage-summary`&&typeof e.data==`object`&&e.data!==null}function b(e){if(e.type!==`span`||typeof e.data!=`object`||e.data===null)return!1;let t=e.data;return typeof t.spanId==`string`&&typeof t.op==`string`&&typeof t.durationMs==`number`}function x(e,t){return t<=0?0:Math.round(e/t*1e3)/10}function te(e){let t=new Map,n=0,r=0,i=0,a=0,o=!0,s=[],c=[],l=e=>{c.length!==0&&(s.push({turnIndex:s.length,source:e,label:y(e),spans:c,totalDurationMs:c.reduce((e,t)=>e+t.durationMs,0)}),c=[])};for(let s of e.history??[]){if(b(s)){let e=s.data;c.push({spanId:e.spanId,op:e.op,durationMs:e.durationMs});continue}if(!ee(s))continue;let e=s.data,u=e.source??_,d=v(u),f=e.promptTokens??0,p=e.completionTokens??0,m=e.totalTokens,h=e.costUsd??0;n+=f,r+=p,i+=m,a+=h,o=!1;let g=t.get(d);g?(g.promptTokens+=f,g.completionTokens+=p,g.totalTokens+=m,g.turns+=1,g.costUsd+=h,g.costExact=!1):t.set(d,{key:d,source:u,label:y(u),promptTokens:f,completionTokens:p,totalTokens:m,turns:1,percentage:0,costUsd:h,costExact:!1}),l(u)}l(_);let u=[...t.values()].map(e=>({...e,percentage:x(e.totalTokens,i)})).sort((e,t)=>t.totalTokens-e.totalTokens);return{sessionId:e.id,totalTokens:i,promptTokens:n,completionTokens:r,costUsd:a,costExact:o,bySource:u,...u[0]?{topConsumer:u[0]}:{},timeline:s}}function S(e){return typeof e==`object`&&!!e&&!Array.isArray(e)&&!(e instanceof Date)}function C(e){return typeof e==`string`&&e.length>0?e:void 0}function w(e){return typeof e==`number`&&Number.isFinite(e)&&e>=0}function T(e){return e===`unknown`||e===`estimated`||e===`exact`}function ne(e){return(e.promptTokens===void 0||w(e.promptTokens))&&(e.completionTokens===void 0||w(e.completionTokens))}function re(e){let t=e.costStatus;return T(t)?t===`unknown`?e.costUsd===void 0:w(e.costUsd):!1}function ie(e){return w(e.totalTokens)&&w(e.contextUsedTokens)&&w(e.contextMaxTokens)&&w(e.contextUsedPercentage)}function ae(e){return e===`cli`||e===`desktop-app`||e===`browser`||e===`remote`||e===`unknown`}function E(e){if(!S(e))return;let t=e.scope;if(t!==`main`&&t!==`subagent`&&t!==`background`&&t!==`tool`&&t!==`command`&&t!==`skill`)return;let n=C(e.id),r=C(e.label);return{scope:t,...n?{id:n}:{},...r?{label:r}:{}}}function D(e){if(!S(e)||e.kind!==`exact`&&e.kind!==`estimated`||e.scope!==`turn`||!ne(e)||!re(e)||!ie(e)||!T(e.costStatus))return;let t=E(e.source);return{kind:e.kind,scope:`turn`,totalTokens:e.totalTokens,contextUsedTokens:e.contextUsedTokens,contextMaxTokens:e.contextMaxTokens,contextUsedPercentage:e.contextUsedPercentage,costStatus:e.costStatus===`exact`?`estimated`:e.costStatus,...w(e.promptTokens)?{promptTokens:e.promptTokens}:{},...w(e.completionTokens)?{completionTokens:e.completionTokens}:{},...w(e.costUsd)?{costUsd:e.costUsd}:{},...t?{source:t}:{}}}function O(e){if(!S(e))return;let t=C(e.providerId),n=C(e.modelId),{promptTokens:r,completionTokens:i,totalTokens:a,costStatus:o,costUsd:s}=e;if(!(!t||!n||!w(r)||!w(i)||!w(a)||o!==`unknown`&&o!==`estimated`||(o===`estimated`?!w(s):s!==void 0)))return{providerId:t,modelId:n,promptTokens:r,completionTokens:i,totalTokens:a,costStatus:o,...o===`estimated`&&w(s)?{costUsd:s}:{}}}function k(e){if(!Array.isArray(e)||e.length===0)return;let t=e.map(O);return t.every(e=>e!==void 0)?t:void 0}function A(e){if(!S(e))return;let t=C(e.usageObservationId),n=C(e.turnId),r=e.outcome;if(!t||!n||r!==`success`&&r!==`failure`&&r!==`interrupted`)return;let i=e.surface,a=E(e.source),o=D(e.usage),s=k(e.modelShares);return{usageObservationId:t,turnId:n,outcome:r,...C(e.modelId)?{modelId:C(e.modelId)}:{},...C(e.providerId)?{providerId:C(e.providerId)}:{},...s?{modelShares:s}:{},...ae(i)?{surface:i}:{},...a?{source:a}:{},...o?{usage:o}:{}}}function j(e){let t=new Date(e.timestamp);return Number.isNaN(t.getTime())?void 0:t}function M(e){let t=[],n=e.history??[],r=new Set,i=n.flatMap((e,t)=>{if(e.type!==`usage-summary`)return[];let n=D(e.data);return n?[{id:e.id,index:t,usage:n}]:[]});for(let[e,t]of n.entries()){if(t.type!==`usage-observation`)continue;let n=A(t.data);if(!n?.usage)continue;let a=JSON.stringify(n.usage),o=i.map((e,t)=>({candidate:e,index:t})).filter(({candidate:e})=>JSON.stringify(e.usage)===a).sort((t,n)=>Math.abs(t.candidate.index-e)-Math.abs(n.candidate.index-e))[0];o&&(r.add(o.candidate.id),i.splice(o.index,1))}for(let i of n){let n=j(i);if(!n)continue;let a=i.type===`usage-observation`?A(i.data):void 0;if(a){t.push({id:a.usageObservationId,sessionId:e.id,at:n,legacy:!1,observation:a});continue}if(i.type!==`usage-summary`||r.has(i.id))continue;let o=D(i.data);if(!o)continue;let s=`legacy:${e.id}:${i.id}`;t.push({id:s,sessionId:e.id,at:n,legacy:!0,observation:{usageObservationId:s,turnId:`legacy:${i.id}`,outcome:`success`,usage:o,...o.source?{source:o.source}:{},surface:`unknown`}})}return t}function N(e){let t=[];for(let n of e.history??[]){let r=n.data;if(n.type!==`tool-start`||!S(r))continue;let i=C(r.toolName),a=j(n);!i||!a||t.push({id:`tool:${e.id}:${n.id}`,sessionId:e.id,key:`tool:${i}`,label:i,kind:`tool`,at:a})}for(let n of e.skillActivationEvents??[]){if(n.status!==`started`)continue;let r=new Date(n.timestamp);Number.isNaN(r.getTime())||t.push({id:`activation:${e.id}:${n.timestamp}:${n.source}:${n.qualifiedName??n.skillName}:${n.invocation}`,sessionId:e.id,key:n.qualifiedName??`${n.source}:${n.skillName}`,label:n.qualifiedName??n.skillName,kind:n.source,at:r})}return t}function P(){return{sessions:new Set,observations:0,turns:0,promptTokens:0,completionTokens:0,totalTokens:0,costUsd:0,hasUnknownCost:!1,hasEstimatedCost:!1}}function F(e,t){let n=new Intl.DateTimeFormat(`en-CA`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`}).formatToParts(e),r=e=>n.find(t=>t.type===e)?.value??``;return`${r(`year`)}-${r(`month`)}-${r(`day`)}`}function oe(e,t,n){let[r,i,a]=F(e,t).split(`-`).map(Number),o=new Date(Date.UTC(r,i-1,a));return Array.from({length:n},(e,t)=>{let r=new Date(o);return r.setUTCDate(r.getUTCDate()-(n-t-1)),r.toISOString().slice(0,10)})}function se(e){let t=new Map;for(let n of e){let e=t.get(n.key);t.set(n.key,{item:n,count:(e?.count??0)+1})}return[...t.values()].map(({item:e,count:t})=>({key:e.key,label:e.label,kind:e.kind,count:t})).sort((e,t)=>t.count-e.count||e.key.localeCompare(t.key))}function I(e,t){let n=t.observation.usage;e.sessions.add(t.sessionId),e.observations+=1,!t.legacy&&t.observation.source?.scope!==`tool`&&(e.turns+=1),e.promptTokens+=n?.promptTokens??0,e.completionTokens+=n?.completionTokens??0,e.totalTokens+=n?.totalTokens??0,e.costUsd+=n?.costUsd??0,(!n||n.costStatus===`unknown`)&&(e.hasUnknownCost=!0),n?.costStatus===`estimated`&&(e.hasEstimatedCost=!0)}function L(e,t){let n=[];for(let r of e){let e=r.observation.modelShares;if(e===void 0){n.push(r);continue}let i=new Map;for(let n of e){let e=t(n),a=i.get(e)?.observation.usage,o=r.observation.usage,s=n.costStatus===`unknown`||a?.costStatus===`unknown`?`unknown`:`estimated`;i.set(e,{...r,observation:{...r.observation,providerId:n.providerId,modelId:n.modelId,usage:{kind:o?.kind??`estimated`,scope:`turn`,contextUsedTokens:o?.contextUsedTokens??0,contextMaxTokens:o?.contextMaxTokens??0,contextUsedPercentage:o?.contextUsedPercentage??0,promptTokens:(a?.promptTokens??0)+n.promptTokens,completionTokens:(a?.completionTokens??0)+n.completionTokens,totalTokens:(a?.totalTokens??0)+n.totalTokens,costStatus:s,...s===`estimated`&&{costUsd:(a?.costUsd??0)+(n.costUsd??0)}}}})}n.push(...i.values())}return z(n,e=>t(e.observation))}function R(e){return{sessions:e.sessions.size,turns:e.turns,promptTokens:e.promptTokens,completionTokens:e.completionTokens,totalTokens:e.totalTokens,costUsd:e.costUsd,costStatus:e.observations===0||e.hasUnknownCost?`unknown`:e.hasEstimatedCost?`estimated`:`exact`}}function z(e,t){let n=new Map;for(let r of e){let e=t(r),i=n.get(e)??P();I(i,r),n.set(e,i)}return[...n.entries()].map(([e,t])=>({key:e,label:e,frozen:R(t),sessionIds:[...t.sessions].sort()})).map(({key:e,label:t,frozen:n,sessionIds:r})=>({key:e,label:t,turns:n.turns,promptTokens:n.promptTokens,completionTokens:n.completionTokens,totalTokens:n.totalTokens,costUsd:n.costUsd,costStatus:n.costStatus,sessionIds:r})).sort((e,t)=>t.totalTokens-e.totalTokens||e.key.localeCompare(t.key))}function B(e){return e?e.id?`${e.scope}:${e.id}`:e.scope:`unknown`}function V(e,t,n){return e.getTime()<=t.now.getTime()&&n.has(F(e,t.request.timezone))}function H(e,t){let n=[],r=[],i=new Set,a=new Set,o=0,s=0;for(let c of e.records){for(let r of M(c))if(V(r.at,e,t)){if(i.has(r.id)){o+=1;continue}i.add(r.id),n.push(r),r.legacy&&(s+=1)}for(let n of N(c))!V(n.at,e,t)||a.has(n.id)||(a.add(n.id),r.push(n))}return{observations:n,activities:r,duplicateObservations:o,legacyObservations:s}}function U(e,t,n){let r=P(),i=new Map(t.map(e=>[e,P()]));for(let t of n.observations)I(r,t),I(i.get(F(t.at,e.request.timezone)),t);for(let t of n.activities)r.sessions.add(t.sessionId),i.get(F(t.at,e.request.timezone))?.sessions.add(t.sessionId);return{totals:r,byDay:i}}function W(e){let t=e.request.period===`30d`?30:7,n=oe(e.now,e.request.timezone,t),r=H(e,new Set(n)),{totals:i,byDay:a}=U(e,n,r),o=r.observations;return{schemaVersion:1,generatedAt:e.now.toISOString(),period:e.request.period,timezone:e.request.timezone,interval:{startDate:n[0],endDate:n.at(-1)},totals:R(i),daily:n.map((e,t)=>({date:e,partial:t===n.length-1,totals:R(a.get(e)),sessionIds:[...a.get(e).sessions].sort()})),byModel:L(o,e=>e.modelId??`unknown`),byProvider:L(o,e=>e.providerId??`unknown`),bySurface:z(o,e=>e.observation.surface??`unknown`),bySource:z(o,e=>B(e.observation.source)),byActivity:se(r.activities),sessionIds:[...i.sessions].sort(),coverage:{validSessions:e.records.length,corruptSessions:e.corruptSessionIds?.length??0,unsupportedSessions:e.unsupportedSessionIds?.length??0,duplicateObservations:r.duplicateObservations,legacyObservations:r.legacyObservations,unknownModelObservations:o.filter(e=>!e.observation.modelId&&!e.observation.modelShares).length,unknownProviderObservations:o.filter(e=>!e.observation.providerId&&!e.observation.modelShares).length,unknownSurfaceObservations:o.filter(e=>!e.observation.surface||e.observation.surface===`unknown`).length,corruptSessionIds:[...e.corruptSessionIds??[]].sort(),unsupportedSessionIds:[...e.unsupportedSessionIds??[]].sort()}}}const G=(1n<<64n)-1n,K=/^(?!0{32}$)[0-9a-f]{32}$/,q=/^(?!0{16}$)[0-9a-f]{16}$/,ce=[`promptExecutionStartedAt`,`promptExecutionEndedAt`,`promptExecutionOutcome`,`promptExecutionTraceId`,`promptExecutionSpanId`];function J(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Y(e,t){e.set(t,(e.get(t)??0)+1)}function X(e){if(typeof e!=`string`)return;let t=new Date(e);if(Number.isNaN(t.getTime())||t.toISOString()!==e)return;let n=BigInt(t.getTime())*1000000n;return n>=0n&&n<=G?n:void 0}function le(e){let t=X(e.promptExecutionStartedAt),n=X(e.promptExecutionEndedAt),r=e.promptExecutionOutcome,i=e.promptExecutionTraceId,a=e.promptExecutionSpanId;if(!(t===void 0||n===void 0||t>n||r!==`success`&&r!==`failure`&&r!==`interrupted`||typeof i!=`string`||!K.test(i)||typeof a!=`string`||!q.test(a)||typeof e.usageObservationId!=`string`||e.usageObservationId.length===0||e.turnId!==e.usageObservationId||e.outcome!==`success`&&e.outcome!==`failure`&&e.outcome!==`interrupted`))return{traceId:i,spanId:a,name:`robota.prompt_execution`,kind:1,startTimeUnixNano:String(t),endTimeUnixNano:String(n),attributes:[{key:`robota.prompt.outcome`,value:{stringValue:r}}],status:{code:r===`success`?1:r===`failure`?2:0}}}function ue(e,t){if(!J(e))return;let n=e.traceId,r=e.parentSpanId,i=e.spanId,a=X(e.startedAt),o=X(e.endedAt),s=e.outcome;if(!(typeof n!=`string`||!K.test(n)||typeof r!=`string`||!q.test(r)||typeof i!=`string`||!q.test(i)||a===void 0||o===void 0||a>o||s!==`success`&&s!==`failure`&&s!==`interrupted`||t===`provider`&&(!Number.isSafeInteger(e.round)||e.round<1)))return{traceId:n,parentSpanId:r,spanId:i,name:t===`provider`?`robota.provider_call`:`robota.tool_body`,kind:1,startTimeUnixNano:String(a),endTimeUnixNano:String(o),attributes:[{key:t===`provider`?`robota.provider.outcome`:`robota.tool.outcome`,value:{stringValue:s}}],status:{code:s===`success`?1:s===`failure`?2:0}}}function de(e){if(!J(e))return;let t=e.traceId,n=e.spanId;return typeof t==`string`&&K.test(t)&&typeof n==`string`&&q.test(n)?`${t}:${n}`:void 0}function Z(e){return typeof e==`number`&&Number.isSafeInteger(e)&&e>=0}function fe(t,n,r,i){if(!J(n)||n.disposition===void 0)return r.legacy+=1,t;if(n.disposition!==`invoked`)return r.invalid+=1,t;i.invoked+=1;let a=n.usageProvenance;if(a===`partial`)return r.partial+=1,i.unknownCost+=1,t;if(a===`absent`||a===void 0)return r.absent+=1,i.unknownCost+=1,t;let o=n.promptTokens,s=n.completionTokens,c=n.totalTokens;if(a!==`complete`||!Z(o)||!Z(s)||!Number.isSafeInteger(o+s)||c!==o+s)return r.invalid+=1,i.unknownCost+=1,t;i.completeUsage+=1,i.inputTokens+=o,i.outputTokens+=s;let l=[...t.attributes,{key:`robota.provider.usage.input_tokens`,value:{intValue:String(o)}},{key:`robota.provider.usage.output_tokens`,value:{intValue:String(s)}}],u=n.modelId,d=typeof u==`string`&&u.length>0&&u.length<=128&&[...u].every(e=>e.charCodeAt(0)>=32)?(0,e.calculateModelCost)(u,o,s):void 0;return d===void 0||!Number.isFinite(d)||d<0?i.unknownCost+=1:(i.estimatedCostUsd+=d,typeof u==`string`&&Object.hasOwn(e.MODEL_PRICES,u)?i.exactPriceMatches+=1:i.familyPriceMatches+=1,l.push({key:`robota.provider.cost.usd.estimated`,value:{doubleValue:d}})),{...t,attributes:l}}function Q(e,t){let n=[],r=new Map,i=new Map;for(let t of e)for(let e of t.history??[]){if(e.type!==`usage-observation`)continue;let a=e.data,o=J(a)?a.usageObservationId:void 0,s=J(a)?a.promptExecutionTraceId:void 0,c=typeof o==`string`&&o.length>0?JSON.stringify([t.id,o]):void 0,l=typeof s==`string`&&K.test(s)?s:void 0;c&&Y(r,c),l&&Y(i,l),n.push({record:t,data:a,observationKey:c,traceId:l})}let a=[],o=new Map,s=0,c=0,l=0;for(let e of n){if(e.observationKey&&(r.get(e.observationKey)??0)>1||e.traceId&&(i.get(e.traceId)??0)>1){l+=1;continue}let t=e.data;if(!J(t)){c+=1;continue}if(ce.every(e=>t[e]===void 0)){s+=1;continue}let n=le(t);if(!n){c+=1;continue}a.push(n);let u=o.get(e.record)??new Map;u.set(n.traceId,n),o.set(e.record,u)}let u=[],d=new Map;for(let t of e)for(let e of t.history??[]){let n=e.type===`provider-call-trace`?`provider`:e.type===`tool-body-trace`?`tool`:void 0;if(!n)continue;let r=de(e.data),i=ue(e.data,n);r&&Y(d,r),u.push({record:t,span:i,identity:r,kind:n,data:e.data})}let f={provider:{exported:0,invalid:0,orphaned:0,duplicate:0},tool:{exported:0,invalid:0,orphaned:0,duplicate:0}},p={legacy:0,invalid:0,partial:0,absent:0,nonInvoked:0},m={invoked:0,completeUsage:0,inputTokens:0,outputTokens:0,estimatedCostUsd:0,unknownCost:0,exactPriceMatches:0,familyPriceMatches:0};for(let e of u){let{span:t}=e,n=f[e.kind];if(e.identity&&(d.get(e.identity)??0)>1){n.duplicate+=1;continue}if(!t){n.invalid+=1;continue}let r=o.get(e.record)?.get(t.traceId);if(!r||t.parentSpanId!==r.spanId||t.spanId===r.spanId){n.orphaned+=1;continue}if(BigInt(t.startTimeUnixNano)<BigInt(r.startTimeUnixNano)||BigInt(t.endTimeUnixNano)>BigInt(r.endTimeUnixNano)){n.invalid+=1;continue}if(e.kind===`provider`&&J(e.data)&&(e.data.disposition===`cache-hit`||e.data.disposition===`preflight-refused`)){p.nonInvoked+=1,(e.data.promptTokens!==void 0||e.data.completionTokens!==void 0||e.data.totalTokens!==void 0||e.data.usageProvenance===`complete`)&&(p.invalid+=1);continue}a.push(e.kind===`provider`?fe(t,e.data,p,m):t),n.exported+=1}return{payload:{resourceSpans:a.length?[{resource:{attributes:[{key:`service.name`,value:{stringValue:`robota`}},{key:`service.version`,value:{stringValue:t}}]},scopeSpans:[{scope:{name:`@robota-sdk/agent-session-analytics`},spans:a}]}]:[]},coverage:{exported:a.length-f.provider.exported-f.tool.exported,missing:s,invalid:c,duplicate:l,providerChildren:f.provider,providerUsage:p,toolChildren:f.tool},callMetrics:m}}function pe(e,t,n){let r=new Set,i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0;for(let t of e)for(let e of M(t)){if(r.has(e.id))continue;r.add(e.id),e.legacy||(i+=1);let t=e.observation.usage;if(!t){u+=1,c+=1;continue}a+=t.totalTokens,o+=t.promptTokens??0,s+=t.completionTokens??0,(t.promptTokens===void 0||t.completionTokens===void 0)&&(c+=1),t.costStatus===`unknown`||t.costUsd===void 0?u+=1:(l+=t.costUsd,t.costStatus===`estimated`&&(d+=1))}let f=Q(e,n),p=f.callMetrics,m=String(BigInt(t.getTime())*1000000n),h=(e,t,n)=>({name:e,unit:t,gauge:{dataPoints:[{timeUnixNano:m,asDouble:n}]}});return{resourceMetrics:[{resource:{attributes:[{key:`service.name`,value:{stringValue:`robota`}},{key:`service.version`,value:{stringValue:n}}]},scopeMetrics:[{scope:{name:`@robota-sdk/agent-session-analytics`},metrics:[h(`robota.session.count`,`{session}`,e.length),h(`robota.turn.count`,`{turn}`,i),h(`robota.token.total`,`{token}`,a),h(`robota.token.input.known`,`{token}`,o),h(`robota.token.output.known`,`{token}`,s),h(`robota.token.split_unknown_observations`,`{observation}`,c),h(`robota.cost.usd.known`,`USD`,l),h(`robota.cost.unknown_observations`,`{observation}`,u),h(`robota.cost.estimated_observations`,`{observation}`,d),h(`robota.provider_call.count`,`{call}`,p.invoked),h(`robota.provider_call.usage.complete_count`,`{call}`,p.completeUsage),h(`robota.provider_call.token.input.known`,`{token}`,p.inputTokens),h(`robota.provider_call.token.output.known`,`{token}`,p.outputTokens),h(`robota.provider_call.cost.usd.estimated`,`USD`,p.estimatedCostUsd),h(`robota.provider_call.cost.unknown_count`,`{call}`,p.unknownCost),h(`robota.provider_call.usage.partial_count`,`{call}`,f.coverage.providerUsage.partial),h(`robota.provider_call.usage.invalid_count`,`{call}`,f.coverage.providerUsage.invalid),h(`robota.provider_call.usage.legacy_count`,`{call}`,f.coverage.providerUsage.legacy),h(`robota.provider_call.price.exact_id_count`,`{call}`,p.exactPriceMatches),h(`robota.provider_call.price.family_fallback_count`,`{call}`,p.familyPriceMatches)]}]}]}}const me=(1n<<64n)-1n;function he(e){let t=e.getTime();if(!Number.isSafeInteger(t)||t<0)throw RangeError(`OTLP log observation time must be a valid post-epoch date.`);let n=BigInt(t)*1000000n;if(n>me)throw RangeError(`OTLP log observation time is out of range.`);return String(n)}function ge(e,t,n){let r=Q(e,t),i=r.payload.resourceSpans[0]?.scopeSpans[0]?.spans??[];if(i.length===0)return{exported:0,coverage:r.coverage,payload:{resourceLogs:[]}};let a=he(n),o=i.map(e=>{let t=e.attributes.find(e=>e.key===`robota.prompt.outcome`||e.key===`robota.provider.outcome`||e.key===`robota.tool.outcome`),n=e.name===`robota.prompt_execution`?`robota.prompt_execution.completed`:e.name===`robota.provider_call`?`robota.provider_call.completed`:`robota.tool_body.completed`,r=e.status.code===1?9:e.status.code===2?17:13,i=r===9?`INFO`:r===17?`ERROR`:`WARN`;return{timeUnixNano:e.endTimeUnixNano,observedTimeUnixNano:a,severityNumber:r,severityText:i,eventName:n,traceId:e.traceId,spanId:e.spanId,attributes:t&&`stringValue`in t.value?[{key:t.key,value:{stringValue:t.value.stringValue}}]:[]}});return{exported:o.length,coverage:r.coverage,payload:{resourceLogs:[{resource:r.payload.resourceSpans[0].resource,scopeLogs:[{scope:{name:`@robota-sdk/agent-session-analytics`},logRecords:o}]}]}}}function $(e,t){return[``,e,...t.length===0?[`(none)`]:t.map(e=>`${e.label} ${e.turns} turns ${e.totalTokens} tokens cost ${e.costStatus===`unknown`?`unknown`:`$${e.costUsd.toFixed(4)}`}`)]}function _e(e){let t=e.totals.costStatus===`unknown`?`unknown`:`$${e.totals.costUsd.toFixed(4)} (${e.totals.costStatus})`;return[`Personal usage — ${e.period} (${e.timezone})`,`${e.interval.startDate} → ${e.interval.endDate}`,`Sessions ${e.totals.sessions} · Turns ${e.totals.turns} · Tokens ${e.totals.totalTokens} (${e.totals.promptTokens} input / ${e.totals.completionTokens} output) · Cost ${t}`,``,`Daily usage`,...e.daily.map(e=>`${e.date}${e.partial?` (partial)`:``} ${e.totals.turns} turns ${e.totals.totalTokens} tokens`),...$(`By model`,e.byModel),...$(`By provider`,e.byProvider),...$(`By surface`,e.bySurface),...$(`By source`,e.bySource),``,`Activity`,...e.byActivity.length===0?[`(none)`]:e.byActivity.map(e=>`${e.kind}:${e.label} ${e.count} calls`),``,`Coverage: legacy ${e.coverage.legacyObservations}, corrupt ${e.coverage.corruptSessions}, unsupported ${e.coverage.unsupportedSessions}, duplicates ${e.coverage.duplicateObservations}`,`Local estimates from recorded sessions; costs are not an invoice.`].join(`
5
+ `)}exports.aggregateReports=s,exports.analyzeSession=o,exports.computeTimingIntervals=i,exports.createOtlpPromptEvents=ge,exports.createOtlpPromptRootTraces=Q,exports.createOtlpUsageSnapshot=pe,exports.formatAggregateReport=g,exports.formatPersonalUsageReport=_e,exports.formatSingleSession=h,exports.formatUsageReport=u,exports.gapMs=t,exports.summarizePersonalUsage=W,exports.summarizeUsageBySource=te;
@@ -0,0 +1,251 @@
1
+ import { IInteractiveSessionRecord } from "@robota-sdk/agent-interface-session";
2
+ import { IHistoryEntry } from "@robota-sdk/agent-core";
3
+ import { IPersonalUsageReport, IPersonalUsageReport as IPersonalUsageReport$1, IPersonalUsageRequest, IPersonalUsageRequest as IPersonalUsageRequest$1, IRunTraceSpan, IRunTraceTurn, IUsageBySourceReport, IUsageBySourceReport as IUsageBySourceReport$1, IUsageSourceTotals } from "@robota-sdk/agent-interface-analytics";
4
+ //#region src/types.d.ts
5
+ /** Minimal session-record projection the analyzer reads — SSOT-derived, not a duplicate. */
6
+ type TSessionAnalysisInput = Pick<IInteractiveSessionRecord, 'id' | 'cwd' | 'createdAt' | 'history'>;
7
+ type TIntervalKind = 'user_to_first_tool' | 'user_to_assistant' | 'tool_exec' | 'llm_between_tools' | 'llm_final_response';
8
+ interface ITimingInterval {
9
+ kind: TIntervalKind;
10
+ fromType: string;
11
+ toType: string;
12
+ fromTimestamp: string;
13
+ toTimestamp: string;
14
+ durationMs: number;
15
+ turnIndex: number;
16
+ }
17
+ interface ITimingStats {
18
+ llmWaitMs: {
19
+ avg: number;
20
+ max: number;
21
+ total: number;
22
+ count: number;
23
+ };
24
+ toolExecMs: {
25
+ avg: number;
26
+ max: number;
27
+ median: number;
28
+ count: number;
29
+ };
30
+ userToAssistantMs: {
31
+ avg: number;
32
+ max: number;
33
+ count: number;
34
+ };
35
+ }
36
+ interface ISessionTimingReport {
37
+ sessionId: string;
38
+ cwd: string;
39
+ createdAt: string;
40
+ totalIntervals: number;
41
+ intervals: ITimingInterval[];
42
+ slowIntervals: ITimingInterval[];
43
+ stats: ITimingStats;
44
+ }
45
+ interface IAggregateReport {
46
+ sessionCount: number;
47
+ fromDate: string;
48
+ toDate: string;
49
+ avgLlmResponseMs: number;
50
+ avgToolExecMs: number;
51
+ maxSingleDelayMs: number;
52
+ maxSingleDelaySession: string;
53
+ maxSingleDelayTurn: number;
54
+ maxSingleDelayKind: string;
55
+ }
56
+ //#endregion
57
+ //#region src/analyze.d.ts
58
+ /** Gap in milliseconds between two timestamps (ISO string or Date). */
59
+ declare function gapMs(from: string | Date, to: string | Date): number;
60
+ /**
61
+ * Compute timing intervals from a history entry array.
62
+ *
63
+ * Strategy: scan the timeline turn by turn. A "turn" starts with a user message. After user, we
64
+ * expect zero or more tool-start → tool-end cycles, optionally a tool-summary, then an assistant
65
+ * message.
66
+ */
67
+ declare function computeTimingIntervals(history: readonly IHistoryEntry[]): ITimingInterval[];
68
+ /** Compute the timing report for a single session record. */
69
+ declare function analyzeSession(record: TSessionAnalysisInput): ISessionTimingReport;
70
+ /** Aggregate multiple single-session reports into a fleet-level summary. */
71
+ declare function aggregateReports(reports: readonly ISessionTimingReport[]): IAggregateReport;
72
+ //#endregion
73
+ //#region src/usage.d.ts
74
+ /** SSOT-derived projection the usage reducer reads. */
75
+ type TUsageAnalysisInput = Pick<IInteractiveSessionRecord, 'id' | 'history'>;
76
+ /**
77
+ * Aggregate a session's `usage-summary` entries into a per-source token+cost breakdown AND assemble
78
+ * the per-operation span timeline (SELFHOST-004).
79
+ *
80
+ * Token/cost: usage snapshots with no `source` are attributed to the main thread; sources are grouped
81
+ * by `<scope>:<id>` and sorted by total tokens descending with the top consumer surfaced. Cost sums
82
+ * each turn's known `IUsageSnapshot.costUsd` (unpriced turns contribute 0). Historical `exact`
83
+ * snapshots were local price-table estimates, so they cannot certify billed cost.
84
+ *
85
+ * Timeline: history is a chronological timeline where each turn ends with its `usage-summary` entry,
86
+ * so the `span` entries that appear before a given usage-summary are the sub-turn spans of THAT turn.
87
+ * The reducer walks in order, buffering spans, and flushes the buffer as the turn's spans at each
88
+ * usage-summary boundary — grouping sub-turn spans under their owning turn. Spans after the last
89
+ * usage-summary (an in-progress turn) form a trailing timeline entry. Pure: no I/O, stays within the
90
+ * declared `agent-core`, `agent-interface-analytics` and `agent-interface-session` dependencies
91
+ * (NO `agent-plugin` read — enforcement lives elsewhere).
92
+ */
93
+ declare function summarizeUsageBySource(input: TUsageAnalysisInput): IUsageBySourceReport$1;
94
+ //#endregion
95
+ //#region src/report.d.ts
96
+ /**
97
+ * ANALYTICS-001 + SELFHOST-004: render a per-source token+cost breakdown and the per-operation span
98
+ * timeline — which part of the session burned the most tokens/cost, and where the wall-clock went.
99
+ * Pure; the caller owns writing it (the headless CLI path for the TUI/GUI trace/cost view).
100
+ */
101
+ declare function formatUsageReport(report: IUsageBySourceReport): string;
102
+ declare function formatSingleSession(report: ISessionTimingReport): string;
103
+ declare function formatAggregateReport(aggregate: IAggregateReport): string;
104
+ //#endregion
105
+ //#region src/personal-usage.d.ts
106
+ interface IPersonalUsageSnapshot {
107
+ readonly request: IPersonalUsageRequest$1;
108
+ readonly now: Date;
109
+ readonly records: readonly IInteractiveSessionRecord[];
110
+ readonly corruptSessionIds?: readonly string[];
111
+ readonly unsupportedSessionIds?: readonly string[];
112
+ }
113
+ declare function summarizePersonalUsage(input: IPersonalUsageSnapshot): IPersonalUsageReport$1;
114
+ //#endregion
115
+ //#region src/otlp-usage-snapshot.d.ts
116
+ /** A non-additive, content-free snapshot. Consumers must read the latest Gauge, not sum exports. */
117
+ declare function createOtlpUsageSnapshot(records: readonly IInteractiveSessionRecord[], at: Date, version: string): {
118
+ resourceMetrics: unknown[];
119
+ };
120
+ //#endregion
121
+ //#region src/otlp-prompt-root-traces.d.ts
122
+ interface IOtlpSpan {
123
+ readonly traceId: string;
124
+ readonly spanId: string;
125
+ readonly parentSpanId?: string;
126
+ readonly name: 'robota.prompt_execution' | 'robota.provider_call' | 'robota.tool_body';
127
+ readonly kind: 1;
128
+ readonly startTimeUnixNano: string;
129
+ readonly endTimeUnixNano: string;
130
+ readonly attributes: readonly {
131
+ readonly key: string;
132
+ readonly value: {
133
+ readonly stringValue: string;
134
+ } | {
135
+ readonly intValue: string;
136
+ } | {
137
+ readonly doubleValue: number;
138
+ };
139
+ }[];
140
+ readonly status: {
141
+ readonly code: number;
142
+ };
143
+ }
144
+ interface IOtlpPromptRootTraces {
145
+ readonly resourceSpans: readonly {
146
+ readonly resource: {
147
+ readonly attributes: readonly {
148
+ readonly key: string;
149
+ readonly value: {
150
+ readonly stringValue: string;
151
+ };
152
+ }[];
153
+ };
154
+ readonly scopeSpans: readonly {
155
+ readonly scope: {
156
+ readonly name: string;
157
+ };
158
+ readonly spans: readonly IOtlpSpan[];
159
+ }[];
160
+ }[];
161
+ }
162
+ interface IPromptRootTraceCoverage {
163
+ readonly exported: number;
164
+ readonly missing: number;
165
+ readonly invalid: number;
166
+ readonly duplicate: number;
167
+ readonly providerChildren: {
168
+ readonly exported: number;
169
+ readonly invalid: number;
170
+ readonly orphaned: number;
171
+ readonly duplicate: number;
172
+ };
173
+ readonly providerUsage: {
174
+ readonly legacy: number;
175
+ readonly invalid: number;
176
+ readonly partial: number;
177
+ readonly absent: number;
178
+ readonly nonInvoked: number;
179
+ };
180
+ readonly toolChildren: {
181
+ readonly exported: number;
182
+ readonly invalid: number;
183
+ readonly orphaned: number;
184
+ readonly duplicate: number;
185
+ };
186
+ }
187
+ interface IAcceptedProviderCallMetrics {
188
+ readonly invoked: number;
189
+ readonly completeUsage: number;
190
+ readonly inputTokens: number;
191
+ readonly outputTokens: number;
192
+ readonly estimatedCostUsd: number;
193
+ readonly unknownCost: number;
194
+ readonly exactPriceMatches: number;
195
+ readonly familyPriceMatches: number;
196
+ }
197
+ /** Project only proven canonical prompt roots; never infer spans from legacy usage or tool events. */
198
+ declare function createOtlpPromptRootTraces(records: readonly IInteractiveSessionRecord[], version: string): {
199
+ readonly payload: IOtlpPromptRootTraces;
200
+ readonly coverage: IPromptRootTraceCoverage;
201
+ readonly callMetrics: IAcceptedProviderCallMetrics;
202
+ };
203
+ //#endregion
204
+ //#region src/otlp-prompt-events.d.ts
205
+ type TEventName = 'robota.prompt_execution.completed' | 'robota.provider_call.completed' | 'robota.tool_body.completed';
206
+ interface IOtlpLogRecord {
207
+ readonly timeUnixNano: string;
208
+ readonly observedTimeUnixNano: string;
209
+ readonly severityNumber: 9 | 13 | 17;
210
+ readonly severityText: 'INFO' | 'WARN' | 'ERROR';
211
+ readonly eventName: TEventName;
212
+ readonly traceId: string;
213
+ readonly spanId: string;
214
+ readonly attributes: readonly {
215
+ readonly key: string;
216
+ readonly value: {
217
+ readonly stringValue: string;
218
+ };
219
+ }[];
220
+ }
221
+ interface IOtlpPromptEvents {
222
+ readonly resourceLogs: readonly {
223
+ readonly resource: {
224
+ readonly attributes: readonly {
225
+ readonly key: string;
226
+ readonly value: {
227
+ readonly stringValue: string;
228
+ };
229
+ }[];
230
+ };
231
+ readonly scopeLogs: readonly {
232
+ readonly scope: {
233
+ readonly name: string;
234
+ };
235
+ readonly logRecords: readonly IOtlpLogRecord[];
236
+ }[];
237
+ }[];
238
+ }
239
+ /** Explicit local OTLP log projection of canonical prompt completion events. */
240
+ declare function createOtlpPromptEvents(records: readonly IInteractiveSessionRecord[], version: string, observedAt: Date): {
241
+ readonly exported: number;
242
+ readonly coverage: IPromptRootTraceCoverage;
243
+ readonly payload: IOtlpPromptEvents;
244
+ };
245
+ //#endregion
246
+ //#region src/personal-usage-format.d.ts
247
+ /** Render the provider-neutral personal-usage report for the terminal surface. */
248
+ declare function formatPersonalUsageReport(report: IPersonalUsageReport$1): string;
249
+ //#endregion
250
+ export { type IAggregateReport, type IPersonalUsageReport, type IPersonalUsageRequest, type IPersonalUsageSnapshot, type IRunTraceSpan, type IRunTraceTurn, type ISessionTimingReport, type ITimingInterval, type ITimingStats, type IUsageBySourceReport, type IUsageSourceTotals, type TIntervalKind, type TSessionAnalysisInput, type TUsageAnalysisInput, aggregateReports, analyzeSession, computeTimingIntervals, createOtlpPromptEvents, createOtlpPromptRootTraces, createOtlpUsageSnapshot, formatAggregateReport, formatPersonalUsageReport, formatSingleSession, formatUsageReport, gapMs, summarizePersonalUsage, summarizeUsageBySource };
251
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/types.ts","../../src/analyze.ts","../../src/usage.ts","../../src/report.ts","../../src/personal-usage.ts","../../src/otlp-usage-snapshot.ts","../../src/otlp-prompt-root-traces.ts","../../src/otlp-prompt-events.ts","../../src/personal-usage-format.ts"],"mappings":";;;;;KAWY,wBAAwB,KAClC;KAIU;UAOK;EACf,MAAM;EACN;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;IAAa;IAAa;IAAa;IAAe;;EACtD;IAAc;IAAa;IAAa;IAAgB;;EACxD;IAAqB;IAAa;IAAa;;;UAGhC;EACf;EACA;EACA;EACA;EACA,WAAW;EACX,eAAe;EACf,OAAO;;UAGQ;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;iBCtCc,MAAM,eAAe,MAAM,aAAa;;;;;;;;iBAkCxC,uBAAuB,kBAAkB,kBAAkB;;iBA6K3D,eAAe,QAAQ,wBAAwB;;iBAiB/C,iBAAiB,kBAAkB,yBAAyB;;;;KC9MhE,sBAAsB,KAAK;;;;;;;;;;;;;;;;;;iBAqDvB,uBAAuB,OAAO,sBAAsB;;;;;;;;iBCnEpD,kBAAkB,QAAQ;iBAiE1B,oBAAoB,QAAQ;iBA0C5B,sBAAsB,WAAW;;;UC9GhC;WACN,SAAS;WACT,KAAK;WACL,kBAAkB;WAClB;WACA;;iBAuPK,uBAAuB,OAAO,yBAAyB;;;;iBC3QvD,wBACd,kBAAkB,6BAClB,IAAI,MACJ;EACG;;;;UCYK;WACC;WACA;WACA;WACA;WACA;WACA;WACA;WACA;aACE;aACA;eAAkB;;eAAmC;;eAAgC;;;WAEvF;aAAmB;;;UAGb;WACN;aACE;eACE;iBACE;iBACA;mBAAkB;;;;aAGtB;eACE;iBAAkB;;eAClB,gBAAgB;;;;UAKd;WACN;WACA;WACA;WACA;WACA;aACE;aACA;aACA;aACA;;WAEF;aACE;aACA;aACA;aACA;aACA;;WAEF;aACE;aACA;aACA;aACA;;;UAII;WACN;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;iBA6KK,2BACd,kBAAkB,6BAClB;WACY,SAAS;WAAgC,UAAU;WAAmC,aAAa;;;;KCjQ5G;UAKK;WACC;WACA;WACA;WACA;WACA,WAAW;WACX;WACA;WACA;aACE;aACA;eAAkB;;;;UAId;WACN;aACE;eACE;iBACE;iBACA;mBAAkB;;;;aAGtB;eACE;iBAAkB;;eAClB,qBAAqB;;;;;iBAkBpB,uBACd,kBAAkB,6BAClB,iBACA,YAAY;WAEH;WACA,UAAU;WACV,SAAS;;;;;iBCtCJ,0BAA0B,QAAQ"}
@@ -1,6 +1,6 @@
1
- import { IInteractiveSessionRecord, IUsageSource } from "@robota-sdk/agent-interface-transport";
2
1
  import { IHistoryEntry } from "@robota-sdk/agent-core";
3
-
2
+ import { IInteractiveSessionRecord } from "@robota-sdk/agent-interface-session";
3
+ import { IPersonalUsageReport, IPersonalUsageReport as IPersonalUsageReport$1, IPersonalUsageRequest, IPersonalUsageRequest as IPersonalUsageRequest$1, IRunTraceSpan, IRunTraceTurn, IUsageBySourceReport, IUsageBySourceReport as IUsageBySourceReport$1, IUsageSourceTotals } from "@robota-sdk/agent-interface-analytics";
4
4
  //#region src/types.d.ts
5
5
  /** Minimal session-record projection the analyzer reads — SSOT-derived, not a duplicate. */
6
6
  type TSessionAnalysisInput = Pick<IInteractiveSessionRecord, 'id' | 'cwd' | 'createdAt' | 'history'>;
@@ -73,45 +73,179 @@ declare function aggregateReports(reports: readonly ISessionTimingReport[]): IAg
73
73
  //#region src/usage.d.ts
74
74
  /** SSOT-derived projection the usage reducer reads. */
75
75
  type TUsageAnalysisInput = Pick<IInteractiveSessionRecord, 'id' | 'history'>;
76
- interface IUsageSourceTotals {
77
- /** Stable grouping key (`<scope>:<id>`). */
78
- key: string;
79
- source: IUsageSource;
80
- label: string;
81
- promptTokens: number;
82
- completionTokens: number;
83
- totalTokens: number;
84
- /** How many usage snapshots (turns) were attributed to this source. */
85
- turns: number;
86
- /** Share of the session's total tokens, 0–100 (rounded to 1 decimal). */
87
- percentage: number;
88
- }
89
- interface IUsageBySourceReport {
90
- sessionId: string;
91
- totalTokens: number;
92
- promptTokens: number;
93
- completionTokens: number;
94
- /** Per-source totals, sorted by `totalTokens` descending. */
95
- bySource: IUsageSourceTotals[];
96
- /** The single biggest token consumer, if any usage was recorded. */
97
- topConsumer?: IUsageSourceTotals;
98
- }
99
76
  /**
100
- * Aggregate a session's `usage-summary` entries into a per-source token breakdown.
77
+ * Aggregate a session's `usage-summary` entries into a per-source token+cost breakdown AND assemble
78
+ * the per-operation span timeline (SELFHOST-004).
101
79
  *
102
- * Usage snapshots with no `source` are attributed to the main thread. Sources are grouped by
103
- * `<scope>:<id>`; the result is sorted by total tokens descending with the top consumer surfaced.
80
+ * Token/cost: usage snapshots with no `source` are attributed to the main thread; sources are grouped
81
+ * by `<scope>:<id>` and sorted by total tokens descending with the top consumer surfaced. Cost sums
82
+ * each turn's known `IUsageSnapshot.costUsd` (unpriced turns contribute 0). Historical `exact`
83
+ * snapshots were local price-table estimates, so they cannot certify billed cost.
84
+ *
85
+ * Timeline: history is a chronological timeline where each turn ends with its `usage-summary` entry,
86
+ * so the `span` entries that appear before a given usage-summary are the sub-turn spans of THAT turn.
87
+ * The reducer walks in order, buffering spans, and flushes the buffer as the turn's spans at each
88
+ * usage-summary boundary — grouping sub-turn spans under their owning turn. Spans after the last
89
+ * usage-summary (an in-progress turn) form a trailing timeline entry. Pure: no I/O, stays within the
90
+ * declared `agent-core`, `agent-interface-analytics` and `agent-interface-session` dependencies
91
+ * (NO `agent-plugin` read — enforcement lives elsewhere).
104
92
  */
105
- declare function summarizeUsageBySource(input: TUsageAnalysisInput): IUsageBySourceReport;
93
+ declare function summarizeUsageBySource(input: TUsageAnalysisInput): IUsageBySourceReport$1;
106
94
  //#endregion
107
95
  //#region src/report.d.ts
108
96
  /**
109
- * ANALYTICS-001: render a per-source token-usage breakdown — which part of the session burned the
110
- * most tokens. Pure; the caller owns writing it.
97
+ * ANALYTICS-001 + SELFHOST-004: render a per-source token+cost breakdown and the per-operation span
98
+ * timeline — which part of the session burned the most tokens/cost, and where the wall-clock went.
99
+ * Pure; the caller owns writing it (the headless CLI path for the TUI/GUI trace/cost view).
111
100
  */
112
101
  declare function formatUsageReport(report: IUsageBySourceReport): string;
113
102
  declare function formatSingleSession(report: ISessionTimingReport): string;
114
103
  declare function formatAggregateReport(aggregate: IAggregateReport): string;
115
104
  //#endregion
116
- export { type IAggregateReport, type ISessionTimingReport, type ITimingInterval, type ITimingStats, type IUsageBySourceReport, type IUsageSourceTotals, type TIntervalKind, type TSessionAnalysisInput, type TUsageAnalysisInput, aggregateReports, analyzeSession, computeTimingIntervals, formatAggregateReport, formatSingleSession, formatUsageReport, gapMs, summarizeUsageBySource };
105
+ //#region src/personal-usage.d.ts
106
+ interface IPersonalUsageSnapshot {
107
+ readonly request: IPersonalUsageRequest$1;
108
+ readonly now: Date;
109
+ readonly records: readonly IInteractiveSessionRecord[];
110
+ readonly corruptSessionIds?: readonly string[];
111
+ readonly unsupportedSessionIds?: readonly string[];
112
+ }
113
+ declare function summarizePersonalUsage(input: IPersonalUsageSnapshot): IPersonalUsageReport$1;
114
+ //#endregion
115
+ //#region src/otlp-usage-snapshot.d.ts
116
+ /** A non-additive, content-free snapshot. Consumers must read the latest Gauge, not sum exports. */
117
+ declare function createOtlpUsageSnapshot(records: readonly IInteractiveSessionRecord[], at: Date, version: string): {
118
+ resourceMetrics: unknown[];
119
+ };
120
+ //#endregion
121
+ //#region src/otlp-prompt-root-traces.d.ts
122
+ interface IOtlpSpan {
123
+ readonly traceId: string;
124
+ readonly spanId: string;
125
+ readonly parentSpanId?: string;
126
+ readonly name: 'robota.prompt_execution' | 'robota.provider_call' | 'robota.tool_body';
127
+ readonly kind: 1;
128
+ readonly startTimeUnixNano: string;
129
+ readonly endTimeUnixNano: string;
130
+ readonly attributes: readonly {
131
+ readonly key: string;
132
+ readonly value: {
133
+ readonly stringValue: string;
134
+ } | {
135
+ readonly intValue: string;
136
+ } | {
137
+ readonly doubleValue: number;
138
+ };
139
+ }[];
140
+ readonly status: {
141
+ readonly code: number;
142
+ };
143
+ }
144
+ interface IOtlpPromptRootTraces {
145
+ readonly resourceSpans: readonly {
146
+ readonly resource: {
147
+ readonly attributes: readonly {
148
+ readonly key: string;
149
+ readonly value: {
150
+ readonly stringValue: string;
151
+ };
152
+ }[];
153
+ };
154
+ readonly scopeSpans: readonly {
155
+ readonly scope: {
156
+ readonly name: string;
157
+ };
158
+ readonly spans: readonly IOtlpSpan[];
159
+ }[];
160
+ }[];
161
+ }
162
+ interface IPromptRootTraceCoverage {
163
+ readonly exported: number;
164
+ readonly missing: number;
165
+ readonly invalid: number;
166
+ readonly duplicate: number;
167
+ readonly providerChildren: {
168
+ readonly exported: number;
169
+ readonly invalid: number;
170
+ readonly orphaned: number;
171
+ readonly duplicate: number;
172
+ };
173
+ readonly providerUsage: {
174
+ readonly legacy: number;
175
+ readonly invalid: number;
176
+ readonly partial: number;
177
+ readonly absent: number;
178
+ readonly nonInvoked: number;
179
+ };
180
+ readonly toolChildren: {
181
+ readonly exported: number;
182
+ readonly invalid: number;
183
+ readonly orphaned: number;
184
+ readonly duplicate: number;
185
+ };
186
+ }
187
+ interface IAcceptedProviderCallMetrics {
188
+ readonly invoked: number;
189
+ readonly completeUsage: number;
190
+ readonly inputTokens: number;
191
+ readonly outputTokens: number;
192
+ readonly estimatedCostUsd: number;
193
+ readonly unknownCost: number;
194
+ readonly exactPriceMatches: number;
195
+ readonly familyPriceMatches: number;
196
+ }
197
+ /** Project only proven canonical prompt roots; never infer spans from legacy usage or tool events. */
198
+ declare function createOtlpPromptRootTraces(records: readonly IInteractiveSessionRecord[], version: string): {
199
+ readonly payload: IOtlpPromptRootTraces;
200
+ readonly coverage: IPromptRootTraceCoverage;
201
+ readonly callMetrics: IAcceptedProviderCallMetrics;
202
+ };
203
+ //#endregion
204
+ //#region src/otlp-prompt-events.d.ts
205
+ type TEventName = 'robota.prompt_execution.completed' | 'robota.provider_call.completed' | 'robota.tool_body.completed';
206
+ interface IOtlpLogRecord {
207
+ readonly timeUnixNano: string;
208
+ readonly observedTimeUnixNano: string;
209
+ readonly severityNumber: 9 | 13 | 17;
210
+ readonly severityText: 'INFO' | 'WARN' | 'ERROR';
211
+ readonly eventName: TEventName;
212
+ readonly traceId: string;
213
+ readonly spanId: string;
214
+ readonly attributes: readonly {
215
+ readonly key: string;
216
+ readonly value: {
217
+ readonly stringValue: string;
218
+ };
219
+ }[];
220
+ }
221
+ interface IOtlpPromptEvents {
222
+ readonly resourceLogs: readonly {
223
+ readonly resource: {
224
+ readonly attributes: readonly {
225
+ readonly key: string;
226
+ readonly value: {
227
+ readonly stringValue: string;
228
+ };
229
+ }[];
230
+ };
231
+ readonly scopeLogs: readonly {
232
+ readonly scope: {
233
+ readonly name: string;
234
+ };
235
+ readonly logRecords: readonly IOtlpLogRecord[];
236
+ }[];
237
+ }[];
238
+ }
239
+ /** Explicit local OTLP log projection of canonical prompt completion events. */
240
+ declare function createOtlpPromptEvents(records: readonly IInteractiveSessionRecord[], version: string, observedAt: Date): {
241
+ readonly exported: number;
242
+ readonly coverage: IPromptRootTraceCoverage;
243
+ readonly payload: IOtlpPromptEvents;
244
+ };
245
+ //#endregion
246
+ //#region src/personal-usage-format.d.ts
247
+ /** Render the provider-neutral personal-usage report for the terminal surface. */
248
+ declare function formatPersonalUsageReport(report: IPersonalUsageReport$1): string;
249
+ //#endregion
250
+ export { type IAggregateReport, type IPersonalUsageReport, type IPersonalUsageRequest, type IPersonalUsageSnapshot, type IRunTraceSpan, type IRunTraceTurn, type ISessionTimingReport, type ITimingInterval, type ITimingStats, type IUsageBySourceReport, type IUsageSourceTotals, type TIntervalKind, type TSessionAnalysisInput, type TUsageAnalysisInput, aggregateReports, analyzeSession, computeTimingIntervals, createOtlpPromptEvents, createOtlpPromptRootTraces, createOtlpUsageSnapshot, formatAggregateReport, formatPersonalUsageReport, formatSingleSession, formatUsageReport, gapMs, summarizePersonalUsage, summarizeUsageBySource };
117
251
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/analyze.ts","../../src/usage.ts","../../src/report.ts"],"mappings":";;;;AAY2B;AAAA,KADf,qBAAA,GAAwB,IAAI,CACtC,yBAAA;AAAA,KAIU,aAAA;AAAA,UAOK,eAAA;EACf,IAAA,EAAM,aAAa;EACnB,QAAA;EACA,MAAA;EACA,aAAA;EACA,WAAA;EACA,UAAA;EACA,SAAA;AAAA;AAAA,UAGe,YAAA;EACf,SAAA;IAAa,GAAA;IAAa,GAAA;IAAa,KAAA;IAAe,KAAA;EAAA;EACtD,UAAA;IAAc,GAAA;IAAa,GAAA;IAAa,MAAA;IAAgB,KAAA;EAAA;EACxD,iBAAA;IAAqB,GAAA;IAAa,GAAA;IAAa,KAAA;EAAA;AAAA;AAAA,UAGhC,oBAAA;EACf,SAAA;EACA,GAAA;EACA,SAAA;EACA,cAAA;EACA,SAAA,EAAW,eAAA;EACX,aAAA,EAAe,eAAA;EACf,KAAA,EAAO,YAAA;AAAA;AAAA,UAGQ,gBAAA;EACf,YAAA;EACA,QAAA;EACA,MAAA;EACA,gBAAA;EACA,aAAA;EACA,gBAAA;EACA,qBAAA;EACA,kBAAA;EACA,kBAAA;AAAA;;;AA9CyB;AAAA,iBCQX,KAAA,CAAM,IAAA,WAAe,IAAA,EAAM,EAAA,WAAa,IAAI;;;;ADJnC;AAOzB;;;iBC+BgB,sBAAA,CAAuB,OAAA,WAAkB,aAAA,KAAkB,eAAe;;iBA6K1E,cAAA,CAAe,MAAA,EAAQ,qBAAA,GAAwB,oBAAoB;;iBAiBnE,gBAAA,CAAiB,OAAA,WAAkB,oBAAA,KAAyB,gBAAgB;;;ADpO5F;AAAA,KEIY,mBAAA,GAAsB,IAAI,CAAC,yBAAA;AAAA,UAKtB,kBAAA;EFTQ;EEWvB,GAAA;EACA,MAAA,EAAQ,YAAY;EACpB,KAAA;EACA,YAAA;EACA,gBAAA;EACA,WAAA;EFRM;EEUN,KAAA;EFRA;EEUA,UAAA;AAAA;AAAA,UAGe,oBAAA;EACf,SAAA;EACA,WAAA;EACA,YAAA;EACA,gBAAA;EFV2B;EEY3B,QAAA,EAAU,kBAAA;EFZiB;EEc3B,WAAA,GAAc,kBAAkB;AAAA;;;;;;;iBA4BlB,sBAAA,CAAuB,KAAA,EAAO,mBAAA,GAAsB,oBAAoB;;;;;AF/D7D;AAI3B;iBGEgB,iBAAA,CAAkB,MAA4B,EAApB,oBAAoB;AAAA,iBAmD9C,mBAAA,CAAoB,MAA4B,EAApB,oBAAoB;AAAA,iBA0ChD,qBAAA,CAAsB,SAA2B,EAAhB,gBAAgB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/analyze.ts","../../src/usage.ts","../../src/report.ts","../../src/personal-usage.ts","../../src/otlp-usage-snapshot.ts","../../src/otlp-prompt-root-traces.ts","../../src/otlp-prompt-events.ts","../../src/personal-usage-format.ts"],"mappings":";;;;;KAWY,wBAAwB,KAClC;KAIU;UAOK;EACf,MAAM;EACN;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;IAAa;IAAa;IAAa;IAAe;;EACtD;IAAc;IAAa;IAAa;IAAgB;;EACxD;IAAqB;IAAa;IAAa;;;UAGhC;EACf;EACA;EACA;EACA;EACA,WAAW;EACX,eAAe;EACf,OAAO;;UAGQ;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;iBCtCc,MAAM,eAAe,MAAM,aAAa;;;;;;;;iBAkCxC,uBAAuB,kBAAkB,kBAAkB;;iBA6K3D,eAAe,QAAQ,wBAAwB;;iBAiB/C,iBAAiB,kBAAkB,yBAAyB;;;;KC9MhE,sBAAsB,KAAK;;;;;;;;;;;;;;;;;;iBAqDvB,uBAAuB,OAAO,sBAAsB;;;;;;;;iBCnEpD,kBAAkB,QAAQ;iBAiE1B,oBAAoB,QAAQ;iBA0C5B,sBAAsB,WAAW;;;UC9GhC;WACN,SAAS;WACT,KAAK;WACL,kBAAkB;WAClB;WACA;;iBAuPK,uBAAuB,OAAO,yBAAyB;;;;iBC3QvD,wBACd,kBAAkB,6BAClB,IAAI,MACJ;EACG;;;;UCYK;WACC;WACA;WACA;WACA;WACA;WACA;WACA;WACA;aACE;aACA;eAAkB;;eAAmC;;eAAgC;;;WAEvF;aAAmB;;;UAGb;WACN;aACE;eACE;iBACE;iBACA;mBAAkB;;;;aAGtB;eACE;iBAAkB;;eAClB,gBAAgB;;;;UAKd;WACN;WACA;WACA;WACA;WACA;aACE;aACA;aACA;aACA;;WAEF;aACE;aACA;aACA;aACA;aACA;;WAEF;aACE;aACA;aACA;aACA;;;UAII;WACN;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;iBA6KK,2BACd,kBAAkB,6BAClB;WACY,SAAS;WAAgC,UAAU;WAAmC,aAAa;;;;KCjQ5G;UAKK;WACC;WACA;WACA;WACA;WACA,WAAW;WACX;WACA;WACA;aACE;aACA;eAAkB;;;;UAId;WACN;aACE;eACE;iBACE;iBACA;mBAAkB;;;;aAGtB;eACE;iBAAkB;;eAClB,qBAAqB;;;;;iBAkBpB,uBACd,kBAAkB,6BAClB,iBACA,YAAY;WAEH;WACA,UAAU;WACV,SAAS;;;;;iBCtCJ,0BAA0B,QAAQ"}
@@ -1,6 +1,6 @@
1
- function e(e,t){return new Date(t).getTime()-new Date(e).getTime()}function t(e){return new Date(e).toISOString()}function n(n,r,i,a,o,s){return{kind:n,fromType:r,toType:i,fromTimestamp:t(a),toTimestamp:t(o),durationMs:e(a,o),turnIndex:s}}function r(e){let t=[],r=0,i=0;for(;i<e.length;){let a=e[i];if(!a){i++;continue}if(a.category===`chat`&&a.type===`user`){r++;let o=a.timestamp,s=!1,c=null,l=i+1;for(;l<e.length;){let i=e[l];if(!i){l++;continue}if(i.category===`chat`&&i.type===`user`)break;if(i.category===`event`&&i.type===`tool-start`){s?c!==null&&t.push(n(`llm_between_tools`,`tool-end`,`tool-start`,c,i.timestamp,r)):(t.push(n(`user_to_first_tool`,`user`,`tool-start`,o,i.timestamp,r)),s=!0),c=null,l++;continue}if(i.category===`event`&&i.type===`tool-end`){let a=e[l-1];a&&a.category===`event`&&a.type===`tool-start`&&t.push(n(`tool_exec`,`tool-start`,`tool-end`,a.timestamp,i.timestamp,r)),c=i.timestamp,l++;continue}if(i.category===`event`&&i.type===`tool-summary`){c!==null&&t.push(n(`llm_final_response`,`tool-end`,`tool-summary`,c,i.timestamp,r)),l++;continue}if(i.category===`chat`&&i.type===`assistant`){s?c!==null&&t.push(n(`llm_final_response`,`tool-end`,`assistant`,c,i.timestamp,r)):t.push(n(`user_to_assistant`,`user`,`assistant`,o,i.timestamp,r)),l++;break}l++}i=l}else i++}return t}function i(e){let t=e.filter(e=>e.kind===`user_to_first_tool`||e.kind===`user_to_assistant`||e.kind===`llm_between_tools`||e.kind===`llm_final_response`),n=e.filter(e=>e.kind===`tool_exec`),r=e.filter(e=>e.kind===`user_to_assistant`),i=e=>e.length?Math.round(e.reduce((e,t)=>e+t.durationMs,0)/e.length):0,a=e=>e.length?Math.max(...e.map(e=>e.durationMs)):0;return{llmWaitMs:{avg:i(t),max:a(t),total:t.reduce((e,t)=>e+t.durationMs,0),count:t.length},toolExecMs:{avg:i(n),max:a(n),median:(e=>{if(!e.length)return 0;let t=[...e].sort((e,t)=>e.durationMs-t.durationMs),n=Math.floor(t.length/2);return t.length%2==0?Math.round(((t[n-1]?.durationMs??0)+(t[n]?.durationMs??0))/2):t[n]?.durationMs??0})(n),count:n.length},userToAssistantMs:{avg:i(r),max:a(r),count:r.length}}}function a(e){let t=r(e.history??[]),n=t.filter(e=>e.durationMs>=1e4);return{sessionId:e.id,cwd:e.cwd,createdAt:e.createdAt,totalIntervals:t.length,intervals:t,slowIntervals:n,stats:i(t)}}function o(e){let t=e.flatMap(e=>e.intervals.filter(e=>e.kind===`user_to_first_tool`||e.kind===`user_to_assistant`||e.kind===`llm_between_tools`||e.kind===`llm_final_response`)),n=e.flatMap(e=>e.intervals.filter(e=>e.kind===`tool_exec`)),r=t.length?Math.round(t.reduce((e,t)=>e+t.durationMs,0)/t.length):0,i=n.length?Math.round(n.reduce((e,t)=>e+t.durationMs,0)/n.length):0,a=0,o=``,s=0,c=``;for(let t of e)for(let e of t.intervals)e.durationMs>a&&(a=e.durationMs,o=t.sessionId,s=e.turnIndex,c=e.kind);let l=e.map(e=>e.createdAt).sort();return{sessionCount:e.length,fromDate:l[0]??``,toDate:l[l.length-1]??``,avgLlmResponseMs:r,avgToolExecMs:i,maxSingleDelayMs:a,maxSingleDelaySession:o,maxSingleDelayTurn:s,maxSingleDelayKind:c}}function s(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:`${e}`}function c(e){let t=[];if(t.push(`Token usage — session ${e.sessionId}`),t.push(` total ${s(e.totalTokens)} (prompt ${s(e.promptTokens)} · completion ${s(e.completionTokens)})`),e.bySource.length===0)return t.push(` (no usage recorded)`),t.join(`
2
- `);t.push(` by source (most tokens first):`);for(let n of e.bySource)t.push(` ${n.label.padEnd(24)} ${String(n.percentage).padStart(5)}% ${s(n.totalTokens).padStart(7)} (${n.turns} turn${n.turns===1?``:`s`})`);return e.topConsumer&&t.push(` top consumer: ${e.topConsumer.label} (${e.topConsumer.percentage}%)`),t.join(`
3
- `)}function l(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${e}ms`}function u(e){return new Date(e).toLocaleString(`sv-SE`,{timeZoneName:`short`}).slice(0,16)}function d(e){return{user_to_first_tool:`user → tool-start`,user_to_assistant:`user → assistant`,tool_exec:`tool-start → tool-end`,llm_between_tools:`tool-end → tool-start`,llm_final_response:`tool-end → assistant/summary`}[e.kind]??e.kind}function f(e){let{stats:t}=e,n=t.llmWaitMs.total+t.toolExecMs.avg*t.toolExecMs.count;if(n===0)return`Verdict: No timing data available.`;let r=Math.round(t.llmWaitMs.total/n*100);return`Verdict: LLM API wait ${r}% | Code processing ${100-r}%`}function p(e){let t=[],{stats:n}=e;if(t.push(`Session: ${e.sessionId}`),t.push(`Created: ${u(e.createdAt)} | cwd: ${e.cwd}`),t.push(``),t.push(`Timing Summary:`),n.llmWaitMs.count>0&&t.push(` LLM API wait ${l(n.llmWaitMs.avg).padStart(8)} avg | ${l(n.llmWaitMs.max).padStart(8)} max`),n.toolExecMs.count>0&&t.push(` Tool execution ${l(n.toolExecMs.avg).padStart(8)} avg | ${l(n.toolExecMs.median).padStart(8)} median`),n.userToAssistantMs.count>0&&t.push(` user → assistant ${l(n.userToAssistantMs.avg).padStart(8)} avg`),n.llmWaitMs.count===0&&n.toolExecMs.count===0&&t.push(` (no intervals computed — session may have no history)`),e.slowIntervals.length>0){t.push(``),t.push(`Slow intervals (>10s):`);for(let n of e.slowIntervals)t.push(` turn ${n.turnIndex} ${d(n).padEnd(32)} ${l(n.durationMs).padStart(8)}`)}return t.push(``),t.push(f(e)),t.join(`
4
- `)}function m(e){let t=[];return t.push(`Analyzed ${e.sessionCount} sessions (${u(e.fromDate)} ~ ${u(e.toDate)})`),t.push(` Avg LLM response: ${l(e.avgLlmResponseMs)}`),t.push(` Avg tool exec: ${l(e.avgToolExecMs)}`),e.maxSingleDelayMs>0&&t.push(` Max single delay: ${l(e.maxSingleDelayMs)} (${e.maxSingleDelaySession}, turn ${e.maxSingleDelayTurn}, ${e.maxSingleDelayKind})`),t.join(`
5
- `)}const h={scope:`main`,label:`main thread`};function g(e){return`${e.scope}:${e.id??``}`}function _(e){return e.label??(e.id?`${e.scope} ${e.id}`:e.scope)}function v(e){return e.type===`usage-summary`&&typeof e.data==`object`&&e.data!==null}function y(e,t){return t<=0?0:Math.round(e/t*1e3)/10}function b(e){let t=new Map,n=0,r=0,i=0;for(let a of e.history??[]){if(!v(a))continue;let e=a.data,o=e.source??h,s=g(o),c=e.promptTokens??0,l=e.completionTokens??0,u=e.totalTokens;n+=c,r+=l,i+=u;let d=t.get(s);d?(d.promptTokens+=c,d.completionTokens+=l,d.totalTokens+=u,d.turns+=1):t.set(s,{key:s,source:o,label:_(o),promptTokens:c,completionTokens:l,totalTokens:u,turns:1,percentage:0})}let a=[...t.values()].map(e=>({...e,percentage:y(e.totalTokens,i)})).sort((e,t)=>t.totalTokens-e.totalTokens);return{sessionId:e.id,totalTokens:i,promptTokens:n,completionTokens:r,bySource:a,...a[0]?{topConsumer:a[0]}:{}}}export{o as aggregateReports,a as analyzeSession,r as computeTimingIntervals,m as formatAggregateReport,p as formatSingleSession,c as formatUsageReport,e as gapMs,b as summarizeUsageBySource};
1
+ import{MODEL_PRICES as e,calculateModelCost as t}from"@robota-sdk/agent-core";function n(e,t){return new Date(t).getTime()-new Date(e).getTime()}function r(e){return new Date(e).toISOString()}function i(e,t,i,a,o,s){return{kind:e,fromType:t,toType:i,fromTimestamp:r(a),toTimestamp:r(o),durationMs:n(a,o),turnIndex:s}}function a(e){let t=[],n=0,r=0;for(;r<e.length;){let a=e[r];if(!a){r++;continue}if(a.category===`chat`&&a.type===`user`){n++;let o=a.timestamp,s=!1,c=null,l=r+1;for(;l<e.length;){let r=e[l];if(!r){l++;continue}if(r.category===`chat`&&r.type===`user`)break;if(r.category===`event`&&r.type===`tool-start`){s?c!==null&&t.push(i(`llm_between_tools`,`tool-end`,`tool-start`,c,r.timestamp,n)):(t.push(i(`user_to_first_tool`,`user`,`tool-start`,o,r.timestamp,n)),s=!0),c=null,l++;continue}if(r.category===`event`&&r.type===`tool-end`){let a=e[l-1];a&&a.category===`event`&&a.type===`tool-start`&&t.push(i(`tool_exec`,`tool-start`,`tool-end`,a.timestamp,r.timestamp,n)),c=r.timestamp,l++;continue}if(r.category===`event`&&r.type===`tool-summary`){c!==null&&t.push(i(`llm_final_response`,`tool-end`,`tool-summary`,c,r.timestamp,n)),l++;continue}if(r.category===`chat`&&r.type===`assistant`){s?c!==null&&t.push(i(`llm_final_response`,`tool-end`,`assistant`,c,r.timestamp,n)):t.push(i(`user_to_assistant`,`user`,`assistant`,o,r.timestamp,n)),l++;break}l++}r=l}else r++}return t}function o(e){let t=e.filter(e=>e.kind===`user_to_first_tool`||e.kind===`user_to_assistant`||e.kind===`llm_between_tools`||e.kind===`llm_final_response`),n=e.filter(e=>e.kind===`tool_exec`),r=e.filter(e=>e.kind===`user_to_assistant`),i=e=>e.length?Math.round(e.reduce((e,t)=>e+t.durationMs,0)/e.length):0,a=e=>e.length?Math.max(...e.map(e=>e.durationMs)):0;return{llmWaitMs:{avg:i(t),max:a(t),total:t.reduce((e,t)=>e+t.durationMs,0),count:t.length},toolExecMs:{avg:i(n),max:a(n),median:(e=>{if(!e.length)return 0;let t=[...e].sort((e,t)=>e.durationMs-t.durationMs),n=Math.floor(t.length/2);return t.length%2==0?Math.round(((t[n-1]?.durationMs??0)+(t[n]?.durationMs??0))/2):t[n]?.durationMs??0})(n),count:n.length},userToAssistantMs:{avg:i(r),max:a(r),count:r.length}}}function s(e){let t=a(e.history??[]),n=t.filter(e=>e.durationMs>=1e4);return{sessionId:e.id,cwd:e.cwd,createdAt:e.createdAt,totalIntervals:t.length,intervals:t,slowIntervals:n,stats:o(t)}}function c(e){let t=e.flatMap(e=>e.intervals.filter(e=>e.kind===`user_to_first_tool`||e.kind===`user_to_assistant`||e.kind===`llm_between_tools`||e.kind===`llm_final_response`)),n=e.flatMap(e=>e.intervals.filter(e=>e.kind===`tool_exec`)),r=t.length?Math.round(t.reduce((e,t)=>e+t.durationMs,0)/t.length):0,i=n.length?Math.round(n.reduce((e,t)=>e+t.durationMs,0)/n.length):0,a=0,o=``,s=0,c=``;for(let t of e)for(let e of t.intervals)e.durationMs>a&&(a=e.durationMs,o=t.sessionId,s=e.turnIndex,c=e.kind);let l=e.map(e=>e.createdAt).sort();return{sessionCount:e.length,fromDate:l[0]??``,toDate:l[l.length-1]??``,avgLlmResponseMs:r,avgToolExecMs:i,maxSingleDelayMs:a,maxSingleDelaySession:o,maxSingleDelayTurn:s,maxSingleDelayKind:c}}function l(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:`${e}`}function u(e,t){return`${t?``:`~`}$${e.toFixed(4)}`}function d(e){let t=[];if(t.push(`Token usage — session ${e.sessionId}`),t.push(` total ${l(e.totalTokens)} (prompt ${l(e.promptTokens)} · completion ${l(e.completionTokens)})`),t.push(` cost ${u(e.costUsd,e.costExact)}`),e.bySource.length===0)t.push(` (no usage recorded)`);else{t.push(` by source (most tokens first):`);for(let n of e.bySource)t.push(` ${n.label.padEnd(24)} ${String(n.percentage).padStart(5)}% ${l(n.totalTokens).padStart(7)} ${u(n.costUsd,n.costExact).padStart(10)} (${n.turns} turn${n.turns===1?``:`s`})`);e.topConsumer&&t.push(` top consumer: ${e.topConsumer.label} (${e.topConsumer.percentage}%)`)}if(e.timeline.length>0){t.push(` trace (per turn, sub-turn spans):`);for(let n of e.timeline){t.push(` turn ${n.turnIndex} — ${n.label} · ${f(n.totalDurationMs)} across ${n.spans.length} op${n.spans.length===1?``:`s`}`);for(let e of n.spans)t.push(` ${e.op.padEnd(22)} ${f(e.durationMs).padStart(8)}`)}}return t.join(`
2
+ `)}function f(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${e}ms`}function p(e){return new Date(e).toLocaleString(`sv-SE`,{timeZoneName:`short`}).slice(0,16)}function m(e){return{user_to_first_tool:`user → tool-start`,user_to_assistant:`user → assistant`,tool_exec:`tool-start → tool-end`,llm_between_tools:`tool-end → tool-start`,llm_final_response:`tool-end → assistant/summary`}[e.kind]??e.kind}function h(e){let{stats:t}=e,n=t.llmWaitMs.total+t.toolExecMs.avg*t.toolExecMs.count;if(n===0)return`Verdict: No timing data available.`;let r=Math.round(t.llmWaitMs.total/n*100);return`Verdict: LLM API wait ${r}% | Code processing ${100-r}%`}function g(e){let t=[],{stats:n}=e;if(t.push(`Session: ${e.sessionId}`),t.push(`Created: ${p(e.createdAt)} | cwd: ${e.cwd}`),t.push(``),t.push(`Timing Summary:`),n.llmWaitMs.count>0&&t.push(` LLM API wait ${f(n.llmWaitMs.avg).padStart(8)} avg | ${f(n.llmWaitMs.max).padStart(8)} max`),n.toolExecMs.count>0&&t.push(` Tool execution ${f(n.toolExecMs.avg).padStart(8)} avg | ${f(n.toolExecMs.median).padStart(8)} median`),n.userToAssistantMs.count>0&&t.push(` user → assistant ${f(n.userToAssistantMs.avg).padStart(8)} avg`),n.llmWaitMs.count===0&&n.toolExecMs.count===0&&t.push(` (no intervals computed — session may have no history)`),e.slowIntervals.length>0){t.push(``),t.push(`Slow intervals (>10s):`);for(let n of e.slowIntervals)t.push(` turn ${n.turnIndex} ${m(n).padEnd(32)} ${f(n.durationMs).padStart(8)}`)}return t.push(``),t.push(h(e)),t.join(`
3
+ `)}function ee(e){let t=[];return t.push(`Analyzed ${e.sessionCount} sessions (${p(e.fromDate)} ~ ${p(e.toDate)})`),t.push(` Avg LLM response: ${f(e.avgLlmResponseMs)}`),t.push(` Avg tool exec: ${f(e.avgToolExecMs)}`),e.maxSingleDelayMs>0&&t.push(` Max single delay: ${f(e.maxSingleDelayMs)} (${e.maxSingleDelaySession}, turn ${e.maxSingleDelayTurn}, ${e.maxSingleDelayKind})`),t.join(`
4
+ `)}const _={scope:`main`,label:`main thread`};function te(e){return`${e.scope}:${e.id??``}`}function v(e){return e.label??(e.id?`${e.scope} ${e.id}`:e.scope)}function ne(e){return e.type===`usage-summary`&&typeof e.data==`object`&&e.data!==null}function re(e){if(e.type!==`span`||typeof e.data!=`object`||e.data===null)return!1;let t=e.data;return typeof t.spanId==`string`&&typeof t.op==`string`&&typeof t.durationMs==`number`}function y(e,t){return t<=0?0:Math.round(e/t*1e3)/10}function b(e){let t=new Map,n=0,r=0,i=0,a=0,o=!0,s=[],c=[],l=e=>{c.length!==0&&(s.push({turnIndex:s.length,source:e,label:v(e),spans:c,totalDurationMs:c.reduce((e,t)=>e+t.durationMs,0)}),c=[])};for(let s of e.history??[]){if(re(s)){let e=s.data;c.push({spanId:e.spanId,op:e.op,durationMs:e.durationMs});continue}if(!ne(s))continue;let e=s.data,u=e.source??_,d=te(u),f=e.promptTokens??0,p=e.completionTokens??0,m=e.totalTokens,h=e.costUsd??0;n+=f,r+=p,i+=m,a+=h,o=!1;let g=t.get(d);g?(g.promptTokens+=f,g.completionTokens+=p,g.totalTokens+=m,g.turns+=1,g.costUsd+=h,g.costExact=!1):t.set(d,{key:d,source:u,label:v(u),promptTokens:f,completionTokens:p,totalTokens:m,turns:1,percentage:0,costUsd:h,costExact:!1}),l(u)}l(_);let u=[...t.values()].map(e=>({...e,percentage:y(e.totalTokens,i)})).sort((e,t)=>t.totalTokens-e.totalTokens);return{sessionId:e.id,totalTokens:i,promptTokens:n,completionTokens:r,costUsd:a,costExact:o,bySource:u,...u[0]?{topConsumer:u[0]}:{},timeline:s}}function x(e){return typeof e==`object`&&!!e&&!Array.isArray(e)&&!(e instanceof Date)}function S(e){return typeof e==`string`&&e.length>0?e:void 0}function C(e){return typeof e==`number`&&Number.isFinite(e)&&e>=0}function w(e){return e===`unknown`||e===`estimated`||e===`exact`}function ie(e){return(e.promptTokens===void 0||C(e.promptTokens))&&(e.completionTokens===void 0||C(e.completionTokens))}function T(e){let t=e.costStatus;return w(t)?t===`unknown`?e.costUsd===void 0:C(e.costUsd):!1}function E(e){return C(e.totalTokens)&&C(e.contextUsedTokens)&&C(e.contextMaxTokens)&&C(e.contextUsedPercentage)}function D(e){return e===`cli`||e===`desktop-app`||e===`browser`||e===`remote`||e===`unknown`}function O(e){if(!x(e))return;let t=e.scope;if(t!==`main`&&t!==`subagent`&&t!==`background`&&t!==`tool`&&t!==`command`&&t!==`skill`)return;let n=S(e.id),r=S(e.label);return{scope:t,...n?{id:n}:{},...r?{label:r}:{}}}function k(e){if(!x(e)||e.kind!==`exact`&&e.kind!==`estimated`||e.scope!==`turn`||!ie(e)||!T(e)||!E(e)||!w(e.costStatus))return;let t=O(e.source);return{kind:e.kind,scope:`turn`,totalTokens:e.totalTokens,contextUsedTokens:e.contextUsedTokens,contextMaxTokens:e.contextMaxTokens,contextUsedPercentage:e.contextUsedPercentage,costStatus:e.costStatus===`exact`?`estimated`:e.costStatus,...C(e.promptTokens)?{promptTokens:e.promptTokens}:{},...C(e.completionTokens)?{completionTokens:e.completionTokens}:{},...C(e.costUsd)?{costUsd:e.costUsd}:{},...t?{source:t}:{}}}function ae(e){if(!x(e))return;let t=S(e.providerId),n=S(e.modelId),{promptTokens:r,completionTokens:i,totalTokens:a,costStatus:o,costUsd:s}=e;if(!(!t||!n||!C(r)||!C(i)||!C(a)||o!==`unknown`&&o!==`estimated`||(o===`estimated`?!C(s):s!==void 0)))return{providerId:t,modelId:n,promptTokens:r,completionTokens:i,totalTokens:a,costStatus:o,...o===`estimated`&&C(s)?{costUsd:s}:{}}}function oe(e){if(!Array.isArray(e)||e.length===0)return;let t=e.map(ae);return t.every(e=>e!==void 0)?t:void 0}function A(e){if(!x(e))return;let t=S(e.usageObservationId),n=S(e.turnId),r=e.outcome;if(!t||!n||r!==`success`&&r!==`failure`&&r!==`interrupted`)return;let i=e.surface,a=O(e.source),o=k(e.usage),s=oe(e.modelShares);return{usageObservationId:t,turnId:n,outcome:r,...S(e.modelId)?{modelId:S(e.modelId)}:{},...S(e.providerId)?{providerId:S(e.providerId)}:{},...s?{modelShares:s}:{},...D(i)?{surface:i}:{},...a?{source:a}:{},...o?{usage:o}:{}}}function j(e){let t=new Date(e.timestamp);return Number.isNaN(t.getTime())?void 0:t}function M(e){let t=[],n=e.history??[],r=new Set,i=n.flatMap((e,t)=>{if(e.type!==`usage-summary`)return[];let n=k(e.data);return n?[{id:e.id,index:t,usage:n}]:[]});for(let[e,t]of n.entries()){if(t.type!==`usage-observation`)continue;let n=A(t.data);if(!n?.usage)continue;let a=JSON.stringify(n.usage),o=i.map((e,t)=>({candidate:e,index:t})).filter(({candidate:e})=>JSON.stringify(e.usage)===a).sort((t,n)=>Math.abs(t.candidate.index-e)-Math.abs(n.candidate.index-e))[0];o&&(r.add(o.candidate.id),i.splice(o.index,1))}for(let i of n){let n=j(i);if(!n)continue;let a=i.type===`usage-observation`?A(i.data):void 0;if(a){t.push({id:a.usageObservationId,sessionId:e.id,at:n,legacy:!1,observation:a});continue}if(i.type!==`usage-summary`||r.has(i.id))continue;let o=k(i.data);if(!o)continue;let s=`legacy:${e.id}:${i.id}`;t.push({id:s,sessionId:e.id,at:n,legacy:!0,observation:{usageObservationId:s,turnId:`legacy:${i.id}`,outcome:`success`,usage:o,...o.source?{source:o.source}:{},surface:`unknown`}})}return t}function se(e){let t=[];for(let n of e.history??[]){let r=n.data;if(n.type!==`tool-start`||!x(r))continue;let i=S(r.toolName),a=j(n);!i||!a||t.push({id:`tool:${e.id}:${n.id}`,sessionId:e.id,key:`tool:${i}`,label:i,kind:`tool`,at:a})}for(let n of e.skillActivationEvents??[]){if(n.status!==`started`)continue;let r=new Date(n.timestamp);Number.isNaN(r.getTime())||t.push({id:`activation:${e.id}:${n.timestamp}:${n.source}:${n.qualifiedName??n.skillName}:${n.invocation}`,sessionId:e.id,key:n.qualifiedName??`${n.source}:${n.skillName}`,label:n.qualifiedName??n.skillName,kind:n.source,at:r})}return t}function N(){return{sessions:new Set,observations:0,turns:0,promptTokens:0,completionTokens:0,totalTokens:0,costUsd:0,hasUnknownCost:!1,hasEstimatedCost:!1}}function P(e,t){let n=new Intl.DateTimeFormat(`en-CA`,{timeZone:t,year:`numeric`,month:`2-digit`,day:`2-digit`}).formatToParts(e),r=e=>n.find(t=>t.type===e)?.value??``;return`${r(`year`)}-${r(`month`)}-${r(`day`)}`}function ce(e,t,n){let[r,i,a]=P(e,t).split(`-`).map(Number),o=new Date(Date.UTC(r,i-1,a));return Array.from({length:n},(e,t)=>{let r=new Date(o);return r.setUTCDate(r.getUTCDate()-(n-t-1)),r.toISOString().slice(0,10)})}function F(e){let t=new Map;for(let n of e){let e=t.get(n.key);t.set(n.key,{item:n,count:(e?.count??0)+1})}return[...t.values()].map(({item:e,count:t})=>({key:e.key,label:e.label,kind:e.kind,count:t})).sort((e,t)=>t.count-e.count||e.key.localeCompare(t.key))}function I(e,t){let n=t.observation.usage;e.sessions.add(t.sessionId),e.observations+=1,!t.legacy&&t.observation.source?.scope!==`tool`&&(e.turns+=1),e.promptTokens+=n?.promptTokens??0,e.completionTokens+=n?.completionTokens??0,e.totalTokens+=n?.totalTokens??0,e.costUsd+=n?.costUsd??0,(!n||n.costStatus===`unknown`)&&(e.hasUnknownCost=!0),n?.costStatus===`estimated`&&(e.hasEstimatedCost=!0)}function L(e,t){let n=[];for(let r of e){let e=r.observation.modelShares;if(e===void 0){n.push(r);continue}let i=new Map;for(let n of e){let e=t(n),a=i.get(e)?.observation.usage,o=r.observation.usage,s=n.costStatus===`unknown`||a?.costStatus===`unknown`?`unknown`:`estimated`;i.set(e,{...r,observation:{...r.observation,providerId:n.providerId,modelId:n.modelId,usage:{kind:o?.kind??`estimated`,scope:`turn`,contextUsedTokens:o?.contextUsedTokens??0,contextMaxTokens:o?.contextMaxTokens??0,contextUsedPercentage:o?.contextUsedPercentage??0,promptTokens:(a?.promptTokens??0)+n.promptTokens,completionTokens:(a?.completionTokens??0)+n.completionTokens,totalTokens:(a?.totalTokens??0)+n.totalTokens,costStatus:s,...s===`estimated`&&{costUsd:(a?.costUsd??0)+(n.costUsd??0)}}}})}n.push(...i.values())}return z(n,e=>t(e.observation))}function R(e){return{sessions:e.sessions.size,turns:e.turns,promptTokens:e.promptTokens,completionTokens:e.completionTokens,totalTokens:e.totalTokens,costUsd:e.costUsd,costStatus:e.observations===0||e.hasUnknownCost?`unknown`:e.hasEstimatedCost?`estimated`:`exact`}}function z(e,t){let n=new Map;for(let r of e){let e=t(r),i=n.get(e)??N();I(i,r),n.set(e,i)}return[...n.entries()].map(([e,t])=>({key:e,label:e,frozen:R(t),sessionIds:[...t.sessions].sort()})).map(({key:e,label:t,frozen:n,sessionIds:r})=>({key:e,label:t,turns:n.turns,promptTokens:n.promptTokens,completionTokens:n.completionTokens,totalTokens:n.totalTokens,costUsd:n.costUsd,costStatus:n.costStatus,sessionIds:r})).sort((e,t)=>t.totalTokens-e.totalTokens||e.key.localeCompare(t.key))}function B(e){return e?e.id?`${e.scope}:${e.id}`:e.scope:`unknown`}function V(e,t,n){return e.getTime()<=t.now.getTime()&&n.has(P(e,t.request.timezone))}function H(e,t){let n=[],r=[],i=new Set,a=new Set,o=0,s=0;for(let c of e.records){for(let r of M(c))if(V(r.at,e,t)){if(i.has(r.id)){o+=1;continue}i.add(r.id),n.push(r),r.legacy&&(s+=1)}for(let n of se(c))!V(n.at,e,t)||a.has(n.id)||(a.add(n.id),r.push(n))}return{observations:n,activities:r,duplicateObservations:o,legacyObservations:s}}function U(e,t,n){let r=N(),i=new Map(t.map(e=>[e,N()]));for(let t of n.observations)I(r,t),I(i.get(P(t.at,e.request.timezone)),t);for(let t of n.activities)r.sessions.add(t.sessionId),i.get(P(t.at,e.request.timezone))?.sessions.add(t.sessionId);return{totals:r,byDay:i}}function W(e){let t=e.request.period===`30d`?30:7,n=ce(e.now,e.request.timezone,t),r=H(e,new Set(n)),{totals:i,byDay:a}=U(e,n,r),o=r.observations;return{schemaVersion:1,generatedAt:e.now.toISOString(),period:e.request.period,timezone:e.request.timezone,interval:{startDate:n[0],endDate:n.at(-1)},totals:R(i),daily:n.map((e,t)=>({date:e,partial:t===n.length-1,totals:R(a.get(e)),sessionIds:[...a.get(e).sessions].sort()})),byModel:L(o,e=>e.modelId??`unknown`),byProvider:L(o,e=>e.providerId??`unknown`),bySurface:z(o,e=>e.observation.surface??`unknown`),bySource:z(o,e=>B(e.observation.source)),byActivity:F(r.activities),sessionIds:[...i.sessions].sort(),coverage:{validSessions:e.records.length,corruptSessions:e.corruptSessionIds?.length??0,unsupportedSessions:e.unsupportedSessionIds?.length??0,duplicateObservations:r.duplicateObservations,legacyObservations:r.legacyObservations,unknownModelObservations:o.filter(e=>!e.observation.modelId&&!e.observation.modelShares).length,unknownProviderObservations:o.filter(e=>!e.observation.providerId&&!e.observation.modelShares).length,unknownSurfaceObservations:o.filter(e=>!e.observation.surface||e.observation.surface===`unknown`).length,corruptSessionIds:[...e.corruptSessionIds??[]].sort(),unsupportedSessionIds:[...e.unsupportedSessionIds??[]].sort()}}}const le=(1n<<64n)-1n,G=/^(?!0{32}$)[0-9a-f]{32}$/,K=/^(?!0{16}$)[0-9a-f]{16}$/,ue=[`promptExecutionStartedAt`,`promptExecutionEndedAt`,`promptExecutionOutcome`,`promptExecutionTraceId`,`promptExecutionSpanId`];function q(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function J(e,t){e.set(t,(e.get(t)??0)+1)}function Y(e){if(typeof e!=`string`)return;let t=new Date(e);if(Number.isNaN(t.getTime())||t.toISOString()!==e)return;let n=BigInt(t.getTime())*1000000n;return n>=0n&&n<=le?n:void 0}function de(e){let t=Y(e.promptExecutionStartedAt),n=Y(e.promptExecutionEndedAt),r=e.promptExecutionOutcome,i=e.promptExecutionTraceId,a=e.promptExecutionSpanId;if(!(t===void 0||n===void 0||t>n||r!==`success`&&r!==`failure`&&r!==`interrupted`||typeof i!=`string`||!G.test(i)||typeof a!=`string`||!K.test(a)||typeof e.usageObservationId!=`string`||e.usageObservationId.length===0||e.turnId!==e.usageObservationId||e.outcome!==`success`&&e.outcome!==`failure`&&e.outcome!==`interrupted`))return{traceId:i,spanId:a,name:`robota.prompt_execution`,kind:1,startTimeUnixNano:String(t),endTimeUnixNano:String(n),attributes:[{key:`robota.prompt.outcome`,value:{stringValue:r}}],status:{code:r===`success`?1:r===`failure`?2:0}}}function fe(e,t){if(!q(e))return;let n=e.traceId,r=e.parentSpanId,i=e.spanId,a=Y(e.startedAt),o=Y(e.endedAt),s=e.outcome;if(!(typeof n!=`string`||!G.test(n)||typeof r!=`string`||!K.test(r)||typeof i!=`string`||!K.test(i)||a===void 0||o===void 0||a>o||s!==`success`&&s!==`failure`&&s!==`interrupted`||t===`provider`&&(!Number.isSafeInteger(e.round)||e.round<1)))return{traceId:n,parentSpanId:r,spanId:i,name:t===`provider`?`robota.provider_call`:`robota.tool_body`,kind:1,startTimeUnixNano:String(a),endTimeUnixNano:String(o),attributes:[{key:t===`provider`?`robota.provider.outcome`:`robota.tool.outcome`,value:{stringValue:s}}],status:{code:s===`success`?1:s===`failure`?2:0}}}function pe(e){if(!q(e))return;let t=e.traceId,n=e.spanId;return typeof t==`string`&&G.test(t)&&typeof n==`string`&&K.test(n)?`${t}:${n}`:void 0}function X(e){return typeof e==`number`&&Number.isSafeInteger(e)&&e>=0}function me(n,r,i,a){if(!q(r)||r.disposition===void 0)return i.legacy+=1,n;if(r.disposition!==`invoked`)return i.invalid+=1,n;a.invoked+=1;let o=r.usageProvenance;if(o===`partial`)return i.partial+=1,a.unknownCost+=1,n;if(o===`absent`||o===void 0)return i.absent+=1,a.unknownCost+=1,n;let s=r.promptTokens,c=r.completionTokens,l=r.totalTokens;if(o!==`complete`||!X(s)||!X(c)||!Number.isSafeInteger(s+c)||l!==s+c)return i.invalid+=1,a.unknownCost+=1,n;a.completeUsage+=1,a.inputTokens+=s,a.outputTokens+=c;let u=[...n.attributes,{key:`robota.provider.usage.input_tokens`,value:{intValue:String(s)}},{key:`robota.provider.usage.output_tokens`,value:{intValue:String(c)}}],d=r.modelId,f=typeof d==`string`&&d.length>0&&d.length<=128&&[...d].every(e=>e.charCodeAt(0)>=32)?t(d,s,c):void 0;return f===void 0||!Number.isFinite(f)||f<0?a.unknownCost+=1:(a.estimatedCostUsd+=f,typeof d==`string`&&Object.hasOwn(e,d)?a.exactPriceMatches+=1:a.familyPriceMatches+=1,u.push({key:`robota.provider.cost.usd.estimated`,value:{doubleValue:f}})),{...n,attributes:u}}function Z(e,t){let n=[],r=new Map,i=new Map;for(let t of e)for(let e of t.history??[]){if(e.type!==`usage-observation`)continue;let a=e.data,o=q(a)?a.usageObservationId:void 0,s=q(a)?a.promptExecutionTraceId:void 0,c=typeof o==`string`&&o.length>0?JSON.stringify([t.id,o]):void 0,l=typeof s==`string`&&G.test(s)?s:void 0;c&&J(r,c),l&&J(i,l),n.push({record:t,data:a,observationKey:c,traceId:l})}let a=[],o=new Map,s=0,c=0,l=0;for(let e of n){if(e.observationKey&&(r.get(e.observationKey)??0)>1||e.traceId&&(i.get(e.traceId)??0)>1){l+=1;continue}let t=e.data;if(!q(t)){c+=1;continue}if(ue.every(e=>t[e]===void 0)){s+=1;continue}let n=de(t);if(!n){c+=1;continue}a.push(n);let u=o.get(e.record)??new Map;u.set(n.traceId,n),o.set(e.record,u)}let u=[],d=new Map;for(let t of e)for(let e of t.history??[]){let n=e.type===`provider-call-trace`?`provider`:e.type===`tool-body-trace`?`tool`:void 0;if(!n)continue;let r=pe(e.data),i=fe(e.data,n);r&&J(d,r),u.push({record:t,span:i,identity:r,kind:n,data:e.data})}let f={provider:{exported:0,invalid:0,orphaned:0,duplicate:0},tool:{exported:0,invalid:0,orphaned:0,duplicate:0}},p={legacy:0,invalid:0,partial:0,absent:0,nonInvoked:0},m={invoked:0,completeUsage:0,inputTokens:0,outputTokens:0,estimatedCostUsd:0,unknownCost:0,exactPriceMatches:0,familyPriceMatches:0};for(let e of u){let{span:t}=e,n=f[e.kind];if(e.identity&&(d.get(e.identity)??0)>1){n.duplicate+=1;continue}if(!t){n.invalid+=1;continue}let r=o.get(e.record)?.get(t.traceId);if(!r||t.parentSpanId!==r.spanId||t.spanId===r.spanId){n.orphaned+=1;continue}if(BigInt(t.startTimeUnixNano)<BigInt(r.startTimeUnixNano)||BigInt(t.endTimeUnixNano)>BigInt(r.endTimeUnixNano)){n.invalid+=1;continue}if(e.kind===`provider`&&q(e.data)&&(e.data.disposition===`cache-hit`||e.data.disposition===`preflight-refused`)){p.nonInvoked+=1,(e.data.promptTokens!==void 0||e.data.completionTokens!==void 0||e.data.totalTokens!==void 0||e.data.usageProvenance===`complete`)&&(p.invalid+=1);continue}a.push(e.kind===`provider`?me(t,e.data,p,m):t),n.exported+=1}return{payload:{resourceSpans:a.length?[{resource:{attributes:[{key:`service.name`,value:{stringValue:`robota`}},{key:`service.version`,value:{stringValue:t}}]},scopeSpans:[{scope:{name:`@robota-sdk/agent-session-analytics`},spans:a}]}]:[]},coverage:{exported:a.length-f.provider.exported-f.tool.exported,missing:s,invalid:c,duplicate:l,providerChildren:f.provider,providerUsage:p,toolChildren:f.tool},callMetrics:m}}function Q(e,t,n){let r=new Set,i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0;for(let t of e)for(let e of M(t)){if(r.has(e.id))continue;r.add(e.id),e.legacy||(i+=1);let t=e.observation.usage;if(!t){u+=1,c+=1;continue}a+=t.totalTokens,o+=t.promptTokens??0,s+=t.completionTokens??0,(t.promptTokens===void 0||t.completionTokens===void 0)&&(c+=1),t.costStatus===`unknown`||t.costUsd===void 0?u+=1:(l+=t.costUsd,t.costStatus===`estimated`&&(d+=1))}let f=Z(e,n),p=f.callMetrics,m=String(BigInt(t.getTime())*1000000n),h=(e,t,n)=>({name:e,unit:t,gauge:{dataPoints:[{timeUnixNano:m,asDouble:n}]}});return{resourceMetrics:[{resource:{attributes:[{key:`service.name`,value:{stringValue:`robota`}},{key:`service.version`,value:{stringValue:n}}]},scopeMetrics:[{scope:{name:`@robota-sdk/agent-session-analytics`},metrics:[h(`robota.session.count`,`{session}`,e.length),h(`robota.turn.count`,`{turn}`,i),h(`robota.token.total`,`{token}`,a),h(`robota.token.input.known`,`{token}`,o),h(`robota.token.output.known`,`{token}`,s),h(`robota.token.split_unknown_observations`,`{observation}`,c),h(`robota.cost.usd.known`,`USD`,l),h(`robota.cost.unknown_observations`,`{observation}`,u),h(`robota.cost.estimated_observations`,`{observation}`,d),h(`robota.provider_call.count`,`{call}`,p.invoked),h(`robota.provider_call.usage.complete_count`,`{call}`,p.completeUsage),h(`robota.provider_call.token.input.known`,`{token}`,p.inputTokens),h(`robota.provider_call.token.output.known`,`{token}`,p.outputTokens),h(`robota.provider_call.cost.usd.estimated`,`USD`,p.estimatedCostUsd),h(`robota.provider_call.cost.unknown_count`,`{call}`,p.unknownCost),h(`robota.provider_call.usage.partial_count`,`{call}`,f.coverage.providerUsage.partial),h(`robota.provider_call.usage.invalid_count`,`{call}`,f.coverage.providerUsage.invalid),h(`robota.provider_call.usage.legacy_count`,`{call}`,f.coverage.providerUsage.legacy),h(`robota.provider_call.price.exact_id_count`,`{call}`,p.exactPriceMatches),h(`robota.provider_call.price.family_fallback_count`,`{call}`,p.familyPriceMatches)]}]}]}}const he=(1n<<64n)-1n;function ge(e){let t=e.getTime();if(!Number.isSafeInteger(t)||t<0)throw RangeError(`OTLP log observation time must be a valid post-epoch date.`);let n=BigInt(t)*1000000n;if(n>he)throw RangeError(`OTLP log observation time is out of range.`);return String(n)}function _e(e,t,n){let r=Z(e,t),i=r.payload.resourceSpans[0]?.scopeSpans[0]?.spans??[];if(i.length===0)return{exported:0,coverage:r.coverage,payload:{resourceLogs:[]}};let a=ge(n),o=i.map(e=>{let t=e.attributes.find(e=>e.key===`robota.prompt.outcome`||e.key===`robota.provider.outcome`||e.key===`robota.tool.outcome`),n=e.name===`robota.prompt_execution`?`robota.prompt_execution.completed`:e.name===`robota.provider_call`?`robota.provider_call.completed`:`robota.tool_body.completed`,r=e.status.code===1?9:e.status.code===2?17:13,i=r===9?`INFO`:r===17?`ERROR`:`WARN`;return{timeUnixNano:e.endTimeUnixNano,observedTimeUnixNano:a,severityNumber:r,severityText:i,eventName:n,traceId:e.traceId,spanId:e.spanId,attributes:t&&`stringValue`in t.value?[{key:t.key,value:{stringValue:t.value.stringValue}}]:[]}});return{exported:o.length,coverage:r.coverage,payload:{resourceLogs:[{resource:r.payload.resourceSpans[0].resource,scopeLogs:[{scope:{name:`@robota-sdk/agent-session-analytics`},logRecords:o}]}]}}}function $(e,t){return[``,e,...t.length===0?[`(none)`]:t.map(e=>`${e.label} ${e.turns} turns ${e.totalTokens} tokens cost ${e.costStatus===`unknown`?`unknown`:`$${e.costUsd.toFixed(4)}`}`)]}function ve(e){let t=e.totals.costStatus===`unknown`?`unknown`:`$${e.totals.costUsd.toFixed(4)} (${e.totals.costStatus})`;return[`Personal usage — ${e.period} (${e.timezone})`,`${e.interval.startDate} → ${e.interval.endDate}`,`Sessions ${e.totals.sessions} · Turns ${e.totals.turns} · Tokens ${e.totals.totalTokens} (${e.totals.promptTokens} input / ${e.totals.completionTokens} output) · Cost ${t}`,``,`Daily usage`,...e.daily.map(e=>`${e.date}${e.partial?` (partial)`:``} ${e.totals.turns} turns ${e.totals.totalTokens} tokens`),...$(`By model`,e.byModel),...$(`By provider`,e.byProvider),...$(`By surface`,e.bySurface),...$(`By source`,e.bySource),``,`Activity`,...e.byActivity.length===0?[`(none)`]:e.byActivity.map(e=>`${e.kind}:${e.label} ${e.count} calls`),``,`Coverage: legacy ${e.coverage.legacyObservations}, corrupt ${e.coverage.corruptSessions}, unsupported ${e.coverage.unsupportedSessions}, duplicates ${e.coverage.duplicateObservations}`,`Local estimates from recorded sessions; costs are not an invoice.`].join(`
5
+ `)}export{c as aggregateReports,s as analyzeSession,a as computeTimingIntervals,_e as createOtlpPromptEvents,Z as createOtlpPromptRootTraces,Q as createOtlpUsageSnapshot,ee as formatAggregateReport,ve as formatPersonalUsageReport,g as formatSingleSession,d as formatUsageReport,n as gapMs,W as summarizePersonalUsage,b as summarizeUsageBySource};
6
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/analyze.ts","../../src/report.ts","../../src/usage.ts"],"sourcesContent":["/**\n * Session-log timing-interval computation and aggregation.\n *\n * Operates on canonical history entries (`IHistoryEntry`); persisted timestamps arrive as ISO\n * strings at runtime (JSON has no Date), so all timestamp math goes through `new Date(...)`.\n */\n\nimport type {\n IAggregateReport,\n TSessionAnalysisInput,\n ISessionTimingReport,\n ITimingInterval,\n ITimingStats,\n TIntervalKind,\n} from './types.js';\nimport type { IHistoryEntry } from '@robota-sdk/agent-core';\n\nconst SLOW_THRESHOLD_MS = 10_000;\n\n/** Gap in milliseconds between two timestamps (ISO string or Date). */\nexport function gapMs(from: string | Date, to: string | Date): number {\n return new Date(to).getTime() - new Date(from).getTime();\n}\n\nfunction toIso(value: string | Date): string {\n return new Date(value).toISOString();\n}\n\nfunction makeInterval(\n kind: TIntervalKind,\n fromType: string,\n toType: string,\n fromTimestamp: string | Date,\n toTimestamp: string | Date,\n turnIndex: number,\n): ITimingInterval {\n return {\n kind,\n fromType,\n toType,\n fromTimestamp: toIso(fromTimestamp),\n toTimestamp: toIso(toTimestamp),\n durationMs: gapMs(fromTimestamp, toTimestamp),\n turnIndex,\n };\n}\n\n/**\n * Compute timing intervals from a history entry array.\n *\n * Strategy: scan the timeline turn by turn. A \"turn\" starts with a user message. After user, we\n * expect zero or more tool-start → tool-end cycles, optionally a tool-summary, then an assistant\n * message.\n */\nexport function computeTimingIntervals(history: readonly IHistoryEntry[]): ITimingInterval[] {\n const intervals: ITimingInterval[] = [];\n let turnIndex = 0;\n\n let i = 0;\n while (i < history.length) {\n const entry = history[i];\n if (!entry) {\n i++;\n continue;\n }\n\n if (entry.category === 'chat' && entry.type === 'user') {\n turnIndex++;\n const userTs = entry.timestamp;\n let foundFirstTool = false;\n let lastToolEndTs: IHistoryEntry['timestamp'] | null = null;\n let j = i + 1;\n\n while (j < history.length) {\n const next = history[j];\n if (!next) {\n j++;\n continue;\n }\n\n if (next.category === 'chat' && next.type === 'user') {\n break;\n }\n\n if (next.category === 'event' && next.type === 'tool-start') {\n if (!foundFirstTool) {\n intervals.push(\n makeInterval(\n 'user_to_first_tool',\n 'user',\n 'tool-start',\n userTs,\n next.timestamp,\n turnIndex,\n ),\n );\n foundFirstTool = true;\n } else if (lastToolEndTs !== null) {\n intervals.push(\n makeInterval(\n 'llm_between_tools',\n 'tool-end',\n 'tool-start',\n lastToolEndTs,\n next.timestamp,\n turnIndex,\n ),\n );\n }\n lastToolEndTs = null;\n j++;\n continue;\n }\n\n if (next.category === 'event' && next.type === 'tool-end') {\n const prev = history[j - 1];\n if (prev && prev.category === 'event' && prev.type === 'tool-start') {\n intervals.push(\n makeInterval(\n 'tool_exec',\n 'tool-start',\n 'tool-end',\n prev.timestamp,\n next.timestamp,\n turnIndex,\n ),\n );\n }\n lastToolEndTs = next.timestamp;\n j++;\n continue;\n }\n\n if (next.category === 'event' && next.type === 'tool-summary') {\n if (lastToolEndTs !== null) {\n intervals.push(\n makeInterval(\n 'llm_final_response',\n 'tool-end',\n 'tool-summary',\n lastToolEndTs,\n next.timestamp,\n turnIndex,\n ),\n );\n }\n j++;\n continue;\n }\n\n if (next.category === 'chat' && next.type === 'assistant') {\n if (!foundFirstTool) {\n intervals.push(\n makeInterval(\n 'user_to_assistant',\n 'user',\n 'assistant',\n userTs,\n next.timestamp,\n turnIndex,\n ),\n );\n } else if (lastToolEndTs !== null) {\n intervals.push(\n makeInterval(\n 'llm_final_response',\n 'tool-end',\n 'assistant',\n lastToolEndTs,\n next.timestamp,\n turnIndex,\n ),\n );\n }\n j++;\n break;\n }\n\n j++;\n }\n\n i = j;\n } else {\n i++;\n }\n }\n\n return intervals;\n}\n\nfunction computeStats(intervals: ITimingInterval[]): ITimingStats {\n const llm = intervals.filter(\n (iv) =>\n iv.kind === 'user_to_first_tool' ||\n iv.kind === 'user_to_assistant' ||\n iv.kind === 'llm_between_tools' ||\n iv.kind === 'llm_final_response',\n );\n const tool = intervals.filter((iv) => iv.kind === 'tool_exec');\n const direct = intervals.filter((iv) => iv.kind === 'user_to_assistant');\n\n const avg = (arr: ITimingInterval[]): number =>\n arr.length ? Math.round(arr.reduce((s, iv) => s + iv.durationMs, 0) / arr.length) : 0;\n const max = (arr: ITimingInterval[]): number =>\n arr.length ? Math.max(...arr.map((iv) => iv.durationMs)) : 0;\n const median = (arr: ITimingInterval[]): number => {\n if (!arr.length) return 0;\n const sorted = [...arr].sort((a, b) => a.durationMs - b.durationMs);\n const mid = Math.floor(sorted.length / 2);\n return sorted.length % 2 === 0\n ? Math.round(((sorted[mid - 1]?.durationMs ?? 0) + (sorted[mid]?.durationMs ?? 0)) / 2)\n : (sorted[mid]?.durationMs ?? 0);\n };\n\n return {\n llmWaitMs: {\n avg: avg(llm),\n max: max(llm),\n total: llm.reduce((s, iv) => s + iv.durationMs, 0),\n count: llm.length,\n },\n toolExecMs: { avg: avg(tool), max: max(tool), median: median(tool), count: tool.length },\n userToAssistantMs: { avg: avg(direct), max: max(direct), count: direct.length },\n };\n}\n\n/** Compute the timing report for a single session record. */\nexport function analyzeSession(record: TSessionAnalysisInput): ISessionTimingReport {\n const history = record.history ?? [];\n const intervals = computeTimingIntervals(history);\n const slowIntervals = intervals.filter((iv) => iv.durationMs >= SLOW_THRESHOLD_MS);\n\n return {\n sessionId: record.id,\n cwd: record.cwd,\n createdAt: record.createdAt,\n totalIntervals: intervals.length,\n intervals,\n slowIntervals,\n stats: computeStats(intervals),\n };\n}\n\n/** Aggregate multiple single-session reports into a fleet-level summary. */\nexport function aggregateReports(reports: readonly ISessionTimingReport[]): IAggregateReport {\n const llmIntervals = reports.flatMap((r) =>\n r.intervals.filter(\n (iv) =>\n iv.kind === 'user_to_first_tool' ||\n iv.kind === 'user_to_assistant' ||\n iv.kind === 'llm_between_tools' ||\n iv.kind === 'llm_final_response',\n ),\n );\n const toolIntervals = reports.flatMap((r) => r.intervals.filter((iv) => iv.kind === 'tool_exec'));\n\n const avgLlmResponseMs = llmIntervals.length\n ? Math.round(llmIntervals.reduce((s, iv) => s + iv.durationMs, 0) / llmIntervals.length)\n : 0;\n const avgToolExecMs = toolIntervals.length\n ? Math.round(toolIntervals.reduce((s, iv) => s + iv.durationMs, 0) / toolIntervals.length)\n : 0;\n\n let maxSingleDelayMs = 0;\n let maxSingleDelaySession = '';\n let maxSingleDelayTurn = 0;\n let maxSingleDelayKind = '';\n\n for (const r of reports) {\n for (const iv of r.intervals) {\n if (iv.durationMs > maxSingleDelayMs) {\n maxSingleDelayMs = iv.durationMs;\n maxSingleDelaySession = r.sessionId;\n maxSingleDelayTurn = iv.turnIndex;\n maxSingleDelayKind = iv.kind;\n }\n }\n }\n\n const dates = reports.map((r) => r.createdAt).sort();\n\n return {\n sessionCount: reports.length,\n fromDate: dates[0] ?? '',\n toDate: dates[dates.length - 1] ?? '',\n avgLlmResponseMs,\n avgToolExecMs,\n maxSingleDelayMs,\n maxSingleDelaySession,\n maxSingleDelayTurn,\n maxSingleDelayKind,\n };\n}\n","/**\n * Text report formatters for session timing analysis. Pure — every function returns a string;\n * callers (e.g. a CLI command) own writing to stdout.\n */\n\nimport type { IAggregateReport, ISessionTimingReport, ITimingInterval } from './types.js';\nimport type { IUsageBySourceReport } from './usage.js';\n\nfunction fmtTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n return `${n}`;\n}\n\n/**\n * ANALYTICS-001: render a per-source token-usage breakdown — which part of the session burned the\n * most tokens. Pure; the caller owns writing it.\n */\nexport function formatUsageReport(report: IUsageBySourceReport): string {\n const lines: string[] = [];\n lines.push(`Token usage — session ${report.sessionId}`);\n lines.push(\n ` total ${fmtTokens(report.totalTokens)} (prompt ${fmtTokens(report.promptTokens)} · completion ${fmtTokens(report.completionTokens)})`,\n );\n if (report.bySource.length === 0) {\n lines.push(' (no usage recorded)');\n return lines.join('\\n');\n }\n lines.push(' by source (most tokens first):');\n for (const s of report.bySource) {\n lines.push(\n ` ${s.label.padEnd(24)} ${String(s.percentage).padStart(5)}% ${fmtTokens(s.totalTokens).padStart(7)} (${s.turns} turn${s.turns === 1 ? '' : 's'})`,\n );\n }\n if (report.topConsumer) {\n lines.push(` top consumer: ${report.topConsumer.label} (${report.topConsumer.percentage}%)`);\n }\n return lines.join('\\n');\n}\n\nfunction fmtMs(ms: number): string {\n if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;\n return `${ms}ms`;\n}\n\nfunction fmtDate(iso: string): string {\n return new Date(iso).toLocaleString('sv-SE', { timeZoneName: 'short' }).slice(0, 16);\n}\n\nfunction intervalLabel(iv: ITimingInterval): string {\n const map: Record<string, string> = {\n user_to_first_tool: 'user → tool-start',\n user_to_assistant: 'user → assistant',\n tool_exec: 'tool-start → tool-end',\n llm_between_tools: 'tool-end → tool-start',\n llm_final_response: 'tool-end → assistant/summary',\n };\n return map[iv.kind] ?? iv.kind;\n}\n\nfunction verdictLine(report: ISessionTimingReport): string {\n const { stats } = report;\n const totalMs = stats.llmWaitMs.total + stats.toolExecMs.avg * stats.toolExecMs.count;\n if (totalMs === 0) return 'Verdict: No timing data available.';\n const llmPct = Math.round((stats.llmWaitMs.total / totalMs) * 100);\n const toolPct = 100 - llmPct;\n return `Verdict: LLM API wait ${llmPct}% | Code processing ${toolPct}%`;\n}\n\nexport function formatSingleSession(report: ISessionTimingReport): string {\n const lines: string[] = [];\n const { stats } = report;\n\n lines.push(`Session: ${report.sessionId}`);\n lines.push(`Created: ${fmtDate(report.createdAt)} | cwd: ${report.cwd}`);\n lines.push('');\n lines.push('Timing Summary:');\n\n if (stats.llmWaitMs.count > 0) {\n lines.push(\n ` LLM API wait ${fmtMs(stats.llmWaitMs.avg).padStart(8)} avg | ${fmtMs(stats.llmWaitMs.max).padStart(8)} max`,\n );\n }\n if (stats.toolExecMs.count > 0) {\n lines.push(\n ` Tool execution ${fmtMs(stats.toolExecMs.avg).padStart(8)} avg | ${fmtMs(stats.toolExecMs.median).padStart(8)} median`,\n );\n }\n if (stats.userToAssistantMs.count > 0) {\n lines.push(` user → assistant ${fmtMs(stats.userToAssistantMs.avg).padStart(8)} avg`);\n }\n if (stats.llmWaitMs.count === 0 && stats.toolExecMs.count === 0) {\n lines.push(' (no intervals computed — session may have no history)');\n }\n\n if (report.slowIntervals.length > 0) {\n lines.push('');\n lines.push('Slow intervals (>10s):');\n for (const iv of report.slowIntervals) {\n lines.push(\n ` turn ${iv.turnIndex} ${intervalLabel(iv).padEnd(32)} ${fmtMs(iv.durationMs).padStart(8)}`,\n );\n }\n }\n\n lines.push('');\n lines.push(verdictLine(report));\n\n return lines.join('\\n');\n}\n\nexport function formatAggregateReport(aggregate: IAggregateReport): string {\n const lines: string[] = [];\n lines.push(\n `Analyzed ${aggregate.sessionCount} sessions (${fmtDate(aggregate.fromDate)} ~ ${fmtDate(aggregate.toDate)})`,\n );\n lines.push(` Avg LLM response: ${fmtMs(aggregate.avgLlmResponseMs)}`);\n lines.push(` Avg tool exec: ${fmtMs(aggregate.avgToolExecMs)}`);\n if (aggregate.maxSingleDelayMs > 0) {\n lines.push(\n ` Max single delay: ${fmtMs(aggregate.maxSingleDelayMs)} (${aggregate.maxSingleDelaySession}, turn ${aggregate.maxSingleDelayTurn}, ${aggregate.maxSingleDelayKind})`,\n );\n }\n return lines.join('\\n');\n}\n","/**\n * ANALYTICS-001: source-attributed token-usage analysis over a session record.\n *\n * The main session history records one `usage-summary` history entry per turn (an `IUsageSnapshot`,\n * created in agent-framework). This reducer aggregates those snapshots into a per-source breakdown —\n * answering \"which part of the session burned the most tokens?\" — so it can be reported to the user\n * and asserted by the testing framework (budget gates). Pure: no I/O, no process concerns.\n */\n\nimport type { IHistoryEntry } from '@robota-sdk/agent-core';\nimport type {\n IInteractiveSessionRecord,\n IUsageSnapshot,\n IUsageSource,\n} from '@robota-sdk/agent-interface-transport';\n\n/** The `type` of the per-turn usage history entry (agent-framework `createUsageSummaryEntry`). */\nconst USAGE_SUMMARY_ENTRY_TYPE = 'usage-summary';\n\n/** SSOT-derived projection the usage reducer reads. */\nexport type TUsageAnalysisInput = Pick<IInteractiveSessionRecord, 'id' | 'history'>;\n\n/** The main thread is the implicit source when a usage snapshot carries none. */\nconst MAIN_THREAD_SOURCE: IUsageSource = { scope: 'main', label: 'main thread' };\n\nexport interface IUsageSourceTotals {\n /** Stable grouping key (`<scope>:<id>`). */\n key: string;\n source: IUsageSource;\n label: string;\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n /** How many usage snapshots (turns) were attributed to this source. */\n turns: number;\n /** Share of the session's total tokens, 0–100 (rounded to 1 decimal). */\n percentage: number;\n}\n\nexport interface IUsageBySourceReport {\n sessionId: string;\n totalTokens: number;\n promptTokens: number;\n completionTokens: number;\n /** Per-source totals, sorted by `totalTokens` descending. */\n bySource: IUsageSourceTotals[];\n /** The single biggest token consumer, if any usage was recorded. */\n topConsumer?: IUsageSourceTotals;\n}\n\nfunction sourceKey(source: IUsageSource): string {\n return `${source.scope}:${source.id ?? ''}`;\n}\n\nfunction sourceLabel(source: IUsageSource): string {\n return source.label ?? (source.id ? `${source.scope} ${source.id}` : source.scope);\n}\n\nfunction isUsageSummaryEntry(entry: IHistoryEntry): entry is IHistoryEntry<IUsageSnapshot> {\n return (\n entry.type === USAGE_SUMMARY_ENTRY_TYPE && typeof entry.data === 'object' && entry.data !== null\n );\n}\n\nfunction roundPercentage(part: number, whole: number): number {\n if (whole <= 0) return 0;\n return Math.round((part / whole) * 1000) / 10;\n}\n\n/**\n * Aggregate a session's `usage-summary` entries into a per-source token breakdown.\n *\n * Usage snapshots with no `source` are attributed to the main thread. Sources are grouped by\n * `<scope>:<id>`; the result is sorted by total tokens descending with the top consumer surfaced.\n */\nexport function summarizeUsageBySource(input: TUsageAnalysisInput): IUsageBySourceReport {\n const totalsByKey = new Map<string, IUsageSourceTotals>();\n let sessionPrompt = 0;\n let sessionCompletion = 0;\n let sessionTotal = 0;\n\n for (const entry of input.history ?? []) {\n if (!isUsageSummaryEntry(entry)) continue;\n const snapshot = entry.data as IUsageSnapshot;\n const source = snapshot.source ?? MAIN_THREAD_SOURCE;\n const key = sourceKey(source);\n const prompt = snapshot.promptTokens ?? 0;\n const completion = snapshot.completionTokens ?? 0;\n const total = snapshot.totalTokens;\n\n sessionPrompt += prompt;\n sessionCompletion += completion;\n sessionTotal += total;\n\n const existing = totalsByKey.get(key);\n if (existing) {\n existing.promptTokens += prompt;\n existing.completionTokens += completion;\n existing.totalTokens += total;\n existing.turns += 1;\n } else {\n totalsByKey.set(key, {\n key,\n source,\n label: sourceLabel(source),\n promptTokens: prompt,\n completionTokens: completion,\n totalTokens: total,\n turns: 1,\n percentage: 0,\n });\n }\n }\n\n const bySource = [...totalsByKey.values()]\n .map((totals) => ({ ...totals, percentage: roundPercentage(totals.totalTokens, sessionTotal) }))\n .sort((left, right) => right.totalTokens - left.totalTokens);\n\n return {\n sessionId: input.id,\n totalTokens: sessionTotal,\n promptTokens: sessionPrompt,\n completionTokens: sessionCompletion,\n bySource,\n ...(bySource[0] ? { topConsumer: bySource[0] } : {}),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["sourceKey","isRecord","MAX_UINT64"],"sources":["../../src/analyze.ts","../../src/report.ts","../../src/usage.ts","../../src/personal-usage-decode.ts","../../src/personal-usage.ts","../../src/otlp-prompt-root-traces.ts","../../src/otlp-usage-snapshot.ts","../../src/otlp-prompt-events.ts","../../src/personal-usage-format.ts"],"sourcesContent":["/**\n * Session-log timing-interval computation and aggregation.\n *\n * Operates on canonical history entries (`IHistoryEntry`); persisted timestamps arrive as ISO\n * strings at runtime (JSON has no Date), so all timestamp math goes through `new Date(...)`.\n */\n\nimport type {\n IAggregateReport,\n TSessionAnalysisInput,\n ISessionTimingReport,\n ITimingInterval,\n ITimingStats,\n TIntervalKind,\n} from './types.js';\nimport type { IHistoryEntry } from '@robota-sdk/agent-core';\n\nconst SLOW_THRESHOLD_MS = 10_000;\n\n/** Gap in milliseconds between two timestamps (ISO string or Date). */\nexport function gapMs(from: string | Date, to: string | Date): number {\n return new Date(to).getTime() - new Date(from).getTime();\n}\n\nfunction toIso(value: string | Date): string {\n return new Date(value).toISOString();\n}\n\nfunction makeInterval(\n kind: TIntervalKind,\n fromType: string,\n toType: string,\n fromTimestamp: string | Date,\n toTimestamp: string | Date,\n turnIndex: number,\n): ITimingInterval {\n return {\n kind,\n fromType,\n toType,\n fromTimestamp: toIso(fromTimestamp),\n toTimestamp: toIso(toTimestamp),\n durationMs: gapMs(fromTimestamp, toTimestamp),\n turnIndex,\n };\n}\n\n/**\n * Compute timing intervals from a history entry array.\n *\n * Strategy: scan the timeline turn by turn. A \"turn\" starts with a user message. After user, we\n * expect zero or more tool-start → tool-end cycles, optionally a tool-summary, then an assistant\n * message.\n */\nexport function computeTimingIntervals(history: readonly IHistoryEntry[]): ITimingInterval[] {\n const intervals: ITimingInterval[] = [];\n let turnIndex = 0;\n\n let i = 0;\n while (i < history.length) {\n const entry = history[i];\n if (!entry) {\n i++;\n continue;\n }\n\n if (entry.category === 'chat' && entry.type === 'user') {\n turnIndex++;\n const userTs = entry.timestamp;\n let foundFirstTool = false;\n let lastToolEndTs: IHistoryEntry['timestamp'] | null = null;\n let j = i + 1;\n\n while (j < history.length) {\n const next = history[j];\n if (!next) {\n j++;\n continue;\n }\n\n if (next.category === 'chat' && next.type === 'user') {\n break;\n }\n\n if (next.category === 'event' && next.type === 'tool-start') {\n if (!foundFirstTool) {\n intervals.push(\n makeInterval(\n 'user_to_first_tool',\n 'user',\n 'tool-start',\n userTs,\n next.timestamp,\n turnIndex,\n ),\n );\n foundFirstTool = true;\n } else if (lastToolEndTs !== null) {\n intervals.push(\n makeInterval(\n 'llm_between_tools',\n 'tool-end',\n 'tool-start',\n lastToolEndTs,\n next.timestamp,\n turnIndex,\n ),\n );\n }\n lastToolEndTs = null;\n j++;\n continue;\n }\n\n if (next.category === 'event' && next.type === 'tool-end') {\n const prev = history[j - 1];\n if (prev && prev.category === 'event' && prev.type === 'tool-start') {\n intervals.push(\n makeInterval(\n 'tool_exec',\n 'tool-start',\n 'tool-end',\n prev.timestamp,\n next.timestamp,\n turnIndex,\n ),\n );\n }\n lastToolEndTs = next.timestamp;\n j++;\n continue;\n }\n\n if (next.category === 'event' && next.type === 'tool-summary') {\n if (lastToolEndTs !== null) {\n intervals.push(\n makeInterval(\n 'llm_final_response',\n 'tool-end',\n 'tool-summary',\n lastToolEndTs,\n next.timestamp,\n turnIndex,\n ),\n );\n }\n j++;\n continue;\n }\n\n if (next.category === 'chat' && next.type === 'assistant') {\n if (!foundFirstTool) {\n intervals.push(\n makeInterval(\n 'user_to_assistant',\n 'user',\n 'assistant',\n userTs,\n next.timestamp,\n turnIndex,\n ),\n );\n } else if (lastToolEndTs !== null) {\n intervals.push(\n makeInterval(\n 'llm_final_response',\n 'tool-end',\n 'assistant',\n lastToolEndTs,\n next.timestamp,\n turnIndex,\n ),\n );\n }\n j++;\n break;\n }\n\n j++;\n }\n\n i = j;\n } else {\n i++;\n }\n }\n\n return intervals;\n}\n\nfunction computeStats(intervals: ITimingInterval[]): ITimingStats {\n const llm = intervals.filter(\n (iv) =>\n iv.kind === 'user_to_first_tool' ||\n iv.kind === 'user_to_assistant' ||\n iv.kind === 'llm_between_tools' ||\n iv.kind === 'llm_final_response',\n );\n const tool = intervals.filter((iv) => iv.kind === 'tool_exec');\n const direct = intervals.filter((iv) => iv.kind === 'user_to_assistant');\n\n const avg = (arr: ITimingInterval[]): number =>\n arr.length ? Math.round(arr.reduce((s, iv) => s + iv.durationMs, 0) / arr.length) : 0;\n const max = (arr: ITimingInterval[]): number =>\n arr.length ? Math.max(...arr.map((iv) => iv.durationMs)) : 0;\n const median = (arr: ITimingInterval[]): number => {\n if (!arr.length) return 0;\n const sorted = [...arr].sort((a, b) => a.durationMs - b.durationMs);\n const mid = Math.floor(sorted.length / 2);\n return sorted.length % 2 === 0\n ? Math.round(((sorted[mid - 1]?.durationMs ?? 0) + (sorted[mid]?.durationMs ?? 0)) / 2)\n : (sorted[mid]?.durationMs ?? 0);\n };\n\n return {\n llmWaitMs: {\n avg: avg(llm),\n max: max(llm),\n total: llm.reduce((s, iv) => s + iv.durationMs, 0),\n count: llm.length,\n },\n toolExecMs: { avg: avg(tool), max: max(tool), median: median(tool), count: tool.length },\n userToAssistantMs: { avg: avg(direct), max: max(direct), count: direct.length },\n };\n}\n\n/** Compute the timing report for a single session record. */\nexport function analyzeSession(record: TSessionAnalysisInput): ISessionTimingReport {\n const history = record.history ?? [];\n const intervals = computeTimingIntervals(history);\n const slowIntervals = intervals.filter((iv) => iv.durationMs >= SLOW_THRESHOLD_MS);\n\n return {\n sessionId: record.id,\n cwd: record.cwd,\n createdAt: record.createdAt,\n totalIntervals: intervals.length,\n intervals,\n slowIntervals,\n stats: computeStats(intervals),\n };\n}\n\n/** Aggregate multiple single-session reports into a fleet-level summary. */\nexport function aggregateReports(reports: readonly ISessionTimingReport[]): IAggregateReport {\n const llmIntervals = reports.flatMap((r) =>\n r.intervals.filter(\n (iv) =>\n iv.kind === 'user_to_first_tool' ||\n iv.kind === 'user_to_assistant' ||\n iv.kind === 'llm_between_tools' ||\n iv.kind === 'llm_final_response',\n ),\n );\n const toolIntervals = reports.flatMap((r) => r.intervals.filter((iv) => iv.kind === 'tool_exec'));\n\n const avgLlmResponseMs = llmIntervals.length\n ? Math.round(llmIntervals.reduce((s, iv) => s + iv.durationMs, 0) / llmIntervals.length)\n : 0;\n const avgToolExecMs = toolIntervals.length\n ? Math.round(toolIntervals.reduce((s, iv) => s + iv.durationMs, 0) / toolIntervals.length)\n : 0;\n\n let maxSingleDelayMs = 0;\n let maxSingleDelaySession = '';\n let maxSingleDelayTurn = 0;\n let maxSingleDelayKind = '';\n\n for (const r of reports) {\n for (const iv of r.intervals) {\n if (iv.durationMs > maxSingleDelayMs) {\n maxSingleDelayMs = iv.durationMs;\n maxSingleDelaySession = r.sessionId;\n maxSingleDelayTurn = iv.turnIndex;\n maxSingleDelayKind = iv.kind;\n }\n }\n }\n\n const dates = reports.map((r) => r.createdAt).sort();\n\n return {\n sessionCount: reports.length,\n fromDate: dates[0] ?? '',\n toDate: dates[dates.length - 1] ?? '',\n avgLlmResponseMs,\n avgToolExecMs,\n maxSingleDelayMs,\n maxSingleDelaySession,\n maxSingleDelayTurn,\n maxSingleDelayKind,\n };\n}\n","/**\n * Text report formatters for session timing analysis. Pure — every function returns a string;\n * callers (e.g. a CLI command) own writing to stdout.\n */\n\nimport type { IAggregateReport, ISessionTimingReport, ITimingInterval } from './types.js';\nimport type { IUsageBySourceReport } from './usage.js';\n\nfunction fmtTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n return `${n}`;\n}\n\n/** SELFHOST-004: `~` marks an estimate or incomplete aggregate, never an invoice amount. */\nfunction fmtCost(usd: number, exact: boolean): string {\n return `${exact ? '' : '~'}$${usd.toFixed(4)}`;\n}\n\n/**\n * ANALYTICS-001 + SELFHOST-004: render a per-source token+cost breakdown and the per-operation span\n * timeline — which part of the session burned the most tokens/cost, and where the wall-clock went.\n * Pure; the caller owns writing it (the headless CLI path for the TUI/GUI trace/cost view).\n */\nexport function formatUsageReport(report: IUsageBySourceReport): string {\n const lines: string[] = [];\n lines.push(`Token usage — session ${report.sessionId}`);\n lines.push(\n ` total ${fmtTokens(report.totalTokens)} (prompt ${fmtTokens(report.promptTokens)} · completion ${fmtTokens(report.completionTokens)})`,\n );\n lines.push(` cost ${fmtCost(report.costUsd, report.costExact)}`);\n if (report.bySource.length === 0) {\n lines.push(' (no usage recorded)');\n } else {\n lines.push(' by source (most tokens first):');\n for (const s of report.bySource) {\n lines.push(\n ` ${s.label.padEnd(24)} ${String(s.percentage).padStart(5)}% ${fmtTokens(s.totalTokens).padStart(7)} ${fmtCost(s.costUsd, s.costExact).padStart(10)} (${s.turns} turn${s.turns === 1 ? '' : 's'})`,\n );\n }\n if (report.topConsumer) {\n lines.push(` top consumer: ${report.topConsumer.label} (${report.topConsumer.percentage}%)`);\n }\n }\n\n // SELFHOST-004: the span timeline — sub-turn operations grouped under their owning turn.\n if (report.timeline.length > 0) {\n lines.push(' trace (per turn, sub-turn spans):');\n for (const turn of report.timeline) {\n lines.push(\n ` turn ${turn.turnIndex} — ${turn.label} · ${fmtMs(turn.totalDurationMs)} across ${turn.spans.length} op${turn.spans.length === 1 ? '' : 's'}`,\n );\n for (const span of turn.spans) {\n lines.push(` ${span.op.padEnd(22)} ${fmtMs(span.durationMs).padStart(8)}`);\n }\n }\n }\n return lines.join('\\n');\n}\n\nfunction fmtMs(ms: number): string {\n if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;\n return `${ms}ms`;\n}\n\nfunction fmtDate(iso: string): string {\n return new Date(iso).toLocaleString('sv-SE', { timeZoneName: 'short' }).slice(0, 16);\n}\n\nfunction intervalLabel(iv: ITimingInterval): string {\n const map: Record<string, string> = {\n user_to_first_tool: 'user → tool-start',\n user_to_assistant: 'user → assistant',\n tool_exec: 'tool-start → tool-end',\n llm_between_tools: 'tool-end → tool-start',\n llm_final_response: 'tool-end → assistant/summary',\n };\n return map[iv.kind] ?? iv.kind;\n}\n\nfunction verdictLine(report: ISessionTimingReport): string {\n const { stats } = report;\n const totalMs = stats.llmWaitMs.total + stats.toolExecMs.avg * stats.toolExecMs.count;\n if (totalMs === 0) return 'Verdict: No timing data available.';\n const llmPct = Math.round((stats.llmWaitMs.total / totalMs) * 100);\n const toolPct = 100 - llmPct;\n return `Verdict: LLM API wait ${llmPct}% | Code processing ${toolPct}%`;\n}\n\nexport function formatSingleSession(report: ISessionTimingReport): string {\n const lines: string[] = [];\n const { stats } = report;\n\n lines.push(`Session: ${report.sessionId}`);\n lines.push(`Created: ${fmtDate(report.createdAt)} | cwd: ${report.cwd}`);\n lines.push('');\n lines.push('Timing Summary:');\n\n if (stats.llmWaitMs.count > 0) {\n lines.push(\n ` LLM API wait ${fmtMs(stats.llmWaitMs.avg).padStart(8)} avg | ${fmtMs(stats.llmWaitMs.max).padStart(8)} max`,\n );\n }\n if (stats.toolExecMs.count > 0) {\n lines.push(\n ` Tool execution ${fmtMs(stats.toolExecMs.avg).padStart(8)} avg | ${fmtMs(stats.toolExecMs.median).padStart(8)} median`,\n );\n }\n if (stats.userToAssistantMs.count > 0) {\n lines.push(` user → assistant ${fmtMs(stats.userToAssistantMs.avg).padStart(8)} avg`);\n }\n if (stats.llmWaitMs.count === 0 && stats.toolExecMs.count === 0) {\n lines.push(' (no intervals computed — session may have no history)');\n }\n\n if (report.slowIntervals.length > 0) {\n lines.push('');\n lines.push('Slow intervals (>10s):');\n for (const iv of report.slowIntervals) {\n lines.push(\n ` turn ${iv.turnIndex} ${intervalLabel(iv).padEnd(32)} ${fmtMs(iv.durationMs).padStart(8)}`,\n );\n }\n }\n\n lines.push('');\n lines.push(verdictLine(report));\n\n return lines.join('\\n');\n}\n\nexport function formatAggregateReport(aggregate: IAggregateReport): string {\n const lines: string[] = [];\n lines.push(\n `Analyzed ${aggregate.sessionCount} sessions (${fmtDate(aggregate.fromDate)} ~ ${fmtDate(aggregate.toDate)})`,\n );\n lines.push(` Avg LLM response: ${fmtMs(aggregate.avgLlmResponseMs)}`);\n lines.push(` Avg tool exec: ${fmtMs(aggregate.avgToolExecMs)}`);\n if (aggregate.maxSingleDelayMs > 0) {\n lines.push(\n ` Max single delay: ${fmtMs(aggregate.maxSingleDelayMs)} (${aggregate.maxSingleDelaySession}, turn ${aggregate.maxSingleDelayTurn}, ${aggregate.maxSingleDelayKind})`,\n );\n }\n return lines.join('\\n');\n}\n","/**\n * ANALYTICS-001: source-attributed token-usage analysis over a session record.\n *\n * The main session history records one `usage-summary` history entry per turn (an `IUsageSnapshot`,\n * created in agent-framework). This reducer aggregates those snapshots into a per-source breakdown —\n * answering \"which part of the session burned the most tokens?\" — so it can be reported to the user\n * and asserted by the testing framework (budget gates). Pure: no I/O, no process concerns.\n */\n\nimport type { IHistoryEntry } from '@robota-sdk/agent-core';\nimport type {\n ISpanEntry,\n IUsageSnapshot,\n IUsageSource,\n IUsageSourceTotals,\n IRunTraceSpan,\n IRunTraceTurn,\n IUsageBySourceReport,\n} from '@robota-sdk/agent-interface-analytics';\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface-session';\n\n// SELFHOST-004: the trace/cost read-model is a boundary contract owned by `agent-interface-analytics`\n// (it crosses the sidecar boundary via a TServerMessage carrier). Re-exported here for consumers that\n// import it alongside the reducer that produces it.\nexport type {\n IUsageSourceTotals,\n IRunTraceSpan,\n IRunTraceTurn,\n IUsageBySourceReport,\n} from '@robota-sdk/agent-interface-analytics';\n\n/** The `type` of the per-turn usage history entry (agent-framework `createUsageSummaryEntry`). */\nconst USAGE_SUMMARY_ENTRY_TYPE = 'usage-summary';\n\n/** The `type` of the per-operation span history entry (agent-framework `createSpanEntry`). */\nconst SPAN_ENTRY_TYPE = 'span';\n\n/** SSOT-derived projection the usage reducer reads. */\nexport type TUsageAnalysisInput = Pick<IInteractiveSessionRecord, 'id' | 'history'>;\n\n/** The main thread is the implicit source when a usage snapshot carries none. */\nconst MAIN_THREAD_SOURCE: IUsageSource = { scope: 'main', label: 'main thread' };\n\nfunction sourceKey(source: IUsageSource): string {\n return `${source.scope}:${source.id ?? ''}`;\n}\n\nfunction sourceLabel(source: IUsageSource): string {\n return source.label ?? (source.id ? `${source.scope} ${source.id}` : source.scope);\n}\n\nfunction isUsageSummaryEntry(entry: IHistoryEntry): entry is IHistoryEntry<IUsageSnapshot> {\n return (\n entry.type === USAGE_SUMMARY_ENTRY_TYPE && typeof entry.data === 'object' && entry.data !== null\n );\n}\n\nfunction isSpanEntry(entry: IHistoryEntry): entry is IHistoryEntry<ISpanEntry> {\n if (entry.type !== SPAN_ENTRY_TYPE || typeof entry.data !== 'object' || entry.data === null) {\n return false;\n }\n const data = entry.data as Partial<ISpanEntry>;\n return (\n typeof data.spanId === 'string' &&\n typeof data.op === 'string' &&\n typeof data.durationMs === 'number'\n );\n}\n\nfunction roundPercentage(part: number, whole: number): number {\n if (whole <= 0) return 0;\n return Math.round((part / whole) * 1000) / 10;\n}\n\n/**\n * Aggregate a session's `usage-summary` entries into a per-source token+cost breakdown AND assemble\n * the per-operation span timeline (SELFHOST-004).\n *\n * Token/cost: usage snapshots with no `source` are attributed to the main thread; sources are grouped\n * by `<scope>:<id>` and sorted by total tokens descending with the top consumer surfaced. Cost sums\n * each turn's known `IUsageSnapshot.costUsd` (unpriced turns contribute 0). Historical `exact`\n * snapshots were local price-table estimates, so they cannot certify billed cost.\n *\n * Timeline: history is a chronological timeline where each turn ends with its `usage-summary` entry,\n * so the `span` entries that appear before a given usage-summary are the sub-turn spans of THAT turn.\n * The reducer walks in order, buffering spans, and flushes the buffer as the turn's spans at each\n * usage-summary boundary — grouping sub-turn spans under their owning turn. Spans after the last\n * usage-summary (an in-progress turn) form a trailing timeline entry. Pure: no I/O, stays within the\n * declared `agent-core`, `agent-interface-analytics` and `agent-interface-session` dependencies\n * (NO `agent-plugin` read — enforcement lives elsewhere).\n */\nexport function summarizeUsageBySource(input: TUsageAnalysisInput): IUsageBySourceReport {\n const totalsByKey = new Map<string, IUsageSourceTotals>();\n let sessionPrompt = 0;\n let sessionCompletion = 0;\n let sessionTotal = 0;\n let sessionCost = 0;\n let sessionCostExact = true;\n\n const timeline: IRunTraceTurn[] = [];\n let pendingSpans: IRunTraceSpan[] = [];\n\n const flushTurn = (source: IUsageSource): void => {\n if (pendingSpans.length === 0) return;\n timeline.push({\n turnIndex: timeline.length,\n source,\n label: sourceLabel(source),\n spans: pendingSpans,\n totalDurationMs: pendingSpans.reduce((sum, span) => sum + span.durationMs, 0),\n });\n pendingSpans = [];\n };\n\n for (const entry of input.history ?? []) {\n if (isSpanEntry(entry)) {\n const span = entry.data as ISpanEntry;\n pendingSpans.push({ spanId: span.spanId, op: span.op, durationMs: span.durationMs });\n continue;\n }\n if (!isUsageSummaryEntry(entry)) continue;\n\n const snapshot = entry.data as IUsageSnapshot;\n const source = snapshot.source ?? MAIN_THREAD_SOURCE;\n const key = sourceKey(source);\n const prompt = snapshot.promptTokens ?? 0;\n const completion = snapshot.completionTokens ?? 0;\n const total = snapshot.totalTokens;\n // Historical `exact` labels were local price-table estimates, not billed values. Preserve the\n // amount while keeping the aggregate's exactness claim false.\n const turnCost = snapshot.costUsd ?? 0;\n const turnPriced = false;\n\n sessionPrompt += prompt;\n sessionCompletion += completion;\n sessionTotal += total;\n sessionCost += turnCost;\n if (!turnPriced) sessionCostExact = false;\n\n const existing = totalsByKey.get(key);\n if (existing) {\n existing.promptTokens += prompt;\n existing.completionTokens += completion;\n existing.totalTokens += total;\n existing.turns += 1;\n existing.costUsd += turnCost;\n if (!turnPriced) existing.costExact = false;\n } else {\n totalsByKey.set(key, {\n key,\n source,\n label: sourceLabel(source),\n promptTokens: prompt,\n completionTokens: completion,\n totalTokens: total,\n turns: 1,\n percentage: 0,\n costUsd: turnCost,\n costExact: turnPriced,\n });\n }\n\n // The usage-summary marks the turn boundary: the buffered spans belong to THIS turn.\n flushTurn(source);\n }\n\n // Spans of an in-progress turn (no usage summary yet) form a trailing timeline entry.\n flushTurn(MAIN_THREAD_SOURCE);\n\n const bySource = [...totalsByKey.values()]\n .map((totals) => ({ ...totals, percentage: roundPercentage(totals.totalTokens, sessionTotal) }))\n .sort((left, right) => right.totalTokens - left.totalTokens);\n\n return {\n sessionId: input.id,\n totalTokens: sessionTotal,\n promptTokens: sessionPrompt,\n completionTokens: sessionCompletion,\n costUsd: sessionCost,\n costExact: sessionCostExact,\n bySource,\n ...(bySource[0] ? { topConsumer: bySource[0] } : {}),\n timeline,\n };\n}\n","import type { IHistoryEntry, IUniversalObjectValue, TUniversalValue } from '@robota-sdk/agent-core';\nimport type {\n IPersonalUsageActivity,\n IUsageModelShare,\n IUsageObservation,\n IUsageSnapshot,\n IUsageSource,\n} from '@robota-sdk/agent-interface-analytics';\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface-session';\n\nexport interface INormalizedObservation {\n readonly id: string;\n readonly sessionId: string;\n readonly at: Date;\n readonly legacy: boolean;\n readonly observation: IUsageObservation;\n}\n\nexport interface INormalizedActivity {\n readonly id: string;\n readonly sessionId: string;\n readonly key: string;\n readonly label: string;\n readonly kind: IPersonalUsageActivity['kind'];\n readonly at: Date;\n}\n\nfunction isRecord(value: TUniversalValue): value is IUniversalObjectValue {\n return (\n typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)\n );\n}\n\nfunction optionalString(value: TUniversalValue): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction isMetric(value: TUniversalValue): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0;\n}\n\nfunction isCostStatus(value: TUniversalValue): value is IUsageSnapshot['costStatus'] {\n return value === 'unknown' || value === 'estimated' || value === 'exact';\n}\n\nfunction hasValidOptionalMetrics(value: IUniversalObjectValue): boolean {\n return (\n (value['promptTokens'] === undefined || isMetric(value['promptTokens'])) &&\n (value['completionTokens'] === undefined || isMetric(value['completionTokens']))\n );\n}\n\nfunction hasValidCost(value: IUniversalObjectValue): boolean {\n const status = value['costStatus'];\n if (!isCostStatus(status)) return false;\n return status === 'unknown' ? value['costUsd'] === undefined : isMetric(value['costUsd']);\n}\n\ntype TRequiredUsageMetrics = {\n readonly totalTokens: number;\n readonly contextUsedTokens: number;\n readonly contextMaxTokens: number;\n readonly contextUsedPercentage: number;\n};\n\nfunction hasRequiredMetrics(\n value: IUniversalObjectValue,\n): value is IUniversalObjectValue & TRequiredUsageMetrics {\n return (\n isMetric(value['totalTokens']) &&\n isMetric(value['contextUsedTokens']) &&\n isMetric(value['contextMaxTokens']) &&\n isMetric(value['contextUsedPercentage'])\n );\n}\n\nfunction isUsageSurface(\n value: TUniversalValue,\n): value is NonNullable<IUsageObservation['surface']> {\n return (\n value === 'cli' ||\n value === 'desktop-app' ||\n value === 'browser' ||\n value === 'remote' ||\n value === 'unknown'\n );\n}\n\nfunction usageSource(value: TUniversalValue): IUsageSource | undefined {\n if (!isRecord(value)) return undefined;\n const scope = value['scope'];\n if (\n scope !== 'main' &&\n scope !== 'subagent' &&\n scope !== 'background' &&\n scope !== 'tool' &&\n scope !== 'command' &&\n scope !== 'skill'\n ) {\n return undefined;\n }\n const id = optionalString(value['id']);\n const label = optionalString(value['label']);\n return { scope, ...(id ? { id } : {}), ...(label ? { label } : {}) };\n}\n\nfunction usageSnapshot(value: TUniversalValue): IUsageSnapshot | undefined {\n if (!isRecord(value)) return undefined;\n if (value['kind'] !== 'exact' && value['kind'] !== 'estimated') return undefined;\n if (value['scope'] !== 'turn' || !hasValidOptionalMetrics(value) || !hasValidCost(value)) {\n return undefined;\n }\n if (!hasRequiredMetrics(value) || !isCostStatus(value['costStatus'])) {\n return undefined;\n }\n const source = usageSource(value['source']);\n return {\n kind: value['kind'],\n scope: 'turn',\n totalTokens: value['totalTokens'],\n contextUsedTokens: value['contextUsedTokens'],\n contextMaxTokens: value['contextMaxTokens'],\n contextUsedPercentage: value['contextUsedPercentage'],\n // Historical `exact` meant a local price-table calculation, not an invoice.\n costStatus: value['costStatus'] === 'exact' ? 'estimated' : value['costStatus'],\n ...(isMetric(value['promptTokens']) ? { promptTokens: value['promptTokens'] } : {}),\n ...(isMetric(value['completionTokens']) ? { completionTokens: value['completionTokens'] } : {}),\n ...(isMetric(value['costUsd']) ? { costUsd: value['costUsd'] } : {}),\n ...(source ? { source } : {}),\n };\n}\n\nfunction modelShare(value: TUniversalValue): IUsageModelShare | undefined {\n if (!isRecord(value)) return undefined;\n const providerId = optionalString(value['providerId']);\n const modelId = optionalString(value['modelId']);\n const { promptTokens, completionTokens, totalTokens, costStatus, costUsd } = value;\n if (\n !providerId ||\n !modelId ||\n !isMetric(promptTokens) ||\n !isMetric(completionTokens) ||\n !isMetric(totalTokens) ||\n (costStatus !== 'unknown' && costStatus !== 'estimated') ||\n (costStatus === 'estimated' ? !isMetric(costUsd) : costUsd !== undefined)\n ) {\n return undefined;\n }\n return {\n providerId,\n modelId,\n promptTokens,\n completionTokens,\n totalTokens,\n costStatus,\n ...(costStatus === 'estimated' && isMetric(costUsd) ? { costUsd } : {}),\n };\n}\n\n/** Every share, or none: a partly readable split would misstate what each model spent. */\nfunction modelShares(value: TUniversalValue): IUsageModelShare[] | undefined {\n if (!Array.isArray(value) || value.length === 0) return undefined;\n const shares = value.map(modelShare);\n return shares.every((share) => share !== undefined) ? (shares as IUsageModelShare[]) : undefined;\n}\n\nfunction decodeObservation(value: TUniversalValue): IUsageObservation | undefined {\n if (!isRecord(value)) return undefined;\n const usageObservationId = optionalString(value['usageObservationId']);\n const turnId = optionalString(value['turnId']);\n const outcome = value['outcome'];\n if (\n !usageObservationId ||\n !turnId ||\n (outcome !== 'success' && outcome !== 'failure' && outcome !== 'interrupted')\n ) {\n return undefined;\n }\n const surface = value['surface'];\n const source = usageSource(value['source']);\n const usage = usageSnapshot(value['usage']);\n const shares = modelShares(value['modelShares']);\n return {\n usageObservationId,\n turnId,\n outcome,\n ...(optionalString(value['modelId']) ? { modelId: optionalString(value['modelId']) } : {}),\n ...(optionalString(value['providerId'])\n ? { providerId: optionalString(value['providerId']) }\n : {}),\n ...(shares ? { modelShares: shares } : {}),\n ...(isUsageSurface(surface) ? { surface } : {}),\n ...(source ? { source } : {}),\n ...(usage ? { usage } : {}),\n };\n}\n\nfunction entryDate(entry: IHistoryEntry): Date | undefined {\n const at = new Date(entry.timestamp);\n return Number.isNaN(at.getTime()) ? undefined : at;\n}\n\nexport function normalizeRecord(record: IInteractiveSessionRecord): INormalizedObservation[] {\n const observations: INormalizedObservation[] = [];\n const history = record.history ?? [];\n const shadowedLegacyIds = new Set<string>();\n const unmatchedLegacy = history.flatMap((entry, index) => {\n if (entry.type !== 'usage-summary') return [];\n const usage = usageSnapshot(entry.data as TUniversalValue);\n return usage ? [{ id: entry.id, index, usage }] : [];\n });\n for (const [canonicalIndex, entry] of history.entries()) {\n if (entry.type !== 'usage-observation') continue;\n const canonical = decodeObservation(entry.data as TUniversalValue);\n if (!canonical?.usage) continue;\n const canonicalKey = JSON.stringify(canonical.usage);\n const candidates = unmatchedLegacy\n .map((candidate, index) => ({ candidate, index }))\n .filter(({ candidate }) => JSON.stringify(candidate.usage) === canonicalKey)\n .sort(\n (left, right) =>\n Math.abs(left.candidate.index - canonicalIndex) -\n Math.abs(right.candidate.index - canonicalIndex),\n );\n const match = candidates[0];\n if (!match) continue;\n shadowedLegacyIds.add(match.candidate.id);\n unmatchedLegacy.splice(match.index, 1);\n }\n for (const entry of history) {\n const at = entryDate(entry);\n if (!at) continue;\n const observation =\n entry.type === 'usage-observation'\n ? decodeObservation(entry.data as TUniversalValue)\n : undefined;\n if (observation) {\n observations.push({\n id: observation.usageObservationId,\n sessionId: record.id,\n at,\n legacy: false,\n observation,\n });\n continue;\n }\n if (entry.type !== 'usage-summary' || shadowedLegacyIds.has(entry.id)) continue;\n const usage = usageSnapshot(entry.data as TUniversalValue);\n if (!usage) continue;\n const id = `legacy:${record.id}:${entry.id}`;\n observations.push({\n id,\n sessionId: record.id,\n at,\n legacy: true,\n observation: {\n usageObservationId: id,\n turnId: `legacy:${entry.id}`,\n outcome: 'success',\n usage,\n ...(usage.source ? { source: usage.source } : {}),\n surface: 'unknown',\n },\n });\n }\n return observations;\n}\n\nexport function normalizeActivities(record: IInteractiveSessionRecord): INormalizedActivity[] {\n const activities: INormalizedActivity[] = [];\n for (const entry of record.history ?? []) {\n const data = entry.data as TUniversalValue;\n if (entry.type !== 'tool-start' || !isRecord(data)) continue;\n const toolName = optionalString(data['toolName']);\n const at = entryDate(entry);\n if (!toolName || !at) continue;\n activities.push({\n id: `tool:${record.id}:${entry.id}`,\n sessionId: record.id,\n key: `tool:${toolName}`,\n label: toolName,\n kind: 'tool',\n at,\n });\n }\n // Activation history mirrors this canonical source and is intentionally not counted again.\n for (const event of record.skillActivationEvents ?? []) {\n if (event.status !== 'started') continue;\n const at = new Date(event.timestamp);\n if (Number.isNaN(at.getTime())) continue;\n activities.push({\n id: `activation:${record.id}:${event.timestamp}:${event.source}:${event.qualifiedName ?? event.skillName}:${event.invocation}`,\n sessionId: record.id,\n key: event.qualifiedName ?? `${event.source}:${event.skillName}`,\n label: event.qualifiedName ?? event.skillName,\n kind: event.source,\n at,\n });\n }\n return activities;\n}\n","import {\n normalizeActivities,\n normalizeRecord,\n type INormalizedActivity,\n type INormalizedObservation,\n} from './personal-usage-decode.js';\n\nimport type {\n IPersonalUsageDimension,\n IPersonalUsageActivity,\n IPersonalUsageReport,\n IPersonalUsageRequest,\n IPersonalUsageTotals,\n IUsageSource,\n} from '@robota-sdk/agent-interface-analytics';\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface-session';\n\nconst SHORT_PERIOD_DAYS = 7;\nconst LONG_PERIOD_DAYS = 30;\nconst ISO_DATE_LENGTH = 'YYYY-MM-DD'.length;\n\nexport interface IPersonalUsageSnapshot {\n readonly request: IPersonalUsageRequest;\n readonly now: Date;\n readonly records: readonly IInteractiveSessionRecord[];\n readonly corruptSessionIds?: readonly string[];\n readonly unsupportedSessionIds?: readonly string[];\n}\n\ninterface IMutableTotals {\n sessions: Set<string>;\n observations: number;\n turns: number;\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n costUsd: number;\n hasUnknownCost: boolean;\n hasEstimatedCost: boolean;\n}\n\nfunction createMutableTotals(): IMutableTotals {\n return {\n sessions: new Set<string>(),\n observations: 0,\n turns: 0,\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n costUsd: 0,\n hasUnknownCost: false,\n hasEstimatedCost: false,\n };\n}\n\nfunction dateKey(date: Date, timezone: string): string {\n const parts = new Intl.DateTimeFormat('en-CA', {\n timeZone: timezone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(date);\n const part = (type: Intl.DateTimeFormatPartTypes): string =>\n parts.find((candidate) => candidate.type === type)?.value ?? '';\n return `${part('year')}-${part('month')}-${part('day')}`;\n}\n\nfunction calendarKeys(now: Date, timezone: string, count: number): string[] {\n // First resolve the current LOCAL date (also validates the IANA timezone), then perform date-only\n // arithmetic in UTC. Subtracting 24-hour instants can skip a local calendar date during the first\n // hour after a spring-forward transition; date-only arithmetic cannot.\n const [year, month, day] = dateKey(now, timezone).split('-').map(Number);\n const localDate = new Date(Date.UTC(year!, month! - 1, day!));\n return Array.from({ length: count }, (_, index) => {\n const value = new Date(localDate);\n value.setUTCDate(value.getUTCDate() - (count - index - 1));\n return value.toISOString().slice(0, ISO_DATE_LENGTH);\n });\n}\n\nfunction activityDimensions(items: readonly INormalizedActivity[]): IPersonalUsageActivity[] {\n const counts = new Map<string, { item: INormalizedActivity; count: number }>();\n for (const item of items) {\n const current = counts.get(item.key);\n counts.set(item.key, { item, count: (current?.count ?? 0) + 1 });\n }\n return [...counts.values()]\n .map(({ item, count }) => ({\n key: item.key,\n label: item.label,\n kind: item.kind,\n count,\n }))\n .sort((left, right) => right.count - left.count || left.key.localeCompare(right.key));\n}\n\nfunction addObservation(target: IMutableTotals, item: INormalizedObservation): void {\n const usage = item.observation.usage;\n target.sessions.add(item.sessionId);\n target.observations += 1;\n // A consulted model's usage (the advisor, a tool source) belongs to the turn that consulted it;\n // it adds tokens and cost, not a turn.\n if (!item.legacy && item.observation.source?.scope !== 'tool') target.turns += 1;\n target.promptTokens += usage?.promptTokens ?? 0;\n target.completionTokens += usage?.completionTokens ?? 0;\n target.totalTokens += usage?.totalTokens ?? 0;\n target.costUsd += usage?.costUsd ?? 0;\n if (!usage || usage.costStatus === 'unknown') target.hasUnknownCost = true;\n if (usage?.costStatus === 'estimated') target.hasEstimatedCost = true;\n}\n\n/**\n * Group a turn by model or provider. A turn that ran on more than one model is split by its shares,\n * so each model is charged only what it spent; the turn still counts once in every group it touched.\n */\nfunction modelDimensions(\n items: readonly INormalizedObservation[],\n keyOf: (share: { providerId?: string; modelId?: string }) => string,\n): IPersonalUsageDimension[] {\n const parts: INormalizedObservation[] = [];\n for (const item of items) {\n const shares = item.observation.modelShares;\n if (shares === undefined) {\n parts.push(item);\n continue;\n }\n const byKey = new Map<string, INormalizedObservation>();\n for (const share of shares) {\n const key = keyOf(share);\n const current = byKey.get(key)?.observation.usage;\n const usage = item.observation.usage;\n const costStatus =\n share.costStatus === 'unknown' || current?.costStatus === 'unknown'\n ? ('unknown' as const)\n : ('estimated' as const);\n byKey.set(key, {\n ...item,\n observation: {\n ...item.observation,\n providerId: share.providerId,\n modelId: share.modelId,\n usage: {\n kind: usage?.kind ?? 'estimated',\n scope: 'turn',\n contextUsedTokens: usage?.contextUsedTokens ?? 0,\n contextMaxTokens: usage?.contextMaxTokens ?? 0,\n contextUsedPercentage: usage?.contextUsedPercentage ?? 0,\n promptTokens: (current?.promptTokens ?? 0) + share.promptTokens,\n completionTokens: (current?.completionTokens ?? 0) + share.completionTokens,\n totalTokens: (current?.totalTokens ?? 0) + share.totalTokens,\n costStatus,\n ...(costStatus === 'estimated' && {\n costUsd: (current?.costUsd ?? 0) + (share.costUsd ?? 0),\n }),\n },\n },\n });\n }\n parts.push(...byKey.values());\n }\n return dimensions(parts, (item) => keyOf(item.observation));\n}\n\nfunction freezeTotals(totals: IMutableTotals): IPersonalUsageTotals {\n return {\n sessions: totals.sessions.size,\n turns: totals.turns,\n promptTokens: totals.promptTokens,\n completionTokens: totals.completionTokens,\n totalTokens: totals.totalTokens,\n costUsd: totals.costUsd,\n costStatus:\n totals.observations === 0 || totals.hasUnknownCost\n ? 'unknown'\n : totals.hasEstimatedCost\n ? 'estimated'\n : 'exact',\n };\n}\n\nfunction dimensions(\n items: readonly INormalizedObservation[],\n keyOf: (item: INormalizedObservation) => string,\n): IPersonalUsageDimension[] {\n const groups = new Map<string, IMutableTotals>();\n for (const item of items) {\n const key = keyOf(item);\n const totals = groups.get(key) ?? createMutableTotals();\n addObservation(totals, item);\n groups.set(key, totals);\n }\n return [...groups.entries()]\n .map(([key, totals]) => {\n const frozen = freezeTotals(totals);\n return { key, label: key, frozen, sessionIds: [...totals.sessions].sort() };\n })\n .map(({ key, label, frozen, sessionIds }) => ({\n key,\n label,\n turns: frozen.turns,\n promptTokens: frozen.promptTokens,\n completionTokens: frozen.completionTokens,\n totalTokens: frozen.totalTokens,\n costUsd: frozen.costUsd,\n costStatus: frozen.costStatus,\n sessionIds,\n }))\n .sort(\n (left, right) => right.totalTokens - left.totalTokens || left.key.localeCompare(right.key),\n );\n}\n\nfunction sourceKey(source: IUsageSource | undefined): string {\n if (!source) return 'unknown';\n return source.id ? `${source.scope}:${source.id}` : source.scope;\n}\n\ninterface ISelectedUsage {\n readonly observations: INormalizedObservation[];\n readonly activities: INormalizedActivity[];\n readonly duplicateObservations: number;\n readonly legacyObservations: number;\n}\n\nfunction isIncluded(at: Date, input: IPersonalUsageSnapshot, dates: ReadonlySet<string>): boolean {\n return at.getTime() <= input.now.getTime() && dates.has(dateKey(at, input.request.timezone));\n}\n\nfunction selectUsage(input: IPersonalUsageSnapshot, dates: ReadonlySet<string>): ISelectedUsage {\n const observations: INormalizedObservation[] = [];\n const activities: INormalizedActivity[] = [];\n const seenObservations = new Set<string>();\n const seenActivities = new Set<string>();\n let duplicateObservations = 0;\n let legacyObservations = 0;\n for (const record of input.records) {\n for (const item of normalizeRecord(record)) {\n if (!isIncluded(item.at, input, dates)) continue;\n if (seenObservations.has(item.id)) {\n duplicateObservations += 1;\n continue;\n }\n seenObservations.add(item.id);\n observations.push(item);\n if (item.legacy) legacyObservations += 1;\n }\n for (const activity of normalizeActivities(record)) {\n if (!isIncluded(activity.at, input, dates) || seenActivities.has(activity.id)) continue;\n seenActivities.add(activity.id);\n activities.push(activity);\n }\n }\n return { observations, activities, duplicateObservations, legacyObservations };\n}\n\nfunction accumulateUsage(\n input: IPersonalUsageSnapshot,\n keys: readonly string[],\n selection: ISelectedUsage,\n): { totals: IMutableTotals; byDay: Map<string, IMutableTotals> } {\n const totals = createMutableTotals();\n const byDay = new Map(keys.map((key) => [key, createMutableTotals()]));\n for (const item of selection.observations) {\n addObservation(totals, item);\n addObservation(byDay.get(dateKey(item.at, input.request.timezone))!, item);\n }\n for (const activity of selection.activities) {\n totals.sessions.add(activity.sessionId);\n byDay.get(dateKey(activity.at, input.request.timezone))?.sessions.add(activity.sessionId);\n }\n return { totals, byDay };\n}\n\nexport function summarizePersonalUsage(input: IPersonalUsageSnapshot): IPersonalUsageReport {\n const count = input.request.period === '30d' ? LONG_PERIOD_DAYS : SHORT_PERIOD_DAYS;\n const keys = calendarKeys(input.now, input.request.timezone, count);\n const selection = selectUsage(input, new Set(keys));\n const { totals, byDay } = accumulateUsage(input, keys, selection);\n const selected = selection.observations;\n\n return {\n schemaVersion: 1,\n generatedAt: input.now.toISOString(),\n period: input.request.period,\n timezone: input.request.timezone,\n interval: { startDate: keys[0]!, endDate: keys.at(-1)! },\n totals: freezeTotals(totals),\n daily: keys.map((date, index) => ({\n date,\n partial: index === keys.length - 1,\n totals: freezeTotals(byDay.get(date)!),\n sessionIds: [...byDay.get(date)!.sessions].sort(),\n })),\n byModel: modelDimensions(selected, (share) => share.modelId ?? 'unknown'),\n byProvider: modelDimensions(selected, (share) => share.providerId ?? 'unknown'),\n bySurface: dimensions(selected, (item) => item.observation.surface ?? 'unknown'),\n bySource: dimensions(selected, (item) => sourceKey(item.observation.source)),\n byActivity: activityDimensions(selection.activities),\n sessionIds: [...totals.sessions].sort(),\n coverage: {\n validSessions: input.records.length,\n corruptSessions: input.corruptSessionIds?.length ?? 0,\n unsupportedSessions: input.unsupportedSessionIds?.length ?? 0,\n duplicateObservations: selection.duplicateObservations,\n legacyObservations: selection.legacyObservations,\n unknownModelObservations: selected.filter(\n (item) => !item.observation.modelId && !item.observation.modelShares,\n ).length,\n unknownProviderObservations: selected.filter(\n (item) => !item.observation.providerId && !item.observation.modelShares,\n ).length,\n unknownSurfaceObservations: selected.filter(\n (item) => !item.observation.surface || item.observation.surface === 'unknown',\n ).length,\n corruptSessionIds: [...(input.corruptSessionIds ?? [])].sort(),\n unsupportedSessionIds: [...(input.unsupportedSessionIds ?? [])].sort(),\n },\n };\n}\n","import { calculateModelCost, MODEL_PRICES } from '@robota-sdk/agent-core';\n\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface-session';\n\nconst MAX_UINT64 = (1n << 64n) - 1n;\nconst TRACE_ID = /^(?!0{32}$)[0-9a-f]{32}$/;\nconst SPAN_ID = /^(?!0{16}$)[0-9a-f]{16}$/;\nconst ROOT_FIELDS = [\n 'promptExecutionStartedAt',\n 'promptExecutionEndedAt',\n 'promptExecutionOutcome',\n 'promptExecutionTraceId',\n 'promptExecutionSpanId',\n] as const;\n\ninterface ICandidate {\n readonly record: IInteractiveSessionRecord;\n readonly data: unknown;\n readonly observationKey?: string;\n readonly traceId?: string;\n}\n\ninterface IOtlpSpan {\n readonly traceId: string;\n readonly spanId: string;\n readonly parentSpanId?: string;\n readonly name: 'robota.prompt_execution' | 'robota.provider_call' | 'robota.tool_body';\n readonly kind: 1;\n readonly startTimeUnixNano: string;\n readonly endTimeUnixNano: string;\n readonly attributes: readonly {\n readonly key: string;\n readonly value: { readonly stringValue: string } | { readonly intValue: string } | { readonly doubleValue: number };\n }[];\n readonly status: { readonly code: number };\n}\n\nexport interface IOtlpPromptRootTraces {\n readonly resourceSpans: readonly {\n readonly resource: {\n readonly attributes: readonly {\n readonly key: string;\n readonly value: { readonly stringValue: string };\n }[];\n };\n readonly scopeSpans: readonly {\n readonly scope: { readonly name: string };\n readonly spans: readonly IOtlpSpan[];\n }[];\n }[];\n}\n\nexport interface IPromptRootTraceCoverage {\n readonly exported: number;\n readonly missing: number;\n readonly invalid: number;\n readonly duplicate: number;\n readonly providerChildren: {\n readonly exported: number;\n readonly invalid: number;\n readonly orphaned: number;\n readonly duplicate: number;\n };\n readonly providerUsage: {\n readonly legacy: number;\n readonly invalid: number;\n readonly partial: number;\n readonly absent: number;\n readonly nonInvoked: number;\n };\n readonly toolChildren: {\n readonly exported: number;\n readonly invalid: number;\n readonly orphaned: number;\n readonly duplicate: number;\n };\n}\n\nexport interface IAcceptedProviderCallMetrics {\n readonly invoked: number;\n readonly completeUsage: number;\n readonly inputTokens: number;\n readonly outputTokens: number;\n readonly estimatedCostUsd: number;\n readonly unknownCost: number;\n readonly exactPriceMatches: number;\n readonly familyPriceMatches: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction count<K>(map: Map<K, number>, key: K): void {\n map.set(key, (map.get(key) ?? 0) + 1);\n}\n\nfunction otlpUnixNano(value: unknown): bigint | undefined {\n if (typeof value !== 'string') return undefined;\n const date = new Date(value);\n if (Number.isNaN(date.getTime()) || date.toISOString() !== value) return undefined;\n const nanos = BigInt(date.getTime()) * 1_000_000n;\n return nanos >= 0n && nanos <= MAX_UINT64 ? nanos : undefined;\n}\n\nfunction rootSpan(data: Record<string, unknown>): IOtlpSpan | undefined {\n const started = otlpUnixNano(data['promptExecutionStartedAt']);\n const ended = otlpUnixNano(data['promptExecutionEndedAt']);\n const outcome = data['promptExecutionOutcome'];\n const traceId = data['promptExecutionTraceId'];\n const spanId = data['promptExecutionSpanId'];\n if (\n started === undefined ||\n ended === undefined ||\n started > ended ||\n (outcome !== 'success' && outcome !== 'failure' && outcome !== 'interrupted') ||\n typeof traceId !== 'string' ||\n !TRACE_ID.test(traceId) ||\n typeof spanId !== 'string' ||\n !SPAN_ID.test(spanId) ||\n typeof data['usageObservationId'] !== 'string' ||\n data['usageObservationId'].length === 0 ||\n data['turnId'] !== data['usageObservationId'] ||\n (data['outcome'] !== 'success' &&\n data['outcome'] !== 'failure' &&\n data['outcome'] !== 'interrupted')\n ) {\n return undefined;\n }\n return {\n traceId,\n spanId,\n name: 'robota.prompt_execution',\n kind: 1,\n startTimeUnixNano: String(started),\n endTimeUnixNano: String(ended),\n attributes: [{ key: 'robota.prompt.outcome', value: { stringValue: outcome } }],\n status: { code: outcome === 'success' ? 1 : outcome === 'failure' ? 2 : 0 },\n };\n}\n\nfunction childSpan(data: unknown, kind: 'provider' | 'tool'): IOtlpSpan | undefined {\n if (!isRecord(data)) return undefined;\n const traceId = data['traceId'];\n const parentSpanId = data['parentSpanId'];\n const spanId = data['spanId'];\n const started = otlpUnixNano(data['startedAt']);\n const ended = otlpUnixNano(data['endedAt']);\n const outcome = data['outcome'];\n if (\n typeof traceId !== 'string' ||\n !TRACE_ID.test(traceId) ||\n typeof parentSpanId !== 'string' ||\n !SPAN_ID.test(parentSpanId) ||\n typeof spanId !== 'string' ||\n !SPAN_ID.test(spanId) ||\n started === undefined ||\n ended === undefined ||\n started > ended ||\n (outcome !== 'success' && outcome !== 'failure' && outcome !== 'interrupted') ||\n (kind === 'provider' && (!Number.isSafeInteger(data['round']) || (data['round'] as number) < 1))\n ) {\n return undefined;\n }\n return {\n traceId,\n parentSpanId,\n spanId,\n name: kind === 'provider' ? 'robota.provider_call' : 'robota.tool_body',\n kind: 1,\n startTimeUnixNano: String(started),\n endTimeUnixNano: String(ended),\n attributes: [\n {\n key: kind === 'provider' ? 'robota.provider.outcome' : 'robota.tool.outcome',\n value: { stringValue: outcome },\n },\n ],\n status: { code: outcome === 'success' ? 1 : outcome === 'failure' ? 2 : 0 },\n };\n}\n\nfunction childIdentity(data: unknown): string | undefined {\n if (!isRecord(data)) return undefined;\n const traceId = data['traceId'];\n const spanId = data['spanId'];\n return typeof traceId === 'string' &&\n TRACE_ID.test(traceId) &&\n typeof spanId === 'string' &&\n SPAN_ID.test(spanId)\n ? `${traceId}:${spanId}`\n : undefined;\n}\n\nfunction validToken(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction enrichProviderSpan(\n span: IOtlpSpan,\n data: unknown,\n usageCoverage: { legacy: number; invalid: number; partial: number; absent: number },\n metrics: { -readonly [K in keyof IAcceptedProviderCallMetrics]: number },\n): IOtlpSpan {\n if (!isRecord(data) || data['disposition'] === undefined) {\n usageCoverage.legacy += 1;\n return span;\n }\n if (data['disposition'] !== 'invoked') {\n usageCoverage.invalid += 1;\n return span;\n }\n metrics.invoked += 1;\n const provenance = data['usageProvenance'];\n if (provenance === 'partial') {\n usageCoverage.partial += 1;\n metrics.unknownCost += 1;\n return span;\n }\n if (provenance === 'absent' || provenance === undefined) {\n usageCoverage.absent += 1;\n metrics.unknownCost += 1;\n return span;\n }\n const input = data['promptTokens'];\n const output = data['completionTokens'];\n const total = data['totalTokens'];\n if (\n provenance !== 'complete' || !validToken(input) || !validToken(output) ||\n !Number.isSafeInteger(input + output) || total !== input + output\n ) {\n usageCoverage.invalid += 1;\n metrics.unknownCost += 1;\n return span;\n }\n metrics.completeUsage += 1;\n metrics.inputTokens += input;\n metrics.outputTokens += output;\n const attrs: IOtlpSpan['attributes'][number][] = [\n ...span.attributes,\n { key: 'robota.provider.usage.input_tokens', value: { intValue: String(input) } },\n { key: 'robota.provider.usage.output_tokens', value: { intValue: String(output) } },\n ];\n const model = data['modelId'];\n const estimated = typeof model === 'string' && model.length > 0 && model.length <= 128 &&\n [...model].every((char) => char.charCodeAt(0) >= 32)\n ? calculateModelCost(model, input, output) : undefined;\n if (estimated === undefined || !Number.isFinite(estimated) || estimated < 0) {\n metrics.unknownCost += 1;\n } else {\n metrics.estimatedCostUsd += estimated;\n if (typeof model === 'string' && Object.hasOwn(MODEL_PRICES, model)) metrics.exactPriceMatches += 1;\n else metrics.familyPriceMatches += 1;\n attrs.push({ key: 'robota.provider.cost.usd.estimated', value: { doubleValue: estimated } });\n }\n return { ...span, attributes: attrs };\n}\n\n/** Project only proven canonical prompt roots; never infer spans from legacy usage or tool events. */\nexport function createOtlpPromptRootTraces(\n records: readonly IInteractiveSessionRecord[],\n version: string,\n): { readonly payload: IOtlpPromptRootTraces; readonly coverage: IPromptRootTraceCoverage; readonly callMetrics: IAcceptedProviderCallMetrics } {\n const candidates: ICandidate[] = [];\n const observationCounts = new Map<string, number>();\n const traceCounts = new Map<string, number>();\n for (const record of records) {\n for (const entry of record.history ?? []) {\n if (entry.type !== 'usage-observation') continue;\n const data: unknown = entry.data;\n const observationId = isRecord(data) ? data['usageObservationId'] : undefined;\n const traceId = isRecord(data) ? data['promptExecutionTraceId'] : undefined;\n const observationKey =\n typeof observationId === 'string' && observationId.length > 0\n ? JSON.stringify([record.id, observationId])\n : undefined;\n const validTraceId =\n typeof traceId === 'string' && TRACE_ID.test(traceId) ? traceId : undefined;\n if (observationKey) count(observationCounts, observationKey);\n if (validTraceId) count(traceCounts, validTraceId);\n candidates.push({ record, data, observationKey, traceId: validTraceId });\n }\n }\n\n const spans: IOtlpSpan[] = [];\n const rootsByRecord = new Map<IInteractiveSessionRecord, Map<string, IOtlpSpan>>();\n let missing = 0;\n let invalid = 0;\n let duplicate = 0;\n for (const candidate of candidates) {\n if (\n (candidate.observationKey && (observationCounts.get(candidate.observationKey) ?? 0) > 1) ||\n (candidate.traceId && (traceCounts.get(candidate.traceId) ?? 0) > 1)\n ) {\n duplicate += 1;\n continue;\n }\n const data = candidate.data;\n if (!isRecord(data)) {\n invalid += 1;\n continue;\n }\n if (ROOT_FIELDS.every((field) => data[field] === undefined)) {\n missing += 1;\n continue;\n }\n const span = rootSpan(data);\n if (!span) {\n invalid += 1;\n continue;\n }\n spans.push(span);\n const recordRoots = rootsByRecord.get(candidate.record) ?? new Map<string, IOtlpSpan>();\n recordRoots.set(span.traceId, span);\n rootsByRecord.set(candidate.record, recordRoots);\n }\n\n const children: {\n readonly record: IInteractiveSessionRecord;\n readonly span: IOtlpSpan | undefined;\n readonly identity: string | undefined;\n readonly kind: 'provider' | 'tool';\n readonly data: unknown;\n }[] = [];\n const childCounts = new Map<string, number>();\n for (const record of records) {\n for (const entry of record.history ?? []) {\n const kind =\n entry.type === 'provider-call-trace'\n ? 'provider'\n : entry.type === 'tool-body-trace'\n ? 'tool'\n : undefined;\n if (!kind) continue;\n const identity = childIdentity(entry.data);\n const span = childSpan(entry.data, kind);\n if (identity) count(childCounts, identity);\n children.push({ record, span, identity, kind, data: entry.data });\n }\n }\n const coverageByKind = {\n provider: { exported: 0, invalid: 0, orphaned: 0, duplicate: 0 },\n tool: { exported: 0, invalid: 0, orphaned: 0, duplicate: 0 },\n };\n const providerUsage = { legacy: 0, invalid: 0, partial: 0, absent: 0, nonInvoked: 0 };\n const callMetrics = {\n invoked: 0, completeUsage: 0, inputTokens: 0, outputTokens: 0,\n estimatedCostUsd: 0, unknownCost: 0, exactPriceMatches: 0, familyPriceMatches: 0,\n };\n for (const child of children) {\n const { span } = child;\n const coverage = coverageByKind[child.kind];\n if (child.identity && (childCounts.get(child.identity) ?? 0) > 1) {\n coverage.duplicate += 1;\n continue;\n }\n if (!span) {\n coverage.invalid += 1;\n continue;\n }\n const root = rootsByRecord.get(child.record)?.get(span.traceId);\n if (!root || span.parentSpanId !== root.spanId || span.spanId === root.spanId) {\n coverage.orphaned += 1;\n continue;\n }\n if (\n BigInt(span.startTimeUnixNano) < BigInt(root.startTimeUnixNano) ||\n BigInt(span.endTimeUnixNano) > BigInt(root.endTimeUnixNano)\n ) {\n coverage.invalid += 1;\n continue;\n }\n if (child.kind === 'provider' && isRecord(child.data) &&\n (child.data['disposition'] === 'cache-hit' || child.data['disposition'] === 'preflight-refused')) {\n providerUsage.nonInvoked += 1;\n if (child.data['promptTokens'] !== undefined || child.data['completionTokens'] !== undefined ||\n child.data['totalTokens'] !== undefined || child.data['usageProvenance'] === 'complete') {\n providerUsage.invalid += 1;\n }\n continue;\n }\n spans.push(child.kind === 'provider'\n ? enrichProviderSpan(span, child.data, providerUsage, callMetrics)\n : span);\n coverage.exported += 1;\n }\n\n return {\n payload: {\n resourceSpans: spans.length\n ? [\n {\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: 'robota' } },\n { key: 'service.version', value: { stringValue: version } },\n ],\n },\n scopeSpans: [{ scope: { name: '@robota-sdk/agent-session-analytics' }, spans }],\n },\n ]\n : [],\n },\n coverage: {\n exported: spans.length - coverageByKind.provider.exported - coverageByKind.tool.exported,\n missing,\n invalid,\n duplicate,\n providerChildren: coverageByKind.provider,\n providerUsage,\n toolChildren: coverageByKind.tool,\n },\n callMetrics,\n };\n}\n","import { createOtlpPromptRootTraces } from './otlp-prompt-root-traces.js';\nimport { normalizeRecord } from './personal-usage-decode.js';\n\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface-session';\n\n/** A non-additive, content-free snapshot. Consumers must read the latest Gauge, not sum exports. */\nexport function createOtlpUsageSnapshot(\n records: readonly IInteractiveSessionRecord[],\n at: Date,\n version: string,\n): { resourceMetrics: unknown[] } {\n const seen = new Set<string>();\n let turns = 0;\n let totalTokens = 0;\n let inputTokens = 0;\n let outputTokens = 0;\n let unknownTokenSplitObservations = 0;\n let knownCost = 0;\n let unknownCostObservations = 0;\n let estimatedCostObservations = 0;\n\n for (const record of records) {\n for (const item of normalizeRecord(record)) {\n if (seen.has(item.id)) continue;\n seen.add(item.id);\n if (!item.legacy) turns += 1;\n const usage = item.observation.usage;\n if (!usage) {\n unknownCostObservations += 1;\n unknownTokenSplitObservations += 1;\n continue;\n }\n totalTokens += usage.totalTokens;\n inputTokens += usage.promptTokens ?? 0;\n outputTokens += usage.completionTokens ?? 0;\n if (usage.promptTokens === undefined || usage.completionTokens === undefined) {\n unknownTokenSplitObservations += 1;\n }\n if (usage.costStatus === 'unknown' || usage.costUsd === undefined) {\n unknownCostObservations += 1;\n } else {\n knownCost += usage.costUsd;\n if (usage.costStatus === 'estimated') estimatedCostObservations += 1;\n }\n }\n }\n // The trace projection is the single accepted-call selector: parent/time/duplicate checks must\n // agree between traces and metrics. These are separate non-additive Gauges, never turn totals.\n const acceptedCalls = createOtlpPromptRootTraces(records, version);\n const calls = acceptedCalls.callMetrics;\n\n const timeUnixNano = String(BigInt(at.getTime()) * 1_000_000n);\n const gauge = (name: string, unit: string, value: number) => ({\n name,\n unit,\n gauge: { dataPoints: [{ timeUnixNano, asDouble: value }] },\n });\n\n return {\n resourceMetrics: [\n {\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: 'robota' } },\n { key: 'service.version', value: { stringValue: version } },\n ],\n },\n scopeMetrics: [\n {\n scope: { name: '@robota-sdk/agent-session-analytics' },\n metrics: [\n gauge('robota.session.count', '{session}', records.length),\n gauge('robota.turn.count', '{turn}', turns),\n gauge('robota.token.total', '{token}', totalTokens),\n gauge('robota.token.input.known', '{token}', inputTokens),\n gauge('robota.token.output.known', '{token}', outputTokens),\n gauge(\n 'robota.token.split_unknown_observations',\n '{observation}',\n unknownTokenSplitObservations,\n ),\n gauge('robota.cost.usd.known', 'USD', knownCost),\n gauge('robota.cost.unknown_observations', '{observation}', unknownCostObservations),\n gauge(\n 'robota.cost.estimated_observations',\n '{observation}',\n estimatedCostObservations,\n ),\n gauge('robota.provider_call.count', '{call}', calls.invoked),\n gauge('robota.provider_call.usage.complete_count', '{call}', calls.completeUsage),\n gauge('robota.provider_call.token.input.known', '{token}', calls.inputTokens),\n gauge('robota.provider_call.token.output.known', '{token}', calls.outputTokens),\n gauge('robota.provider_call.cost.usd.estimated', 'USD', calls.estimatedCostUsd),\n gauge('robota.provider_call.cost.unknown_count', '{call}', calls.unknownCost),\n gauge('robota.provider_call.usage.partial_count', '{call}', acceptedCalls.coverage.providerUsage.partial),\n gauge('robota.provider_call.usage.invalid_count', '{call}', acceptedCalls.coverage.providerUsage.invalid),\n gauge('robota.provider_call.usage.legacy_count', '{call}', acceptedCalls.coverage.providerUsage.legacy),\n gauge('robota.provider_call.price.exact_id_count', '{call}', calls.exactPriceMatches),\n gauge('robota.provider_call.price.family_fallback_count', '{call}', calls.familyPriceMatches),\n ],\n },\n ],\n },\n ],\n };\n}\n","import { createOtlpPromptRootTraces } from './otlp-prompt-root-traces.js';\n\nimport type { IPromptRootTraceCoverage } from './otlp-prompt-root-traces.js';\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface-session';\n\ntype TEventName =\n | 'robota.prompt_execution.completed'\n | 'robota.provider_call.completed'\n | 'robota.tool_body.completed';\n\ninterface IOtlpLogRecord {\n readonly timeUnixNano: string;\n readonly observedTimeUnixNano: string;\n readonly severityNumber: 9 | 13 | 17;\n readonly severityText: 'INFO' | 'WARN' | 'ERROR';\n readonly eventName: TEventName;\n readonly traceId: string;\n readonly spanId: string;\n readonly attributes: readonly {\n readonly key: string;\n readonly value: { readonly stringValue: string };\n }[];\n}\n\nexport interface IOtlpPromptEvents {\n readonly resourceLogs: readonly {\n readonly resource: {\n readonly attributes: readonly {\n readonly key: string;\n readonly value: { readonly stringValue: string };\n }[];\n };\n readonly scopeLogs: readonly {\n readonly scope: { readonly name: string };\n readonly logRecords: readonly IOtlpLogRecord[];\n }[];\n }[];\n}\n\nconst MAX_UINT64 = (1n << 64n) - 1n;\n\nfunction observedUnixNano(observedAt: Date): string {\n const millis = observedAt.getTime();\n if (!Number.isSafeInteger(millis) || millis < 0) {\n throw new RangeError('OTLP log observation time must be a valid post-epoch date.');\n }\n const nanos = BigInt(millis) * 1_000_000n;\n if (nanos > MAX_UINT64) throw new RangeError('OTLP log observation time is out of range.');\n return String(nanos);\n}\n\n/** Explicit local OTLP log projection of canonical prompt completion events. */\nexport function createOtlpPromptEvents(\n records: readonly IInteractiveSessionRecord[],\n version: string,\n observedAt: Date,\n): {\n readonly exported: number;\n readonly coverage: IPromptRootTraceCoverage;\n readonly payload: IOtlpPromptEvents;\n} {\n const traces = createOtlpPromptRootTraces(records, version);\n const spans = traces.payload.resourceSpans[0]?.scopeSpans[0]?.spans ?? [];\n if (spans.length === 0) {\n return { exported: 0, coverage: traces.coverage, payload: { resourceLogs: [] } };\n }\n const observedTimeUnixNano = observedUnixNano(observedAt);\n const logRecords: IOtlpLogRecord[] = spans.map((span) => {\n const outcomeAttribute = span.attributes.find((attribute) =>\n attribute.key === 'robota.prompt.outcome' ||\n attribute.key === 'robota.provider.outcome' ||\n attribute.key === 'robota.tool.outcome');\n const eventName: TEventName =\n span.name === 'robota.prompt_execution'\n ? 'robota.prompt_execution.completed'\n : span.name === 'robota.provider_call'\n ? 'robota.provider_call.completed'\n : 'robota.tool_body.completed';\n const severityNumber = span.status.code === 1 ? 9 : span.status.code === 2 ? 17 : 13;\n const severityText = severityNumber === 9 ? 'INFO' : severityNumber === 17 ? 'ERROR' : 'WARN';\n return {\n timeUnixNano: span.endTimeUnixNano,\n observedTimeUnixNano,\n severityNumber,\n severityText,\n eventName,\n traceId: span.traceId,\n spanId: span.spanId,\n attributes: outcomeAttribute && 'stringValue' in outcomeAttribute.value\n ? [{ key: outcomeAttribute.key, value: { stringValue: outcomeAttribute.value.stringValue } }]\n : [],\n };\n });\n return {\n exported: logRecords.length,\n coverage: traces.coverage,\n payload: {\n resourceLogs: [\n {\n resource: traces.payload.resourceSpans[0]!.resource,\n scopeLogs: [\n {\n scope: { name: '@robota-sdk/agent-session-analytics' },\n logRecords,\n },\n ],\n },\n ],\n },\n };\n}\n","import type {\n IPersonalUsageDimension,\n IPersonalUsageReport,\n} from '@robota-sdk/agent-interface-analytics';\n\nconst COST_DECIMAL_PLACES = 4;\n\nfunction dimensionLines(title: string, values: readonly IPersonalUsageDimension[]): string[] {\n return [\n '',\n title,\n ...(values.length === 0\n ? ['(none)']\n : values.map(\n (value) =>\n `${value.label} ${value.turns} turns ${value.totalTokens} tokens cost ${value.costStatus === 'unknown' ? 'unknown' : `$${value.costUsd.toFixed(COST_DECIMAL_PLACES)}`}`,\n )),\n ];\n}\n\n/** Render the provider-neutral personal-usage report for the terminal surface. */\nexport function formatPersonalUsageReport(report: IPersonalUsageReport): string {\n const cost =\n report.totals.costStatus === 'unknown'\n ? 'unknown'\n : `$${report.totals.costUsd.toFixed(COST_DECIMAL_PLACES)} (${report.totals.costStatus})`;\n return [\n `Personal usage — ${report.period} (${report.timezone})`,\n `${report.interval.startDate} → ${report.interval.endDate}`,\n `Sessions ${report.totals.sessions} · Turns ${report.totals.turns} · Tokens ${report.totals.totalTokens} (${report.totals.promptTokens} input / ${report.totals.completionTokens} output) · Cost ${cost}`,\n '',\n 'Daily usage',\n ...report.daily.map(\n (day) =>\n `${day.date}${day.partial ? ' (partial)' : ''} ${day.totals.turns} turns ${day.totals.totalTokens} tokens`,\n ),\n ...dimensionLines('By model', report.byModel),\n ...dimensionLines('By provider', report.byProvider),\n ...dimensionLines('By surface', report.bySurface),\n ...dimensionLines('By source', report.bySource),\n '',\n 'Activity',\n ...(report.byActivity.length === 0\n ? ['(none)']\n : report.byActivity.map(\n (activity) => `${activity.kind}:${activity.label} ${activity.count} calls`,\n )),\n '',\n `Coverage: legacy ${report.coverage.legacyObservations}, corrupt ${report.coverage.corruptSessions}, unsupported ${report.coverage.unsupportedSessions}, duplicates ${report.coverage.duplicateObservations}`,\n 'Local estimates from recorded sessions; costs are not an invoice.',\n ].join('\\n');\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,21 +1,32 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-session-analytics",
3
- "version": "3.0.0-beta.79",
3
+ "version": "3.0.0-beta.82",
4
4
  "description": "Session-log timing analysis and reporting for the Robota SDK (analyzeSession, aggregateReports, report formatters)",
5
5
  "type": "module",
6
6
  "main": "dist/node/index.js",
7
7
  "types": "dist/node/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/node/index.d.ts",
11
10
  "source": "./src/index.ts",
12
11
  "node": {
13
- "import": "./dist/node/index.js",
14
- "require": "./dist/node/index.cjs"
12
+ "import": {
13
+ "types": "./dist/node/index.d.ts",
14
+ "default": "./dist/node/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/node/index.d.cts",
18
+ "default": "./dist/node/index.cjs"
19
+ }
15
20
  },
16
21
  "default": {
17
- "import": "./dist/node/index.js",
18
- "require": "./dist/node/index.cjs"
22
+ "import": {
23
+ "types": "./dist/node/index.d.ts",
24
+ "default": "./dist/node/index.js"
25
+ },
26
+ "require": {
27
+ "types": "./dist/node/index.d.cts",
28
+ "default": "./dist/node/index.cjs"
29
+ }
19
30
  }
20
31
  }
21
32
  },
@@ -31,26 +42,30 @@
31
42
  "dist"
32
43
  ],
33
44
  "dependencies": {
34
- "@robota-sdk/agent-core": "3.0.0-beta.79",
35
- "@robota-sdk/agent-interface-transport": "3.0.0-beta.79"
45
+ "@robota-sdk/agent-core": "3.0.0-beta.82",
46
+ "@robota-sdk/agent-interface-analytics": "3.0.0-beta.82",
47
+ "@robota-sdk/agent-interface-session": "3.0.0-beta.82"
36
48
  },
37
49
  "devDependencies": {
38
50
  "rimraf": "^5.0.10",
39
- "tsdown": "^0.22.2",
40
- "typescript": "^5.9.3",
51
+ "tsdown": "^0.22.14",
52
+ "typescript": "^6.0.3",
41
53
  "vitest": "^3.2.6"
42
54
  },
43
55
  "license": "AGPL-3.0-only OR LicenseRef-Commercial",
44
56
  "publishConfig": {
45
57
  "access": "public"
46
58
  },
59
+ "volta": {
60
+ "extends": "../../package.json"
61
+ },
47
62
  "scripts": {
48
- "build": "tsdown",
49
- "build:js": "tsdown --no-dts",
50
- "build:types": "tsdown --dts",
63
+ "build": "rimraf dist && tsdown",
64
+ "build:js": "pnpm run build",
65
+ "build:types": "pnpm run build",
51
66
  "test": "vitest run --passWithNoTests",
52
67
  "test:coverage": "vitest run --coverage --passWithNoTests",
53
- "typecheck": "tsc --noEmit",
68
+ "typecheck": "tsgo --noEmit",
54
69
  "lint": "eslint src/ --ext .ts",
55
70
  "clean": "rimraf dist"
56
71
  }