proofscan 0.10.62 → 0.11.1
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.ja.md +1 -0
- package/README.md +2 -0
- package/dist/a2a/agent-card.d.ts +2 -0
- package/dist/a2a/agent-card.d.ts.map +1 -1
- package/dist/a2a/agent-card.js +2 -2
- package/dist/a2a/agent-card.js.map +1 -1
- package/dist/a2a/client.d.ts +74 -12
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +228 -29
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/normalizer.d.ts +4 -0
- package/dist/a2a/normalizer.d.ts.map +1 -1
- package/dist/a2a/normalizer.js +7 -4
- package/dist/a2a/normalizer.js.map +1 -1
- package/dist/a2a/session-manager.d.ts +81 -0
- package/dist/a2a/session-manager.d.ts.map +1 -0
- package/dist/a2a/session-manager.js +176 -0
- package/dist/a2a/session-manager.js.map +1 -0
- package/dist/a2a/types.d.ts +60 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +35 -10
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +12 -10
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/connectors.js +2 -2
- package/dist/commands/connectors.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/plans.js +1 -1
- package/dist/commands/plans.js.map +1 -1
- package/dist/commands/record.js +5 -4
- package/dist/commands/record.js.map +1 -1
- package/dist/commands/rpc.d.ts.map +1 -1
- package/dist/commands/rpc.js +90 -28
- package/dist/commands/rpc.js.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +8 -10
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/secrets.d.ts.map +1 -1
- package/dist/commands/secrets.js +11 -10
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/sessions.js +2 -2
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/summary.d.ts.map +1 -1
- package/dist/commands/summary.js +4 -2
- package/dist/commands/summary.js.map +1 -1
- package/dist/commands/task.d.ts +14 -0
- package/dist/commands/task.d.ts.map +1 -0
- package/dist/commands/task.js +520 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +68 -21
- package/dist/db/connection.js.map +1 -1
- package/dist/db/events-store.d.ts +307 -8
- package/dist/db/events-store.d.ts.map +1 -1
- package/dist/db/events-store.js +620 -26
- package/dist/db/events-store.js.map +1 -1
- package/dist/db/proofs-store.d.ts +8 -1
- package/dist/db/proofs-store.d.ts.map +1 -1
- package/dist/db/proofs-store.js +18 -8
- package/dist/db/proofs-store.js.map +1 -1
- package/dist/db/schema.d.ts +15 -3
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +150 -5
- package/dist/db/schema.js.map +1 -1
- package/dist/db/tool-analysis.d.ts +15 -3
- package/dist/db/tool-analysis.d.ts.map +1 -1
- package/dist/db/tool-analysis.js +35 -17
- package/dist/db/tool-analysis.js.map +1 -1
- package/dist/db/types.d.ts +64 -1
- package/dist/db/types.d.ts.map +1 -1
- package/dist/filter/fields.d.ts.map +1 -1
- package/dist/filter/fields.js +22 -0
- package/dist/filter/fields.js.map +1 -1
- package/dist/filter/parser.js +2 -2
- package/dist/filter/parser.js.map +1 -1
- package/dist/filter/types.d.ts +1 -1
- package/dist/filter/types.d.ts.map +1 -1
- package/dist/html/analytics.test.ts +682 -0
- package/dist/html/analytics.ts +499 -0
- package/dist/html/browser.ts +39 -0
- package/dist/html/index.ts +97 -0
- package/dist/html/rpc-inspector.test.ts +529 -0
- package/dist/html/rpc-inspector.ts +1700 -0
- package/dist/html/templates.js +4 -4
- package/dist/html/templates.js.map +1 -1
- package/dist/html/templates.test.ts +861 -0
- package/dist/html/templates.ts +3163 -0
- package/dist/html/trace-viewer.html +624 -0
- package/dist/html/types.d.ts +3 -3
- package/dist/html/types.d.ts.map +1 -1
- package/dist/html/types.ts +491 -0
- package/dist/html/utils.ts +107 -0
- package/dist/monitor/data/connectors.d.ts.map +1 -1
- package/dist/monitor/data/connectors.js +113 -8
- package/dist/monitor/data/connectors.js.map +1 -1
- package/dist/monitor/data/popl.js +2 -2
- package/dist/monitor/data/popl.js.map +1 -1
- package/dist/monitor/routes/api.js +2 -2
- package/dist/monitor/routes/api.js.map +1 -1
- package/dist/monitor/routes/connectors.js +15 -15
- package/dist/monitor/routes/connectors.js.map +1 -1
- package/dist/monitor/routes/popl.js +5 -5
- package/dist/monitor/routes/popl.js.map +1 -1
- package/dist/monitor/templates/components.js +2 -2
- package/dist/monitor/templates/components.js.map +1 -1
- package/dist/monitor/templates/popl.js +4 -4
- package/dist/monitor/templates/popl.js.map +1 -1
- package/dist/monitor/types.d.ts +2 -2
- package/dist/monitor/types.d.ts.map +1 -1
- package/dist/proxy/bridge-utils.d.ts +41 -0
- package/dist/proxy/bridge-utils.d.ts.map +1 -0
- package/dist/proxy/bridge-utils.js +60 -0
- package/dist/proxy/bridge-utils.js.map +1 -0
- package/dist/proxy/ipc-client.d.ts.map +1 -1
- package/dist/proxy/ipc-client.js +1 -2
- package/dist/proxy/ipc-client.js.map +1 -1
- package/dist/proxy/ipc-server.d.ts.map +1 -1
- package/dist/proxy/ipc-server.js +4 -2
- package/dist/proxy/ipc-server.js.map +1 -1
- package/dist/proxy/mcp-server.d.ts +31 -0
- package/dist/proxy/mcp-server.d.ts.map +1 -1
- package/dist/proxy/mcp-server.js +393 -4
- package/dist/proxy/mcp-server.js.map +1 -1
- package/dist/proxy/types.d.ts +95 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/secrets/management.d.ts +2 -2
- package/dist/secrets/management.d.ts.map +1 -1
- package/dist/secrets/management.js +7 -7
- package/dist/secrets/management.js.map +1 -1
- package/dist/shell/completer.d.ts.map +1 -1
- package/dist/shell/completer.js +16 -0
- package/dist/shell/completer.js.map +1 -1
- package/dist/shell/context-applicator.d.ts.map +1 -1
- package/dist/shell/context-applicator.js +32 -0
- package/dist/shell/context-applicator.js.map +1 -1
- package/dist/shell/filter-mappers.d.ts +5 -1
- package/dist/shell/filter-mappers.d.ts.map +1 -1
- package/dist/shell/filter-mappers.js +12 -0
- package/dist/shell/filter-mappers.js.map +1 -1
- package/dist/shell/find-command.js +13 -13
- package/dist/shell/find-command.js.map +1 -1
- package/dist/shell/inscribe-commands.js +5 -5
- package/dist/shell/inscribe-commands.js.map +1 -1
- package/dist/shell/pager/less-pager.d.ts +1 -1
- package/dist/shell/pager/less-pager.d.ts.map +1 -1
- package/dist/shell/pager/less-pager.js +5 -2
- package/dist/shell/pager/less-pager.js.map +1 -1
- package/dist/shell/pager/more-pager.d.ts +1 -1
- package/dist/shell/pager/more-pager.d.ts.map +1 -1
- package/dist/shell/pager/more-pager.js +3 -2
- package/dist/shell/pager/more-pager.js.map +1 -1
- package/dist/shell/pager/renderer.d.ts.map +1 -1
- package/dist/shell/pager/renderer.js +66 -15
- package/dist/shell/pager/renderer.js.map +1 -1
- package/dist/shell/pager/types.d.ts +5 -2
- package/dist/shell/pager/types.d.ts.map +1 -1
- package/dist/shell/pager/utils.d.ts +5 -2
- package/dist/shell/pager/utils.d.ts.map +1 -1
- package/dist/shell/pager/utils.js +14 -17
- package/dist/shell/pager/utils.js.map +1 -1
- package/dist/shell/pipeline-types.d.ts +12 -4
- package/dist/shell/pipeline-types.d.ts.map +1 -1
- package/dist/shell/ref-commands.js +7 -7
- package/dist/shell/ref-commands.js.map +1 -1
- package/dist/shell/ref-resolver.d.ts +15 -15
- package/dist/shell/ref-resolver.d.ts.map +1 -1
- package/dist/shell/ref-resolver.js +34 -20
- package/dist/shell/ref-resolver.js.map +1 -1
- package/dist/shell/repl.d.ts +25 -0
- package/dist/shell/repl.d.ts.map +1 -1
- package/dist/shell/repl.js +285 -51
- package/dist/shell/repl.js.map +1 -1
- package/dist/shell/router-commands.d.ts +30 -0
- package/dist/shell/router-commands.d.ts.map +1 -1
- package/dist/shell/router-commands.js +1011 -62
- package/dist/shell/router-commands.js.map +1 -1
- package/dist/shell/selector.d.ts +1 -1
- package/dist/shell/selector.d.ts.map +1 -1
- package/dist/shell/selector.js +1 -1
- package/dist/shell/selector.js.map +1 -1
- package/dist/shell/types.d.ts.map +1 -1
- package/dist/shell/types.js +3 -1
- package/dist/shell/types.js.map +1 -1
- package/dist/shell/where-command.d.ts.map +1 -1
- package/dist/shell/where-command.js +19 -3
- package/dist/shell/where-command.js.map +1 -1
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +7 -1
- package/dist/utils/output.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics computation for Connector HTML reports (Phase 5.2)
|
|
3
|
+
*
|
|
4
|
+
* Computes KPIs, heatmap, latency histogram, and top tools
|
|
5
|
+
* from session report data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
HtmlSessionReportV1,
|
|
10
|
+
SessionRpcDetail,
|
|
11
|
+
HtmlConnectorAnalyticsV1,
|
|
12
|
+
HtmlConnectorKpis,
|
|
13
|
+
HtmlHeatmapData,
|
|
14
|
+
HtmlHeatmapCell,
|
|
15
|
+
HtmlLatencyHistogram,
|
|
16
|
+
HtmlLatencyBucket,
|
|
17
|
+
HtmlMethodLatencyData,
|
|
18
|
+
HtmlMethodLatencyEntry,
|
|
19
|
+
HtmlTopToolsData,
|
|
20
|
+
HtmlTopTool,
|
|
21
|
+
HtmlMethodDistribution,
|
|
22
|
+
HtmlMethodSlice,
|
|
23
|
+
} from './types.js';
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Constants
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Minimum sample size for P95 calculation.
|
|
31
|
+
* Below this threshold, P95 returns null to avoid statistical noise.
|
|
32
|
+
*/
|
|
33
|
+
export const P95_MIN_SAMPLES = 20;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fixed latency histogram buckets.
|
|
37
|
+
* to_ms: null means +infinity (for "1000+" bucket)
|
|
38
|
+
*/
|
|
39
|
+
export const LATENCY_BUCKETS = [
|
|
40
|
+
{ label: '0-10', from_ms: 0, to_ms: 10 },
|
|
41
|
+
{ label: '10-25', from_ms: 10, to_ms: 25 },
|
|
42
|
+
{ label: '25-50', from_ms: 25, to_ms: 50 },
|
|
43
|
+
{ label: '50-100', from_ms: 50, to_ms: 100 },
|
|
44
|
+
{ label: '100-250', from_ms: 100, to_ms: 250 },
|
|
45
|
+
{ label: '250-500', from_ms: 250, to_ms: 500 },
|
|
46
|
+
{ label: '500-1000', from_ms: 500, to_ms: 1000 },
|
|
47
|
+
{ label: '1000+', from_ms: 1000, to_ms: null }, // null = +infinity
|
|
48
|
+
] as const;
|
|
49
|
+
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Main Entry Point
|
|
52
|
+
// ============================================================================
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Compute all analytics for a connector report.
|
|
56
|
+
*/
|
|
57
|
+
export function computeConnectorAnalytics(args: {
|
|
58
|
+
sessionReports: Record<string, HtmlSessionReportV1>;
|
|
59
|
+
sessionsTotal: number;
|
|
60
|
+
sessionsDisplayed: number;
|
|
61
|
+
}): HtmlConnectorAnalyticsV1 {
|
|
62
|
+
const { sessionReports, sessionsTotal, sessionsDisplayed } = args;
|
|
63
|
+
|
|
64
|
+
// Collect all RPCs from all sessions
|
|
65
|
+
const allRpcs: SessionRpcDetail[] = [];
|
|
66
|
+
for (const report of Object.values(sessionReports)) {
|
|
67
|
+
allRpcs.push(...report.rpcs);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Compute each analytics component
|
|
71
|
+
const topTools = computeTopTools(allRpcs);
|
|
72
|
+
const kpis = computeKpis(allRpcs, sessionsTotal, sessionsDisplayed, topTools);
|
|
73
|
+
const heatmap = computeHeatmap(allRpcs);
|
|
74
|
+
const latency = computeLatencyHistogram(allRpcs);
|
|
75
|
+
const methodLatency = computeMethodLatency(allRpcs);
|
|
76
|
+
const methodDistribution = computeMethodDistribution(allRpcs);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
kpis,
|
|
80
|
+
heatmap,
|
|
81
|
+
latency,
|
|
82
|
+
method_latency: methodLatency,
|
|
83
|
+
top_tools: topTools,
|
|
84
|
+
method_distribution: methodDistribution,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// KPI Computation
|
|
90
|
+
// ============================================================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Compute KPI metrics from RPCs.
|
|
94
|
+
*/
|
|
95
|
+
function computeKpis(
|
|
96
|
+
rpcs: SessionRpcDetail[],
|
|
97
|
+
sessionsTotal: number,
|
|
98
|
+
sessionsDisplayed: number,
|
|
99
|
+
topTools: HtmlTopToolsData
|
|
100
|
+
): HtmlConnectorKpis {
|
|
101
|
+
// Count by status
|
|
102
|
+
let okCount = 0;
|
|
103
|
+
let errCount = 0;
|
|
104
|
+
let pendingCount = 0;
|
|
105
|
+
|
|
106
|
+
// Latency collection
|
|
107
|
+
const latencies: number[] = [];
|
|
108
|
+
|
|
109
|
+
// Bytes
|
|
110
|
+
let totalRequestBytes = 0;
|
|
111
|
+
let totalResponseBytes = 0;
|
|
112
|
+
|
|
113
|
+
for (const rpc of rpcs) {
|
|
114
|
+
// Status counting
|
|
115
|
+
switch (rpc.status) {
|
|
116
|
+
case 'OK':
|
|
117
|
+
okCount++;
|
|
118
|
+
break;
|
|
119
|
+
case 'ERR':
|
|
120
|
+
errCount++;
|
|
121
|
+
break;
|
|
122
|
+
case 'PENDING':
|
|
123
|
+
pendingCount++;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Latency
|
|
128
|
+
if (rpc.latency_ms !== null) {
|
|
129
|
+
latencies.push(rpc.latency_ms);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Bytes
|
|
133
|
+
totalRequestBytes += rpc.request.size;
|
|
134
|
+
totalResponseBytes += rpc.response.size;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Latency statistics
|
|
138
|
+
const avgLatency = latencies.length > 0 ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : null;
|
|
139
|
+
const p95Latency = computeP95(latencies);
|
|
140
|
+
const maxLatency = latencies.length > 0 ? Math.max(...latencies) : null;
|
|
141
|
+
|
|
142
|
+
// Top tool from topTools
|
|
143
|
+
const topToolName = topTools.items.length > 0 ? topTools.items[0].name : null;
|
|
144
|
+
const topToolCalls = topTools.items.length > 0 ? topTools.items[0].count : null;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
rpc_total: rpcs.length,
|
|
148
|
+
rpc_ok: okCount,
|
|
149
|
+
rpc_err: errCount,
|
|
150
|
+
rpc_pending: pendingCount,
|
|
151
|
+
avg_latency_ms: avgLatency,
|
|
152
|
+
p95_latency_ms: p95Latency,
|
|
153
|
+
max_latency_ms: maxLatency,
|
|
154
|
+
total_request_bytes: totalRequestBytes,
|
|
155
|
+
total_response_bytes: totalResponseBytes,
|
|
156
|
+
sessions_total: sessionsTotal,
|
|
157
|
+
sessions_displayed: sessionsDisplayed,
|
|
158
|
+
top_tool_name: topToolName,
|
|
159
|
+
top_tool_calls: topToolCalls,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Compute P95 latency using nearest-rank method.
|
|
165
|
+
* Returns null if sample size < P95_MIN_SAMPLES.
|
|
166
|
+
*
|
|
167
|
+
* Algorithm: k = ceil(0.95 * n), take sorted[k-1]
|
|
168
|
+
*/
|
|
169
|
+
function computeP95(latencies: number[]): number | null {
|
|
170
|
+
const n = latencies.length;
|
|
171
|
+
if (n < P95_MIN_SAMPLES) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const sorted = [...latencies].sort((a, b) => a - b);
|
|
176
|
+
const k = Math.ceil(0.95 * n);
|
|
177
|
+
return sorted[k - 1];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// Heatmap Computation
|
|
182
|
+
// ============================================================================
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Compute activity heatmap from RPCs.
|
|
186
|
+
* Groups RPCs by UTC date and fills gaps with zero-count days.
|
|
187
|
+
*
|
|
188
|
+
* Note: request_ts is stored in UTC ISO8601 format (Z suffix),
|
|
189
|
+
* so slice(0, 10) gives the UTC date.
|
|
190
|
+
*/
|
|
191
|
+
function computeHeatmap(rpcs: SessionRpcDetail[]): HtmlHeatmapData {
|
|
192
|
+
if (rpcs.length === 0) {
|
|
193
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
194
|
+
return {
|
|
195
|
+
start_date: today,
|
|
196
|
+
end_date: today,
|
|
197
|
+
cells: [{ date: today, count: 0 }],
|
|
198
|
+
max_count: 0,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Count RPCs per UTC date
|
|
203
|
+
const dateCounts = new Map<string, number>();
|
|
204
|
+
for (const rpc of rpcs) {
|
|
205
|
+
// request_ts is stored in UTC ISO8601 (Z suffix), so slice(0, 10) gives UTC date
|
|
206
|
+
const date = rpc.request_ts.slice(0, 10);
|
|
207
|
+
dateCounts.set(date, (dateCounts.get(date) || 0) + 1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Find date range
|
|
211
|
+
const dates = Array.from(dateCounts.keys()).sort();
|
|
212
|
+
const startDate = dates[0];
|
|
213
|
+
const endDate = dates[dates.length - 1];
|
|
214
|
+
|
|
215
|
+
// Fill gaps with zero-count days
|
|
216
|
+
const cells: HtmlHeatmapCell[] = [];
|
|
217
|
+
const current = new Date(startDate + 'T00:00:00Z');
|
|
218
|
+
const end = new Date(endDate + 'T00:00:00Z');
|
|
219
|
+
|
|
220
|
+
while (current <= end) {
|
|
221
|
+
const dateStr = current.toISOString().slice(0, 10);
|
|
222
|
+
cells.push({
|
|
223
|
+
date: dateStr,
|
|
224
|
+
count: dateCounts.get(dateStr) || 0,
|
|
225
|
+
});
|
|
226
|
+
current.setUTCDate(current.getUTCDate() + 1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Calculate max count for intensity scaling
|
|
230
|
+
const maxCount = Math.max(...cells.map((c) => c.count));
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
start_date: startDate,
|
|
234
|
+
end_date: endDate,
|
|
235
|
+
cells,
|
|
236
|
+
max_count: maxCount,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// Latency Histogram Computation
|
|
242
|
+
// ============================================================================
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Compute latency histogram using fixed buckets.
|
|
246
|
+
*/
|
|
247
|
+
function computeLatencyHistogram(rpcs: SessionRpcDetail[]): HtmlLatencyHistogram {
|
|
248
|
+
// Initialize buckets with zero counts
|
|
249
|
+
const buckets: HtmlLatencyBucket[] = LATENCY_BUCKETS.map((b) => ({
|
|
250
|
+
label: b.label,
|
|
251
|
+
from_ms: b.from_ms,
|
|
252
|
+
to_ms: b.to_ms,
|
|
253
|
+
count: 0,
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
let sampleSize = 0;
|
|
257
|
+
let excludedCount = 0;
|
|
258
|
+
|
|
259
|
+
for (const rpc of rpcs) {
|
|
260
|
+
if (rpc.latency_ms === null) {
|
|
261
|
+
excludedCount++;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
sampleSize++;
|
|
266
|
+
const latency = rpc.latency_ms;
|
|
267
|
+
|
|
268
|
+
// Find matching bucket
|
|
269
|
+
// Bucket condition: from_ms <= latency < to_ms (or to_ms is null for +infinity)
|
|
270
|
+
for (const bucket of buckets) {
|
|
271
|
+
const inLowerBound = latency >= bucket.from_ms;
|
|
272
|
+
const inUpperBound = bucket.to_ms === null || latency < bucket.to_ms;
|
|
273
|
+
if (inLowerBound && inUpperBound) {
|
|
274
|
+
bucket.count++;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
buckets,
|
|
282
|
+
sample_size: sampleSize,
|
|
283
|
+
excluded_count: excludedCount,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// Method Latency Computation
|
|
289
|
+
// ============================================================================
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Compute method-based latency data for bar chart.
|
|
293
|
+
* Groups latencies by method name (e.g., initialize, tools/list, tools/call).
|
|
294
|
+
*/
|
|
295
|
+
function computeMethodLatency(rpcs: SessionRpcDetail[]): HtmlMethodLatencyData {
|
|
296
|
+
if (rpcs.length === 0) {
|
|
297
|
+
return {
|
|
298
|
+
methods: [],
|
|
299
|
+
sample_size: 0,
|
|
300
|
+
max_latency_ms: 0,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Group latencies by method
|
|
305
|
+
const methodLatencies = new Map<string, number[]>();
|
|
306
|
+
let sampleSize = 0;
|
|
307
|
+
let maxLatency = 0;
|
|
308
|
+
|
|
309
|
+
for (const rpc of rpcs) {
|
|
310
|
+
if (rpc.latency_ms === null) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
sampleSize++;
|
|
315
|
+
if (rpc.latency_ms > maxLatency) {
|
|
316
|
+
maxLatency = rpc.latency_ms;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const existing = methodLatencies.get(rpc.method);
|
|
320
|
+
if (existing) {
|
|
321
|
+
existing.push(rpc.latency_ms);
|
|
322
|
+
} else {
|
|
323
|
+
methodLatencies.set(rpc.method, [rpc.latency_ms]);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Convert to entries with statistics
|
|
328
|
+
const methods: HtmlMethodLatencyEntry[] = [];
|
|
329
|
+
|
|
330
|
+
for (const [method, latencies] of methodLatencies.entries()) {
|
|
331
|
+
const sorted = [...latencies].sort((a, b) => a - b);
|
|
332
|
+
const count = latencies.length;
|
|
333
|
+
const min = sorted[0];
|
|
334
|
+
const max = sorted[sorted.length - 1];
|
|
335
|
+
const avg = Math.round(latencies.reduce((a, b) => a + b, 0) / count);
|
|
336
|
+
// Median (P50)
|
|
337
|
+
const p50Idx = Math.floor(count / 2);
|
|
338
|
+
const p50 = count % 2 === 0
|
|
339
|
+
? Math.round((sorted[p50Idx - 1] + sorted[p50Idx]) / 2)
|
|
340
|
+
: sorted[p50Idx];
|
|
341
|
+
|
|
342
|
+
methods.push({
|
|
343
|
+
method,
|
|
344
|
+
latencies: sorted,
|
|
345
|
+
min_ms: min,
|
|
346
|
+
max_ms: max,
|
|
347
|
+
avg_ms: avg,
|
|
348
|
+
p50_ms: p50,
|
|
349
|
+
count,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Sort by total call count descending, take top 6 methods
|
|
354
|
+
methods.sort((a, b) => b.count - a.count);
|
|
355
|
+
const topMethods = methods.slice(0, 6);
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
methods: topMethods,
|
|
359
|
+
sample_size: sampleSize,
|
|
360
|
+
max_latency_ms: maxLatency,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// Top Tools Computation
|
|
366
|
+
// ============================================================================
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Compute top 5 tools from tools/call RPCs.
|
|
370
|
+
*
|
|
371
|
+
* Tool name extraction fallback chain: params.name ?? params.tool ?? params.toolName
|
|
372
|
+
* If none found, the RPC is skipped (not counted as "unknown").
|
|
373
|
+
*/
|
|
374
|
+
function computeTopTools(rpcs: SessionRpcDetail[]): HtmlTopToolsData {
|
|
375
|
+
const toolCounts = new Map<string, number>();
|
|
376
|
+
let totalCalls = 0;
|
|
377
|
+
|
|
378
|
+
for (const rpc of rpcs) {
|
|
379
|
+
if (rpc.method !== 'tools/call') {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Extract tool name from request payload
|
|
384
|
+
const toolName = extractToolName(rpc.request.json);
|
|
385
|
+
if (toolName === null) {
|
|
386
|
+
continue; // Skip if no tool name found
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
toolCounts.set(toolName, (toolCounts.get(toolName) || 0) + 1);
|
|
390
|
+
totalCalls++;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Sort by count descending and take top 5
|
|
394
|
+
const sorted = Array.from(toolCounts.entries())
|
|
395
|
+
.sort((a, b) => b[1] - a[1])
|
|
396
|
+
.slice(0, 5);
|
|
397
|
+
|
|
398
|
+
const items: HtmlTopTool[] = sorted.map(([name, count]) => ({
|
|
399
|
+
name,
|
|
400
|
+
count,
|
|
401
|
+
pct: totalCalls > 0 ? Math.round((count / totalCalls) * 100) : 0,
|
|
402
|
+
}));
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
items,
|
|
406
|
+
total_calls: totalCalls,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Extract tool name from request JSON.
|
|
412
|
+
* Fallback chain: params.name ?? params.tool ?? params.toolName
|
|
413
|
+
*/
|
|
414
|
+
function extractToolName(json: unknown): string | null {
|
|
415
|
+
if (json === null || typeof json !== 'object') {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const obj = json as Record<string, unknown>;
|
|
420
|
+
const params = obj.params;
|
|
421
|
+
|
|
422
|
+
if (params === null || typeof params !== 'object') {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const p = params as Record<string, unknown>;
|
|
427
|
+
|
|
428
|
+
// Fallback chain: params.name ?? params.tool ?? params.toolName
|
|
429
|
+
if (typeof p.name === 'string' && p.name.length > 0) {
|
|
430
|
+
return p.name;
|
|
431
|
+
}
|
|
432
|
+
if (typeof p.tool === 'string' && p.tool.length > 0) {
|
|
433
|
+
return p.tool;
|
|
434
|
+
}
|
|
435
|
+
if (typeof p.toolName === 'string' && p.toolName.length > 0) {
|
|
436
|
+
return p.toolName;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ============================================================================
|
|
443
|
+
// Method Distribution Computation
|
|
444
|
+
// ============================================================================
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Compute method distribution for donut chart.
|
|
448
|
+
* Shows top 5 methods + "Others" bucket.
|
|
449
|
+
*/
|
|
450
|
+
function computeMethodDistribution(rpcs: SessionRpcDetail[]): HtmlMethodDistribution {
|
|
451
|
+
if (rpcs.length === 0) {
|
|
452
|
+
return {
|
|
453
|
+
slices: [],
|
|
454
|
+
total_rpcs: 0,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Count by method
|
|
459
|
+
const methodCounts = new Map<string, number>();
|
|
460
|
+
for (const rpc of rpcs) {
|
|
461
|
+
methodCounts.set(rpc.method, (methodCounts.get(rpc.method) || 0) + 1);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Sort by count descending
|
|
465
|
+
const sorted = Array.from(methodCounts.entries()).sort((a, b) => b[1] - a[1]);
|
|
466
|
+
|
|
467
|
+
const totalRpcs = rpcs.length;
|
|
468
|
+
const slices: HtmlMethodSlice[] = [];
|
|
469
|
+
|
|
470
|
+
// Take top 5
|
|
471
|
+
const top5 = sorted.slice(0, 5);
|
|
472
|
+
let top5Total = 0;
|
|
473
|
+
|
|
474
|
+
for (const [method, count] of top5) {
|
|
475
|
+
top5Total += count;
|
|
476
|
+
slices.push({
|
|
477
|
+
method,
|
|
478
|
+
count,
|
|
479
|
+
pct: Math.round((count / totalRpcs) * 100),
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Add "Others" if there are more than 5 methods
|
|
484
|
+
if (sorted.length > 5) {
|
|
485
|
+
const othersCount = totalRpcs - top5Total;
|
|
486
|
+
if (othersCount > 0) {
|
|
487
|
+
slices.push({
|
|
488
|
+
method: 'Others',
|
|
489
|
+
count: othersCount,
|
|
490
|
+
pct: Math.round((othersCount / totalRpcs) * 100),
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
slices,
|
|
497
|
+
total_rpcs: totalRpcs,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Utility (Phase 5.0)
|
|
3
|
+
*
|
|
4
|
+
* Cross-platform browser opening functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { exec } from 'child_process';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Open a file in the default browser
|
|
11
|
+
*
|
|
12
|
+
* Platform-specific commands:
|
|
13
|
+
* - macOS: open
|
|
14
|
+
* - Windows: cmd /c start "" (handles paths with spaces)
|
|
15
|
+
* - Linux: xdg-open
|
|
16
|
+
*/
|
|
17
|
+
export function openInBrowser(filePath: string): Promise<void> {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
let cmd: string;
|
|
20
|
+
|
|
21
|
+
if (process.platform === 'darwin') {
|
|
22
|
+
cmd = `open "${filePath}"`;
|
|
23
|
+
} else if (process.platform === 'win32') {
|
|
24
|
+
// Windows: cmd /c start "" to handle paths with spaces
|
|
25
|
+
cmd = `cmd /c start "" "${filePath}"`;
|
|
26
|
+
} else {
|
|
27
|
+
// Linux and others
|
|
28
|
+
cmd = `xdg-open "${filePath}"`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
exec(cmd, (error) => {
|
|
32
|
+
if (error) {
|
|
33
|
+
reject(error);
|
|
34
|
+
} else {
|
|
35
|
+
resolve();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Export Module (Phase 5.0)
|
|
3
|
+
*
|
|
4
|
+
* Exports standalone HTML reports for RPC and Session data.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export {
|
|
9
|
+
HTML_REPORT_SCHEMA_VERSION,
|
|
10
|
+
DEFAULT_EMBED_MAX_BYTES,
|
|
11
|
+
TRUNCATION_PREVIEW_LENGTH,
|
|
12
|
+
SHORT_ID_LENGTH,
|
|
13
|
+
DEFAULT_EXPORT_OPTIONS,
|
|
14
|
+
toRpcStatus,
|
|
15
|
+
getStatusSymbol,
|
|
16
|
+
createPayloadData,
|
|
17
|
+
getRpcHtmlFilename,
|
|
18
|
+
getSessionHtmlFilename,
|
|
19
|
+
getSpillFilename,
|
|
20
|
+
getConnectorHtmlFilename,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
|
|
23
|
+
export type {
|
|
24
|
+
HtmlReportMeta,
|
|
25
|
+
RpcStatus,
|
|
26
|
+
HtmlExportOptions,
|
|
27
|
+
PayloadData,
|
|
28
|
+
HtmlRpcData,
|
|
29
|
+
HtmlRpcReportV1,
|
|
30
|
+
SessionRpcDetail,
|
|
31
|
+
HtmlSessionData,
|
|
32
|
+
HtmlSessionReportV1,
|
|
33
|
+
// Connector HTML types (Phase 5.1)
|
|
34
|
+
HtmlMcpCapabilities,
|
|
35
|
+
HtmlMcpServerInfo,
|
|
36
|
+
HtmlConnectorInfo,
|
|
37
|
+
HtmlConnectorSessionRow,
|
|
38
|
+
HtmlConnectorReportV1,
|
|
39
|
+
// Connector Analytics types (Phase 5.2)
|
|
40
|
+
HtmlConnectorKpis,
|
|
41
|
+
HtmlHeatmapCell,
|
|
42
|
+
HtmlHeatmapData,
|
|
43
|
+
HtmlLatencyBucket,
|
|
44
|
+
HtmlLatencyHistogram,
|
|
45
|
+
HtmlTopTool,
|
|
46
|
+
HtmlTopToolsData,
|
|
47
|
+
HtmlMethodSlice,
|
|
48
|
+
HtmlMethodDistribution,
|
|
49
|
+
HtmlConnectorAnalyticsV1,
|
|
50
|
+
} from './types.js';
|
|
51
|
+
|
|
52
|
+
// Templates
|
|
53
|
+
export {
|
|
54
|
+
escapeHtml,
|
|
55
|
+
escapeJsonForScript,
|
|
56
|
+
generateRpcHtml,
|
|
57
|
+
generateSessionHtml,
|
|
58
|
+
generateConnectorHtml,
|
|
59
|
+
renderHeatmap,
|
|
60
|
+
renderMethodDistribution,
|
|
61
|
+
} from './templates.js';
|
|
62
|
+
|
|
63
|
+
// Browser
|
|
64
|
+
export { openInBrowser } from './browser.js';
|
|
65
|
+
|
|
66
|
+
// Utilities
|
|
67
|
+
export {
|
|
68
|
+
getPackageVersion,
|
|
69
|
+
validateOutputPath,
|
|
70
|
+
validateEmbedMaxBytes,
|
|
71
|
+
ensureOutputDir,
|
|
72
|
+
safeWriteFile,
|
|
73
|
+
} from './utils.js';
|
|
74
|
+
|
|
75
|
+
// Analytics (Phase 5.2)
|
|
76
|
+
export { computeConnectorAnalytics, LATENCY_BUCKETS, P95_MIN_SAMPLES } from './analytics.js';
|
|
77
|
+
|
|
78
|
+
// RPC Inspector (Phase 11.5)
|
|
79
|
+
export {
|
|
80
|
+
escapeJsonPointer,
|
|
81
|
+
renderJsonWithPaths,
|
|
82
|
+
renderMethodSummary,
|
|
83
|
+
renderRequestSummary,
|
|
84
|
+
renderResponseSummary,
|
|
85
|
+
renderSummaryRowsHtml,
|
|
86
|
+
registerMethodHandler,
|
|
87
|
+
getRpcInspectorStyles,
|
|
88
|
+
getRpcInspectorScript,
|
|
89
|
+
} from './rpc-inspector.js';
|
|
90
|
+
|
|
91
|
+
export type {
|
|
92
|
+
JsonPointerTarget,
|
|
93
|
+
JsonPointer,
|
|
94
|
+
SummaryRow,
|
|
95
|
+
MethodSummary,
|
|
96
|
+
MethodSummaryHandler,
|
|
97
|
+
} from './types.js';
|