autotel-devtools 23.1.0 → 24.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +2 -2
- package/dist/cli.js +2 -2
- package/dist/{error-aggregator-B52JI8jL.d.ts → error-aggregator-BzhW482_.d.ts} +1 -1
- package/dist/{error-aggregator-DSwUvgFF.d.cts → error-aggregator-DkUrfGol.d.cts} +1 -1
- package/dist/{exporter-C88W22vY.d.ts → exporter-BRSxg3Du.d.ts} +25 -2
- package/dist/{exporter-OoWkSMCR.d.cts → exporter-BgjMeWvt.d.cts} +25 -2
- package/dist/fullpage.global.js +37 -30
- package/dist/genai/index.d.cts +1 -1
- package/dist/genai/index.d.ts +1 -1
- package/dist/{grpc-D0B3P9sI.cjs → grpc-AXr0CnZ_.cjs} +1 -1
- package/dist/{grpc-DY-C1jSU.js → grpc-C35twtEj.js} +1 -1
- package/dist/{http-1afd_01N.cjs → http-Dc57PtwQ.cjs} +256 -0
- package/dist/{http-gk1xapnA.js → http-JTSqQPVJ.js} +257 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/server/exporter.d.cts +1 -1
- package/dist/server/exporter.d.ts +1 -1
- package/dist/server/index.cjs +2 -2
- package/dist/server/index.d.cts +4 -4
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +2 -2
- package/dist/{types-DM8y4A9Z.d.ts → types-Do48caKG.d.ts} +1 -1
- package/dist/{types-B0tjwFqj.d.cts → types-ZU74Na4z.d.cts} +1 -1
- package/dist/types-hVSf0DoT.d.cts +138 -0
- package/dist/types-hVSf0DoT.d.ts +138 -0
- package/dist/widget.global.js +14 -14
- package/dist/wire/index.d.cts +1 -1
- package/dist/wire/index.d.ts +1 -1
- package/package.json +2 -2
- package/dist/types-DHDvZnnn.d.cts +0 -47
- package/dist/types-DHDvZnnn.d.ts +0 -47
package/dist/genai/index.d.cts
CHANGED
package/dist/genai/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as decodeOtlpTraceRequest, o as decodeOtlpLogsRequest, s as decodeOtlpMetricsRequest } from "./http-
|
|
1
|
+
import { c as decodeOtlpTraceRequest, o as decodeOtlpLogsRequest, s as decodeOtlpMetricsRequest } from "./http-JTSqQPVJ.js";
|
|
2
2
|
import { Server, ServerCredentials, status } from "@grpc/grpc-js";
|
|
3
3
|
|
|
4
4
|
//#region src/server/grpc.ts
|
|
@@ -316,6 +316,200 @@ var ErrorAggregator = class {
|
|
|
316
316
|
}
|
|
317
317
|
};
|
|
318
318
|
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/server/webmcp-aggregator.ts
|
|
321
|
+
/** Span names this fold consumes. Everything else in the trace is ignored. */
|
|
322
|
+
const INSTALL = "webmcp.install";
|
|
323
|
+
const REGISTER = "webmcp.tool.register";
|
|
324
|
+
const EXECUTE = "webmcp.tool.execute";
|
|
325
|
+
const WITHDRAW = "webmcp.tool.withdraw";
|
|
326
|
+
/** How many recent executions a tool carries. Enough to see a pattern, not a log. */
|
|
327
|
+
const RECENT_CALLS = 5;
|
|
328
|
+
/**
|
|
329
|
+
* `{"content":[{"type":"text","text":` + `}]}` and the quoting around it.
|
|
330
|
+
*
|
|
331
|
+
* Chrome does not unwrap the MCP envelope, so an enveloped result spends these
|
|
332
|
+
* bytes of the agent's context on structure carrying no information. Measured
|
|
333
|
+
* rather than derived: 45 bytes wrapped versus 13 plain for the same text.
|
|
334
|
+
*/
|
|
335
|
+
const ENVELOPE_OVERHEAD_BYTES = 32;
|
|
336
|
+
const median = (values) => {
|
|
337
|
+
if (values.length === 0) return 0;
|
|
338
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
339
|
+
const mid = Math.floor(sorted.length / 2);
|
|
340
|
+
return sorted.length % 2 === 0 ? Math.round((sorted[mid - 1] + sorted[mid]) / 2) : sorted[mid];
|
|
341
|
+
};
|
|
342
|
+
const csv = (value) => {
|
|
343
|
+
const raw = require_json_fields.asString(value);
|
|
344
|
+
if (!raw) return [];
|
|
345
|
+
return raw.split(",").map((part) => part.trim()).filter(Boolean);
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Spans with no installation id — emitted by an `autotel-webmcp` older than the
|
|
349
|
+
* lifecycle release. Grouping them under one key per service keeps them
|
|
350
|
+
* readable as a single legacy installation rather than scattering one
|
|
351
|
+
* installation per tool.
|
|
352
|
+
*/
|
|
353
|
+
const UNKNOWN_INSTALLATION = "unknown";
|
|
354
|
+
const isWebMcpSpan = (span) => span.name === INSTALL || span.name === REGISTER || span.name === EXECUTE || span.name === WITHDRAW;
|
|
355
|
+
function foldWebMcpTools(traces, activityWindow) {
|
|
356
|
+
const tools = /* @__PURE__ */ new Map();
|
|
357
|
+
/** Installations seen, and whether each one ever registered anything. */
|
|
358
|
+
const installations = /* @__PURE__ */ new Map();
|
|
359
|
+
const latestInstallation = /* @__PURE__ */ new Map();
|
|
360
|
+
const spans = traces.flatMap((trace) => trace.spans.map((span) => ({
|
|
361
|
+
span,
|
|
362
|
+
service: trace.service
|
|
363
|
+
}))).sort((a, b) => a.span.startTime - b.span.startTime);
|
|
364
|
+
for (const { span, service } of spans) {
|
|
365
|
+
if (!isWebMcpSpan(span)) continue;
|
|
366
|
+
if (activityWindow && span.startTime > activityWindow.end) continue;
|
|
367
|
+
const attrs = span.attributes ?? {};
|
|
368
|
+
const installationId = require_json_fields.asString(attrs["webmcp.installation.id"]) ?? UNKNOWN_INSTALLATION;
|
|
369
|
+
const installationKey = `${service}${installationId}`;
|
|
370
|
+
if (!installations.has(installationKey)) installations.set(installationKey, false);
|
|
371
|
+
if (span.name === INSTALL) {
|
|
372
|
+
latestInstallation.set(service, installationId);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const name = require_json_fields.asString(attrs["webmcp.tool.name"]) ?? require_json_fields.asString(attrs["gen_ai.tool.name"]);
|
|
376
|
+
if (!name) continue;
|
|
377
|
+
const key = `${service}${installationId}${name}`;
|
|
378
|
+
let tool = tools.get(key);
|
|
379
|
+
if (!tool) {
|
|
380
|
+
tool = {
|
|
381
|
+
name,
|
|
382
|
+
installationId,
|
|
383
|
+
service,
|
|
384
|
+
sessionId: require_json_fields.asString(attrs["session.id"]),
|
|
385
|
+
observedAtRegistration: false,
|
|
386
|
+
offered: false,
|
|
387
|
+
firstSeen: span.startTime,
|
|
388
|
+
lastSeen: span.startTime,
|
|
389
|
+
annotationsSent: [],
|
|
390
|
+
annotationsDropped: [],
|
|
391
|
+
labelMismatch: false,
|
|
392
|
+
redefined: false,
|
|
393
|
+
refusedCalls: 0,
|
|
394
|
+
calls: 0,
|
|
395
|
+
errors: 0,
|
|
396
|
+
envelopeCalls: 0,
|
|
397
|
+
envelopeBytes: 0,
|
|
398
|
+
substitutedCalls: 0,
|
|
399
|
+
resultBytes: 0,
|
|
400
|
+
medianResultBytes: 0,
|
|
401
|
+
resultSizes: [],
|
|
402
|
+
recentCalls: [],
|
|
403
|
+
traceId: span.traceId,
|
|
404
|
+
spanId: span.spanId
|
|
405
|
+
};
|
|
406
|
+
tools.set(key, tool);
|
|
407
|
+
}
|
|
408
|
+
tool.firstSeen = Math.min(tool.firstSeen, span.startTime);
|
|
409
|
+
tool.lastSeen = Math.max(tool.lastSeen, span.startTime);
|
|
410
|
+
tool.sessionId ??= require_json_fields.asString(attrs["session.id"]);
|
|
411
|
+
switch (span.name) {
|
|
412
|
+
case REGISTER:
|
|
413
|
+
installations.set(installationKey, true);
|
|
414
|
+
tool.observedAtRegistration = true;
|
|
415
|
+
tool.offered = true;
|
|
416
|
+
tool.descriptionLength = require_json_fields.asNumber(attrs["webmcp.tool.description.length"]);
|
|
417
|
+
tool.hasInputSchema = require_json_fields.asBoolean(attrs["webmcp.tool.has_input_schema"]);
|
|
418
|
+
tool.annotationsSent = csv(attrs["webmcp.annotations.sent"]);
|
|
419
|
+
tool.annotationsDropped = csv(attrs["webmcp.annotations.dropped"]);
|
|
420
|
+
tool.title = require_json_fields.asString(attrs["webmcp.tool.title"]);
|
|
421
|
+
tool.labelMismatch = require_json_fields.asBoolean(attrs["webmcp.tool.label_mismatch"]) ?? false;
|
|
422
|
+
tool.descriptor = require_json_fields.asString(attrs["webmcp.tool.descriptor"]);
|
|
423
|
+
if (require_json_fields.asBoolean(attrs["webmcp.tool.redefined"])) tool.redefined = true;
|
|
424
|
+
tool.traceId = span.traceId;
|
|
425
|
+
tool.spanId = span.spanId;
|
|
426
|
+
break;
|
|
427
|
+
case WITHDRAW:
|
|
428
|
+
tool.offered = false;
|
|
429
|
+
break;
|
|
430
|
+
case EXECUTE: {
|
|
431
|
+
if (activityWindow && span.startTime < activityWindow.start) break;
|
|
432
|
+
tool.calls += 1;
|
|
433
|
+
const bytes = require_json_fields.asNumber(attrs["webmcp.result.bytes"]) ?? 0;
|
|
434
|
+
tool.resultBytes += bytes;
|
|
435
|
+
tool.resultSizes.push(bytes);
|
|
436
|
+
if (require_json_fields.asBoolean(attrs["webmcp.result.envelope"])) {
|
|
437
|
+
tool.envelopeCalls += 1;
|
|
438
|
+
tool.envelopeBytes += ENVELOPE_OVERHEAD_BYTES;
|
|
439
|
+
}
|
|
440
|
+
if (require_json_fields.asBoolean(attrs["webmcp.result.substituted"])) tool.substitutedCalls += 1;
|
|
441
|
+
const refused = require_json_fields.asBoolean(attrs["webmcp.result.refused"]) ?? false;
|
|
442
|
+
const refusal = require_json_fields.asString(attrs["webmcp.result.refusal"]);
|
|
443
|
+
if (refused) tool.refusedCalls += 1;
|
|
444
|
+
const failed = span.status.code === "ERROR" || (require_json_fields.asBoolean(attrs["webmcp.result.error"]) ?? false) || require_json_fields.asString(attrs["error.type"]) !== void 0;
|
|
445
|
+
if (failed) tool.errors += 1;
|
|
446
|
+
tool.recentCalls.push({
|
|
447
|
+
timestamp: span.startTime,
|
|
448
|
+
durationMs: span.duration,
|
|
449
|
+
resultBytes: bytes,
|
|
450
|
+
resultType: require_json_fields.asString(attrs["webmcp.result.type"]),
|
|
451
|
+
envelope: require_json_fields.asBoolean(attrs["webmcp.result.envelope"]) ?? false,
|
|
452
|
+
substituted: require_json_fields.asBoolean(attrs["webmcp.result.substituted"]) ?? false,
|
|
453
|
+
error: failed,
|
|
454
|
+
seq: require_json_fields.asNumber(attrs["webmcp.execute.seq"]),
|
|
455
|
+
refused,
|
|
456
|
+
...refusal === "confirm" || refusal === "unavailable" ? { refusal } : {},
|
|
457
|
+
input: require_json_fields.asString(attrs["webmcp.input"]) ?? require_json_fields.asString(attrs["gen_ai.tool.call.arguments"]),
|
|
458
|
+
result: require_json_fields.asString(attrs["webmcp.result"]) ?? require_json_fields.asString(attrs["gen_ai.tool.call.result"]),
|
|
459
|
+
traceId: span.traceId,
|
|
460
|
+
spanId: span.spanId
|
|
461
|
+
});
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const list = [...tools.values()].map((tool) => {
|
|
467
|
+
const { resultSizes, ...rest } = tool;
|
|
468
|
+
return {
|
|
469
|
+
...rest,
|
|
470
|
+
offered: rest.offered && (latestInstallation.get(rest.service) ?? rest.installationId) === rest.installationId,
|
|
471
|
+
medianResultBytes: median(resultSizes),
|
|
472
|
+
recentCalls: [...tool.recentCalls].sort((a, b) => b.timestamp - a.timestamp).slice(0, RECENT_CALLS)
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
list.sort((a, b) => Number(b.offered) - Number(a.offered) || b.calls - a.calls || a.name.localeCompare(b.name));
|
|
476
|
+
return {
|
|
477
|
+
tools: list,
|
|
478
|
+
summary: summarize(list, installations)
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function summarize(tools, installations) {
|
|
482
|
+
const summary = {
|
|
483
|
+
installations: installations.size,
|
|
484
|
+
emptyInstallations: [...installations.values()].filter((saw) => !saw).length,
|
|
485
|
+
toolsOffered: 0,
|
|
486
|
+
toolsWithdrawn: 0,
|
|
487
|
+
calls: 0,
|
|
488
|
+
errors: 0,
|
|
489
|
+
resultBytes: 0,
|
|
490
|
+
envelopeBytes: 0,
|
|
491
|
+
toolsWithDroppedAnnotations: 0,
|
|
492
|
+
toolsWithoutInputSchema: 0,
|
|
493
|
+
toolsWithLabelMismatch: 0,
|
|
494
|
+
toolsRedefined: 0,
|
|
495
|
+
refusedCalls: 0
|
|
496
|
+
};
|
|
497
|
+
for (const tool of tools) {
|
|
498
|
+
if (tool.offered) summary.toolsOffered += 1;
|
|
499
|
+
else summary.toolsWithdrawn += 1;
|
|
500
|
+
summary.calls += tool.calls;
|
|
501
|
+
summary.errors += tool.errors;
|
|
502
|
+
summary.resultBytes += tool.resultBytes;
|
|
503
|
+
summary.envelopeBytes += tool.envelopeBytes;
|
|
504
|
+
if (tool.annotationsDropped.length > 0) summary.toolsWithDroppedAnnotations += 1;
|
|
505
|
+
if (tool.observedAtRegistration && tool.hasInputSchema === false) summary.toolsWithoutInputSchema += 1;
|
|
506
|
+
if (tool.labelMismatch) summary.toolsWithLabelMismatch += 1;
|
|
507
|
+
if (tool.redefined) summary.toolsRedefined += 1;
|
|
508
|
+
summary.refusedCalls += tool.refusedCalls;
|
|
509
|
+
}
|
|
510
|
+
return summary;
|
|
511
|
+
}
|
|
512
|
+
|
|
319
513
|
//#endregion
|
|
320
514
|
//#region src/server/telemetry-limits.ts
|
|
321
515
|
const defaultLimit = 100;
|
|
@@ -2754,6 +2948,44 @@ var DevtoolsServer = class {
|
|
|
2754
2948
|
} while (cursor);
|
|
2755
2949
|
return { errors: aggregator.getErrorGroups() };
|
|
2756
2950
|
}
|
|
2951
|
+
/**
|
|
2952
|
+
* Fold WebMCP lifecycle history through the window end into the tool surface
|
|
2953
|
+
* an agent is offered, while counting executions only inside the window.
|
|
2954
|
+
*
|
|
2955
|
+
* Drains every page rather than folding the first one: an inventory built
|
|
2956
|
+
* from a page of results does not fail, it *under-reports* — "2 tools dropped
|
|
2957
|
+
* annotations" when the answer is 6 — and gets more wrong the more traffic
|
|
2958
|
+
* there is, which is backwards for an observability answer.
|
|
2959
|
+
*
|
|
2960
|
+
* The span-name filter is composed here rather than accepted from the client:
|
|
2961
|
+
* this endpoint answers one question, and a caller-supplied predicate could
|
|
2962
|
+
* only narrow it into a wrong answer.
|
|
2963
|
+
*/
|
|
2964
|
+
queryWebMcp(args) {
|
|
2965
|
+
const traces = [];
|
|
2966
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
2967
|
+
let cursor;
|
|
2968
|
+
do {
|
|
2969
|
+
const result = this.store.queryTraces({
|
|
2970
|
+
query: "name ^ \"webmcp.\"",
|
|
2971
|
+
window: args.window ? {
|
|
2972
|
+
start: 0,
|
|
2973
|
+
end: args.window.end
|
|
2974
|
+
} : void 0,
|
|
2975
|
+
limit: args.limit,
|
|
2976
|
+
cursor
|
|
2977
|
+
});
|
|
2978
|
+
if (result.errors) return {
|
|
2979
|
+
webmcp: foldWebMcpTools([]),
|
|
2980
|
+
errors_parse: result.errors
|
|
2981
|
+
};
|
|
2982
|
+
traces.push(...result.traces);
|
|
2983
|
+
cursor = result.nextCursor ?? void 0;
|
|
2984
|
+
if (cursor && seenCursors.has(cursor)) break;
|
|
2985
|
+
if (cursor) seenCursors.add(cursor);
|
|
2986
|
+
} while (cursor);
|
|
2987
|
+
return { webmcp: foldWebMcpTools(traces, args.window) };
|
|
2988
|
+
}
|
|
2757
2989
|
/** Run a log query against the durable store. */
|
|
2758
2990
|
queryLogs(args) {
|
|
2759
2991
|
return this.store.queryLogs(args);
|
|
@@ -3757,6 +3989,30 @@ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
|
3757
3989
|
}
|
|
3758
3990
|
return;
|
|
3759
3991
|
}
|
|
3992
|
+
if (req.method === "POST" && url === "/api/query/webmcp") {
|
|
3993
|
+
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
3994
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
try {
|
|
3998
|
+
const body = await readJsonBody(req);
|
|
3999
|
+
const result = devtools.queryWebMcp({
|
|
4000
|
+
window: body.window,
|
|
4001
|
+
limit: body.limit
|
|
4002
|
+
});
|
|
4003
|
+
if (result.errors_parse) {
|
|
4004
|
+
sendJson(res, 400, { errors: result.errors_parse });
|
|
4005
|
+
return;
|
|
4006
|
+
}
|
|
4007
|
+
sendJson(res, 200, { webmcp: result.webmcp });
|
|
4008
|
+
} catch (e) {
|
|
4009
|
+
sendJson(res, 400, {
|
|
4010
|
+
error: "Invalid query request",
|
|
4011
|
+
message: e instanceof Error ? e.message : String(e)
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
3760
4016
|
if (req.method === "GET" && url === "/api/metrics") {
|
|
3761
4017
|
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
3762
4018
|
sendJson(res, 403, { error: "Forbidden" });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as encodeTraces } from "./wire-2Rmfg6IT.js";
|
|
2
|
-
import { i as asString, o as stringAttr, t as asObject } from "./json-fields-CPjKZ2WH.js";
|
|
2
|
+
import { i as asString, n as asBoolean, o as stringAttr, r as asNumber, t as asObject } from "./json-fields-CPjKZ2WH.js";
|
|
3
3
|
import { t as pickRoot } from "./trace-root-EHnvuA7f.js";
|
|
4
4
|
import { a as compileWhere, t as parse } from "./parse-D_RmPPQs.js";
|
|
5
5
|
import { t as getResourceName } from "./resource-utils-B4UVvfnH.js";
|
|
@@ -287,6 +287,200 @@ var ErrorAggregator = class {
|
|
|
287
287
|
}
|
|
288
288
|
};
|
|
289
289
|
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/server/webmcp-aggregator.ts
|
|
292
|
+
/** Span names this fold consumes. Everything else in the trace is ignored. */
|
|
293
|
+
const INSTALL = "webmcp.install";
|
|
294
|
+
const REGISTER = "webmcp.tool.register";
|
|
295
|
+
const EXECUTE = "webmcp.tool.execute";
|
|
296
|
+
const WITHDRAW = "webmcp.tool.withdraw";
|
|
297
|
+
/** How many recent executions a tool carries. Enough to see a pattern, not a log. */
|
|
298
|
+
const RECENT_CALLS = 5;
|
|
299
|
+
/**
|
|
300
|
+
* `{"content":[{"type":"text","text":` + `}]}` and the quoting around it.
|
|
301
|
+
*
|
|
302
|
+
* Chrome does not unwrap the MCP envelope, so an enveloped result spends these
|
|
303
|
+
* bytes of the agent's context on structure carrying no information. Measured
|
|
304
|
+
* rather than derived: 45 bytes wrapped versus 13 plain for the same text.
|
|
305
|
+
*/
|
|
306
|
+
const ENVELOPE_OVERHEAD_BYTES = 32;
|
|
307
|
+
const median = (values) => {
|
|
308
|
+
if (values.length === 0) return 0;
|
|
309
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
310
|
+
const mid = Math.floor(sorted.length / 2);
|
|
311
|
+
return sorted.length % 2 === 0 ? Math.round((sorted[mid - 1] + sorted[mid]) / 2) : sorted[mid];
|
|
312
|
+
};
|
|
313
|
+
const csv = (value) => {
|
|
314
|
+
const raw = asString(value);
|
|
315
|
+
if (!raw) return [];
|
|
316
|
+
return raw.split(",").map((part) => part.trim()).filter(Boolean);
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* Spans with no installation id — emitted by an `autotel-webmcp` older than the
|
|
320
|
+
* lifecycle release. Grouping them under one key per service keeps them
|
|
321
|
+
* readable as a single legacy installation rather than scattering one
|
|
322
|
+
* installation per tool.
|
|
323
|
+
*/
|
|
324
|
+
const UNKNOWN_INSTALLATION = "unknown";
|
|
325
|
+
const isWebMcpSpan = (span) => span.name === INSTALL || span.name === REGISTER || span.name === EXECUTE || span.name === WITHDRAW;
|
|
326
|
+
function foldWebMcpTools(traces, activityWindow) {
|
|
327
|
+
const tools = /* @__PURE__ */ new Map();
|
|
328
|
+
/** Installations seen, and whether each one ever registered anything. */
|
|
329
|
+
const installations = /* @__PURE__ */ new Map();
|
|
330
|
+
const latestInstallation = /* @__PURE__ */ new Map();
|
|
331
|
+
const spans = traces.flatMap((trace) => trace.spans.map((span) => ({
|
|
332
|
+
span,
|
|
333
|
+
service: trace.service
|
|
334
|
+
}))).sort((a, b) => a.span.startTime - b.span.startTime);
|
|
335
|
+
for (const { span, service } of spans) {
|
|
336
|
+
if (!isWebMcpSpan(span)) continue;
|
|
337
|
+
if (activityWindow && span.startTime > activityWindow.end) continue;
|
|
338
|
+
const attrs = span.attributes ?? {};
|
|
339
|
+
const installationId = asString(attrs["webmcp.installation.id"]) ?? UNKNOWN_INSTALLATION;
|
|
340
|
+
const installationKey = `${service}${installationId}`;
|
|
341
|
+
if (!installations.has(installationKey)) installations.set(installationKey, false);
|
|
342
|
+
if (span.name === INSTALL) {
|
|
343
|
+
latestInstallation.set(service, installationId);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const name = asString(attrs["webmcp.tool.name"]) ?? asString(attrs["gen_ai.tool.name"]);
|
|
347
|
+
if (!name) continue;
|
|
348
|
+
const key = `${service}${installationId}${name}`;
|
|
349
|
+
let tool = tools.get(key);
|
|
350
|
+
if (!tool) {
|
|
351
|
+
tool = {
|
|
352
|
+
name,
|
|
353
|
+
installationId,
|
|
354
|
+
service,
|
|
355
|
+
sessionId: asString(attrs["session.id"]),
|
|
356
|
+
observedAtRegistration: false,
|
|
357
|
+
offered: false,
|
|
358
|
+
firstSeen: span.startTime,
|
|
359
|
+
lastSeen: span.startTime,
|
|
360
|
+
annotationsSent: [],
|
|
361
|
+
annotationsDropped: [],
|
|
362
|
+
labelMismatch: false,
|
|
363
|
+
redefined: false,
|
|
364
|
+
refusedCalls: 0,
|
|
365
|
+
calls: 0,
|
|
366
|
+
errors: 0,
|
|
367
|
+
envelopeCalls: 0,
|
|
368
|
+
envelopeBytes: 0,
|
|
369
|
+
substitutedCalls: 0,
|
|
370
|
+
resultBytes: 0,
|
|
371
|
+
medianResultBytes: 0,
|
|
372
|
+
resultSizes: [],
|
|
373
|
+
recentCalls: [],
|
|
374
|
+
traceId: span.traceId,
|
|
375
|
+
spanId: span.spanId
|
|
376
|
+
};
|
|
377
|
+
tools.set(key, tool);
|
|
378
|
+
}
|
|
379
|
+
tool.firstSeen = Math.min(tool.firstSeen, span.startTime);
|
|
380
|
+
tool.lastSeen = Math.max(tool.lastSeen, span.startTime);
|
|
381
|
+
tool.sessionId ??= asString(attrs["session.id"]);
|
|
382
|
+
switch (span.name) {
|
|
383
|
+
case REGISTER:
|
|
384
|
+
installations.set(installationKey, true);
|
|
385
|
+
tool.observedAtRegistration = true;
|
|
386
|
+
tool.offered = true;
|
|
387
|
+
tool.descriptionLength = asNumber(attrs["webmcp.tool.description.length"]);
|
|
388
|
+
tool.hasInputSchema = asBoolean(attrs["webmcp.tool.has_input_schema"]);
|
|
389
|
+
tool.annotationsSent = csv(attrs["webmcp.annotations.sent"]);
|
|
390
|
+
tool.annotationsDropped = csv(attrs["webmcp.annotations.dropped"]);
|
|
391
|
+
tool.title = asString(attrs["webmcp.tool.title"]);
|
|
392
|
+
tool.labelMismatch = asBoolean(attrs["webmcp.tool.label_mismatch"]) ?? false;
|
|
393
|
+
tool.descriptor = asString(attrs["webmcp.tool.descriptor"]);
|
|
394
|
+
if (asBoolean(attrs["webmcp.tool.redefined"])) tool.redefined = true;
|
|
395
|
+
tool.traceId = span.traceId;
|
|
396
|
+
tool.spanId = span.spanId;
|
|
397
|
+
break;
|
|
398
|
+
case WITHDRAW:
|
|
399
|
+
tool.offered = false;
|
|
400
|
+
break;
|
|
401
|
+
case EXECUTE: {
|
|
402
|
+
if (activityWindow && span.startTime < activityWindow.start) break;
|
|
403
|
+
tool.calls += 1;
|
|
404
|
+
const bytes = asNumber(attrs["webmcp.result.bytes"]) ?? 0;
|
|
405
|
+
tool.resultBytes += bytes;
|
|
406
|
+
tool.resultSizes.push(bytes);
|
|
407
|
+
if (asBoolean(attrs["webmcp.result.envelope"])) {
|
|
408
|
+
tool.envelopeCalls += 1;
|
|
409
|
+
tool.envelopeBytes += ENVELOPE_OVERHEAD_BYTES;
|
|
410
|
+
}
|
|
411
|
+
if (asBoolean(attrs["webmcp.result.substituted"])) tool.substitutedCalls += 1;
|
|
412
|
+
const refused = asBoolean(attrs["webmcp.result.refused"]) ?? false;
|
|
413
|
+
const refusal = asString(attrs["webmcp.result.refusal"]);
|
|
414
|
+
if (refused) tool.refusedCalls += 1;
|
|
415
|
+
const failed = span.status.code === "ERROR" || (asBoolean(attrs["webmcp.result.error"]) ?? false) || asString(attrs["error.type"]) !== void 0;
|
|
416
|
+
if (failed) tool.errors += 1;
|
|
417
|
+
tool.recentCalls.push({
|
|
418
|
+
timestamp: span.startTime,
|
|
419
|
+
durationMs: span.duration,
|
|
420
|
+
resultBytes: bytes,
|
|
421
|
+
resultType: asString(attrs["webmcp.result.type"]),
|
|
422
|
+
envelope: asBoolean(attrs["webmcp.result.envelope"]) ?? false,
|
|
423
|
+
substituted: asBoolean(attrs["webmcp.result.substituted"]) ?? false,
|
|
424
|
+
error: failed,
|
|
425
|
+
seq: asNumber(attrs["webmcp.execute.seq"]),
|
|
426
|
+
refused,
|
|
427
|
+
...refusal === "confirm" || refusal === "unavailable" ? { refusal } : {},
|
|
428
|
+
input: asString(attrs["webmcp.input"]) ?? asString(attrs["gen_ai.tool.call.arguments"]),
|
|
429
|
+
result: asString(attrs["webmcp.result"]) ?? asString(attrs["gen_ai.tool.call.result"]),
|
|
430
|
+
traceId: span.traceId,
|
|
431
|
+
spanId: span.spanId
|
|
432
|
+
});
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const list = [...tools.values()].map((tool) => {
|
|
438
|
+
const { resultSizes, ...rest } = tool;
|
|
439
|
+
return {
|
|
440
|
+
...rest,
|
|
441
|
+
offered: rest.offered && (latestInstallation.get(rest.service) ?? rest.installationId) === rest.installationId,
|
|
442
|
+
medianResultBytes: median(resultSizes),
|
|
443
|
+
recentCalls: [...tool.recentCalls].sort((a, b) => b.timestamp - a.timestamp).slice(0, RECENT_CALLS)
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
list.sort((a, b) => Number(b.offered) - Number(a.offered) || b.calls - a.calls || a.name.localeCompare(b.name));
|
|
447
|
+
return {
|
|
448
|
+
tools: list,
|
|
449
|
+
summary: summarize(list, installations)
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function summarize(tools, installations) {
|
|
453
|
+
const summary = {
|
|
454
|
+
installations: installations.size,
|
|
455
|
+
emptyInstallations: [...installations.values()].filter((saw) => !saw).length,
|
|
456
|
+
toolsOffered: 0,
|
|
457
|
+
toolsWithdrawn: 0,
|
|
458
|
+
calls: 0,
|
|
459
|
+
errors: 0,
|
|
460
|
+
resultBytes: 0,
|
|
461
|
+
envelopeBytes: 0,
|
|
462
|
+
toolsWithDroppedAnnotations: 0,
|
|
463
|
+
toolsWithoutInputSchema: 0,
|
|
464
|
+
toolsWithLabelMismatch: 0,
|
|
465
|
+
toolsRedefined: 0,
|
|
466
|
+
refusedCalls: 0
|
|
467
|
+
};
|
|
468
|
+
for (const tool of tools) {
|
|
469
|
+
if (tool.offered) summary.toolsOffered += 1;
|
|
470
|
+
else summary.toolsWithdrawn += 1;
|
|
471
|
+
summary.calls += tool.calls;
|
|
472
|
+
summary.errors += tool.errors;
|
|
473
|
+
summary.resultBytes += tool.resultBytes;
|
|
474
|
+
summary.envelopeBytes += tool.envelopeBytes;
|
|
475
|
+
if (tool.annotationsDropped.length > 0) summary.toolsWithDroppedAnnotations += 1;
|
|
476
|
+
if (tool.observedAtRegistration && tool.hasInputSchema === false) summary.toolsWithoutInputSchema += 1;
|
|
477
|
+
if (tool.labelMismatch) summary.toolsWithLabelMismatch += 1;
|
|
478
|
+
if (tool.redefined) summary.toolsRedefined += 1;
|
|
479
|
+
summary.refusedCalls += tool.refusedCalls;
|
|
480
|
+
}
|
|
481
|
+
return summary;
|
|
482
|
+
}
|
|
483
|
+
|
|
290
484
|
//#endregion
|
|
291
485
|
//#region src/server/telemetry-limits.ts
|
|
292
486
|
const defaultLimit = 100;
|
|
@@ -2725,6 +2919,44 @@ var DevtoolsServer = class {
|
|
|
2725
2919
|
} while (cursor);
|
|
2726
2920
|
return { errors: aggregator.getErrorGroups() };
|
|
2727
2921
|
}
|
|
2922
|
+
/**
|
|
2923
|
+
* Fold WebMCP lifecycle history through the window end into the tool surface
|
|
2924
|
+
* an agent is offered, while counting executions only inside the window.
|
|
2925
|
+
*
|
|
2926
|
+
* Drains every page rather than folding the first one: an inventory built
|
|
2927
|
+
* from a page of results does not fail, it *under-reports* — "2 tools dropped
|
|
2928
|
+
* annotations" when the answer is 6 — and gets more wrong the more traffic
|
|
2929
|
+
* there is, which is backwards for an observability answer.
|
|
2930
|
+
*
|
|
2931
|
+
* The span-name filter is composed here rather than accepted from the client:
|
|
2932
|
+
* this endpoint answers one question, and a caller-supplied predicate could
|
|
2933
|
+
* only narrow it into a wrong answer.
|
|
2934
|
+
*/
|
|
2935
|
+
queryWebMcp(args) {
|
|
2936
|
+
const traces = [];
|
|
2937
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
2938
|
+
let cursor;
|
|
2939
|
+
do {
|
|
2940
|
+
const result = this.store.queryTraces({
|
|
2941
|
+
query: "name ^ \"webmcp.\"",
|
|
2942
|
+
window: args.window ? {
|
|
2943
|
+
start: 0,
|
|
2944
|
+
end: args.window.end
|
|
2945
|
+
} : void 0,
|
|
2946
|
+
limit: args.limit,
|
|
2947
|
+
cursor
|
|
2948
|
+
});
|
|
2949
|
+
if (result.errors) return {
|
|
2950
|
+
webmcp: foldWebMcpTools([]),
|
|
2951
|
+
errors_parse: result.errors
|
|
2952
|
+
};
|
|
2953
|
+
traces.push(...result.traces);
|
|
2954
|
+
cursor = result.nextCursor ?? void 0;
|
|
2955
|
+
if (cursor && seenCursors.has(cursor)) break;
|
|
2956
|
+
if (cursor) seenCursors.add(cursor);
|
|
2957
|
+
} while (cursor);
|
|
2958
|
+
return { webmcp: foldWebMcpTools(traces, args.window) };
|
|
2959
|
+
}
|
|
2728
2960
|
/** Run a log query against the durable store. */
|
|
2729
2961
|
queryLogs(args) {
|
|
2730
2962
|
return this.store.queryLogs(args);
|
|
@@ -3728,6 +3960,30 @@ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
|
3728
3960
|
}
|
|
3729
3961
|
return;
|
|
3730
3962
|
}
|
|
3963
|
+
if (req.method === "POST" && url === "/api/query/webmcp") {
|
|
3964
|
+
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
3965
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
3966
|
+
return;
|
|
3967
|
+
}
|
|
3968
|
+
try {
|
|
3969
|
+
const body = await readJsonBody(req);
|
|
3970
|
+
const result = devtools.queryWebMcp({
|
|
3971
|
+
window: body.window,
|
|
3972
|
+
limit: body.limit
|
|
3973
|
+
});
|
|
3974
|
+
if (result.errors_parse) {
|
|
3975
|
+
sendJson(res, 400, { errors: result.errors_parse });
|
|
3976
|
+
return;
|
|
3977
|
+
}
|
|
3978
|
+
sendJson(res, 200, { webmcp: result.webmcp });
|
|
3979
|
+
} catch (e) {
|
|
3980
|
+
sendJson(res, 400, {
|
|
3981
|
+
error: "Invalid query request",
|
|
3982
|
+
message: e instanceof Error ? e.message : String(e)
|
|
3983
|
+
});
|
|
3984
|
+
}
|
|
3985
|
+
return;
|
|
3986
|
+
}
|
|
3731
3987
|
if (req.method === "GET" && url === "/api/metrics") {
|
|
3732
3988
|
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
3733
3989
|
sendJson(res, 403, { error: "Forbidden" });
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_http = require('./http-
|
|
2
|
+
const require_http = require('./http-Dc57PtwQ.cjs');
|
|
3
3
|
const require_listen = require('./listen-l09RRHht.cjs');
|
|
4
4
|
const require_server_exporter = require('./server/exporter.cjs');
|
|
5
5
|
const require_server_log_exporter = require('./server/log-exporter.cjs');
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { n as SpanAttributes, t as AttributeValue } from "./types-
|
|
2
|
-
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-
|
|
3
|
-
import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-
|
|
1
|
+
import { n as SpanAttributes, t as AttributeValue } from "./types-hVSf0DoT.cjs";
|
|
2
|
+
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-ZU74Na4z.cjs";
|
|
3
|
+
import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-BgjMeWvt.cjs";
|
|
4
4
|
import { DevtoolsLogExporter } from "./server/log-exporter.cjs";
|
|
5
5
|
import { DevtoolsRemoteExporter } from "./server/remote-exporter.cjs";
|
|
6
|
-
import { t as ErrorAggregator } from "./error-aggregator-
|
|
6
|
+
import { t as ErrorAggregator } from "./error-aggregator-DkUrfGol.cjs";
|
|
7
7
|
import { Server } from "node:http";
|
|
8
8
|
//#region src/index.d.ts
|
|
9
9
|
interface CreateDevtoolsOptions {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { n as SpanAttributes, t as AttributeValue } from "./types-
|
|
2
|
-
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-
|
|
3
|
-
import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-
|
|
1
|
+
import { n as SpanAttributes, t as AttributeValue } from "./types-hVSf0DoT.js";
|
|
2
|
+
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-Do48caKG.js";
|
|
3
|
+
import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-BRSxg3Du.js";
|
|
4
4
|
import { DevtoolsLogExporter } from "./server/log-exporter.js";
|
|
5
5
|
import { DevtoolsRemoteExporter } from "./server/remote-exporter.js";
|
|
6
|
-
import { t as ErrorAggregator } from "./error-aggregator-
|
|
6
|
+
import { t as ErrorAggregator } from "./error-aggregator-BzhW482_.js";
|
|
7
7
|
import { Server } from "node:http";
|
|
8
8
|
//#region src/index.d.ts
|
|
9
9
|
interface CreateDevtoolsOptions {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ErrorAggregator, g as hostHeaderIsLoopback, l as DevtoolsServer, r as resolveSourceRoot, t as attachDevtoolsRoutes } from "./http-
|
|
1
|
+
import { C as ErrorAggregator, g as hostHeaderIsLoopback, l as DevtoolsServer, r as resolveSourceRoot, t as attachDevtoolsRoutes } from "./http-JTSqQPVJ.js";
|
|
2
2
|
import { t as listenLoopbackDualStack } from "./listen-D-lLgfro.js";
|
|
3
3
|
import { DevtoolsSpanExporter } from "./server/exporter.js";
|
|
4
4
|
import { DevtoolsLogExporter } from "./server/log-exporter.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as DevtoolsSpanExporter } from "../exporter-
|
|
1
|
+
import { t as DevtoolsSpanExporter } from "../exporter-BgjMeWvt.cjs";
|
|
2
2
|
export { DevtoolsSpanExporter };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as DevtoolsSpanExporter } from "../exporter-
|
|
1
|
+
import { t as DevtoolsSpanExporter } from "../exporter-BRSxg3Du.js";
|
|
2
2
|
export { DevtoolsSpanExporter };
|
package/dist/server/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_http = require('../http-
|
|
2
|
+
const require_http = require('../http-Dc57PtwQ.cjs');
|
|
3
3
|
const require_server_exporter = require('./exporter.cjs');
|
|
4
4
|
const require_server_log_exporter = require('./log-exporter.cjs');
|
|
5
5
|
const require_server_remote_exporter = require('./remote-exporter.cjs');
|
|
6
|
-
const require_grpc = require('../grpc-
|
|
6
|
+
const require_grpc = require('../grpc-AXr0CnZ_.cjs');
|
|
7
7
|
|
|
8
8
|
exports.DEVTOOLS_IDENTITY = require_http.DEVTOOLS_IDENTITY;
|
|
9
9
|
exports.DevtoolsLogExporter = require_server_log_exporter.DevtoolsLogExporter;
|
package/dist/server/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import "../types-
|
|
2
|
-
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, r as ErrorOccurrence, t as DevtoolsData } from "../types-
|
|
3
|
-
import { a as DevtoolsStoreOptions, c as QueryLogsArgs, d as QueryTracesArgs, f as QueryTracesResult, i as DevtoolsStore, l as QueryLogsResult, m as TimeWindow, n as DevtoolsServer, o as MetricCatalogEntry, p as SPAN_SCHEMA, r as DevtoolsServerOptions, s as MetricSeries, t as DevtoolsSpanExporter, u as QueryMetricSeriesArgs } from "../exporter-
|
|
1
|
+
import "../types-hVSf0DoT.cjs";
|
|
2
|
+
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, r as ErrorOccurrence, t as DevtoolsData } from "../types-ZU74Na4z.cjs";
|
|
3
|
+
import { a as DevtoolsStoreOptions, c as QueryLogsArgs, d as QueryTracesArgs, f as QueryTracesResult, i as DevtoolsStore, l as QueryLogsResult, m as TimeWindow, n as DevtoolsServer, o as MetricCatalogEntry, p as SPAN_SCHEMA, r as DevtoolsServerOptions, s as MetricSeries, t as DevtoolsSpanExporter, u as QueryMetricSeriesArgs } from "../exporter-BgjMeWvt.cjs";
|
|
4
4
|
import { DevtoolsLogExporter } from "./log-exporter.cjs";
|
|
5
5
|
import { DevtoolsRemoteExporter, DevtoolsRemoteExporterOptions } from "./remote-exporter.cjs";
|
|
6
|
-
import { t as ErrorAggregator } from "../error-aggregator-
|
|
6
|
+
import { t as ErrorAggregator } from "../error-aggregator-DkUrfGol.cjs";
|
|
7
7
|
import { AgentRawEvent, OtelMetricRecord } from "autotel-agents";
|
|
8
8
|
import { Server } from "node:http";
|
|
9
9
|
//#region src/server/http.d.ts
|