autotel-devtools 23.1.0 → 24.0.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-DSwUvgFF.d.cts → error-aggregator-C0at0wzF.d.cts} +1 -1
- package/dist/{error-aggregator-B52JI8jL.d.ts → error-aggregator-C25_JiVI.d.ts} +1 -1
- package/dist/{exporter-OoWkSMCR.d.cts → exporter-AkgWRDpc.d.cts} +25 -2
- package/dist/{exporter-C88W22vY.d.ts → exporter-yo7lLDIs.d.ts} +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-CSW1Evnt.cjs} +1 -1
- package/dist/{grpc-DY-C1jSU.js → grpc-D1tt8UPi.js} +1 -1
- package/dist/{http-1afd_01N.cjs → http-CahjEnpE.cjs} +237 -0
- package/dist/{http-gk1xapnA.js → http-z8UbJsqk.js} +238 -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-B86Qkqg5.d.cts +120 -0
- package/dist/types-B86Qkqg5.d.ts +120 -0
- package/dist/{types-DM8y4A9Z.d.ts → types-BhbHVeiy.d.ts} +1 -1
- package/dist/{types-B0tjwFqj.d.cts → types-C-ORUrT2.d.cts} +1 -1
- 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-z8UbJsqk.js";
|
|
2
2
|
import { Server, ServerCredentials, status } from "@grpc/grpc-js";
|
|
3
3
|
|
|
4
4
|
//#region src/server/grpc.ts
|
|
@@ -316,6 +316,181 @@ 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
|
+
calls: 0,
|
|
392
|
+
errors: 0,
|
|
393
|
+
envelopeCalls: 0,
|
|
394
|
+
envelopeBytes: 0,
|
|
395
|
+
substitutedCalls: 0,
|
|
396
|
+
resultBytes: 0,
|
|
397
|
+
medianResultBytes: 0,
|
|
398
|
+
resultSizes: [],
|
|
399
|
+
recentCalls: [],
|
|
400
|
+
traceId: span.traceId,
|
|
401
|
+
spanId: span.spanId
|
|
402
|
+
};
|
|
403
|
+
tools.set(key, tool);
|
|
404
|
+
}
|
|
405
|
+
tool.firstSeen = Math.min(tool.firstSeen, span.startTime);
|
|
406
|
+
tool.lastSeen = Math.max(tool.lastSeen, span.startTime);
|
|
407
|
+
tool.sessionId ??= require_json_fields.asString(attrs["session.id"]);
|
|
408
|
+
switch (span.name) {
|
|
409
|
+
case REGISTER:
|
|
410
|
+
installations.set(installationKey, true);
|
|
411
|
+
tool.observedAtRegistration = true;
|
|
412
|
+
tool.offered = true;
|
|
413
|
+
tool.descriptionLength = require_json_fields.asNumber(attrs["webmcp.tool.description.length"]);
|
|
414
|
+
tool.hasInputSchema = require_json_fields.asBoolean(attrs["webmcp.tool.has_input_schema"]);
|
|
415
|
+
tool.annotationsSent = csv(attrs["webmcp.annotations.sent"]);
|
|
416
|
+
tool.annotationsDropped = csv(attrs["webmcp.annotations.dropped"]);
|
|
417
|
+
tool.traceId = span.traceId;
|
|
418
|
+
tool.spanId = span.spanId;
|
|
419
|
+
break;
|
|
420
|
+
case WITHDRAW:
|
|
421
|
+
tool.offered = false;
|
|
422
|
+
break;
|
|
423
|
+
case EXECUTE: {
|
|
424
|
+
if (activityWindow && span.startTime < activityWindow.start) break;
|
|
425
|
+
tool.calls += 1;
|
|
426
|
+
const bytes = require_json_fields.asNumber(attrs["webmcp.result.bytes"]) ?? 0;
|
|
427
|
+
tool.resultBytes += bytes;
|
|
428
|
+
tool.resultSizes.push(bytes);
|
|
429
|
+
if (require_json_fields.asBoolean(attrs["webmcp.result.envelope"])) {
|
|
430
|
+
tool.envelopeCalls += 1;
|
|
431
|
+
tool.envelopeBytes += ENVELOPE_OVERHEAD_BYTES;
|
|
432
|
+
}
|
|
433
|
+
if (require_json_fields.asBoolean(attrs["webmcp.result.substituted"])) tool.substitutedCalls += 1;
|
|
434
|
+
const failed = span.status.code === "ERROR" || (require_json_fields.asBoolean(attrs["webmcp.result.error"]) ?? false) || require_json_fields.asString(attrs["error.type"]) !== void 0;
|
|
435
|
+
if (failed) tool.errors += 1;
|
|
436
|
+
tool.recentCalls.push({
|
|
437
|
+
timestamp: span.startTime,
|
|
438
|
+
durationMs: span.duration,
|
|
439
|
+
resultBytes: bytes,
|
|
440
|
+
resultType: require_json_fields.asString(attrs["webmcp.result.type"]),
|
|
441
|
+
envelope: require_json_fields.asBoolean(attrs["webmcp.result.envelope"]) ?? false,
|
|
442
|
+
substituted: require_json_fields.asBoolean(attrs["webmcp.result.substituted"]) ?? false,
|
|
443
|
+
error: failed,
|
|
444
|
+
input: require_json_fields.asString(attrs["webmcp.input"]) ?? require_json_fields.asString(attrs["gen_ai.tool.call.arguments"]),
|
|
445
|
+
result: require_json_fields.asString(attrs["webmcp.result"]) ?? require_json_fields.asString(attrs["gen_ai.tool.call.result"]),
|
|
446
|
+
traceId: span.traceId,
|
|
447
|
+
spanId: span.spanId
|
|
448
|
+
});
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const list = [...tools.values()].map((tool) => {
|
|
454
|
+
const { resultSizes, ...rest } = tool;
|
|
455
|
+
return {
|
|
456
|
+
...rest,
|
|
457
|
+
offered: rest.offered && (latestInstallation.get(rest.service) ?? rest.installationId) === rest.installationId,
|
|
458
|
+
medianResultBytes: median(resultSizes),
|
|
459
|
+
recentCalls: [...tool.recentCalls].sort((a, b) => b.timestamp - a.timestamp).slice(0, RECENT_CALLS)
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
list.sort((a, b) => Number(b.offered) - Number(a.offered) || b.calls - a.calls || a.name.localeCompare(b.name));
|
|
463
|
+
return {
|
|
464
|
+
tools: list,
|
|
465
|
+
summary: summarize(list, installations)
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function summarize(tools, installations) {
|
|
469
|
+
const summary = {
|
|
470
|
+
installations: installations.size,
|
|
471
|
+
emptyInstallations: [...installations.values()].filter((saw) => !saw).length,
|
|
472
|
+
toolsOffered: 0,
|
|
473
|
+
toolsWithdrawn: 0,
|
|
474
|
+
calls: 0,
|
|
475
|
+
errors: 0,
|
|
476
|
+
resultBytes: 0,
|
|
477
|
+
envelopeBytes: 0,
|
|
478
|
+
toolsWithDroppedAnnotations: 0,
|
|
479
|
+
toolsWithoutInputSchema: 0
|
|
480
|
+
};
|
|
481
|
+
for (const tool of tools) {
|
|
482
|
+
if (tool.offered) summary.toolsOffered += 1;
|
|
483
|
+
else summary.toolsWithdrawn += 1;
|
|
484
|
+
summary.calls += tool.calls;
|
|
485
|
+
summary.errors += tool.errors;
|
|
486
|
+
summary.resultBytes += tool.resultBytes;
|
|
487
|
+
summary.envelopeBytes += tool.envelopeBytes;
|
|
488
|
+
if (tool.annotationsDropped.length > 0) summary.toolsWithDroppedAnnotations += 1;
|
|
489
|
+
if (tool.observedAtRegistration && tool.hasInputSchema === false) summary.toolsWithoutInputSchema += 1;
|
|
490
|
+
}
|
|
491
|
+
return summary;
|
|
492
|
+
}
|
|
493
|
+
|
|
319
494
|
//#endregion
|
|
320
495
|
//#region src/server/telemetry-limits.ts
|
|
321
496
|
const defaultLimit = 100;
|
|
@@ -2754,6 +2929,44 @@ var DevtoolsServer = class {
|
|
|
2754
2929
|
} while (cursor);
|
|
2755
2930
|
return { errors: aggregator.getErrorGroups() };
|
|
2756
2931
|
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Fold WebMCP lifecycle history through the window end into the tool surface
|
|
2934
|
+
* an agent is offered, while counting executions only inside the window.
|
|
2935
|
+
*
|
|
2936
|
+
* Drains every page rather than folding the first one: an inventory built
|
|
2937
|
+
* from a page of results does not fail, it *under-reports* — "2 tools dropped
|
|
2938
|
+
* annotations" when the answer is 6 — and gets more wrong the more traffic
|
|
2939
|
+
* there is, which is backwards for an observability answer.
|
|
2940
|
+
*
|
|
2941
|
+
* The span-name filter is composed here rather than accepted from the client:
|
|
2942
|
+
* this endpoint answers one question, and a caller-supplied predicate could
|
|
2943
|
+
* only narrow it into a wrong answer.
|
|
2944
|
+
*/
|
|
2945
|
+
queryWebMcp(args) {
|
|
2946
|
+
const traces = [];
|
|
2947
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
2948
|
+
let cursor;
|
|
2949
|
+
do {
|
|
2950
|
+
const result = this.store.queryTraces({
|
|
2951
|
+
query: "name ^ \"webmcp.\"",
|
|
2952
|
+
window: args.window ? {
|
|
2953
|
+
start: 0,
|
|
2954
|
+
end: args.window.end
|
|
2955
|
+
} : void 0,
|
|
2956
|
+
limit: args.limit,
|
|
2957
|
+
cursor
|
|
2958
|
+
});
|
|
2959
|
+
if (result.errors) return {
|
|
2960
|
+
webmcp: foldWebMcpTools([]),
|
|
2961
|
+
errors_parse: result.errors
|
|
2962
|
+
};
|
|
2963
|
+
traces.push(...result.traces);
|
|
2964
|
+
cursor = result.nextCursor ?? void 0;
|
|
2965
|
+
if (cursor && seenCursors.has(cursor)) break;
|
|
2966
|
+
if (cursor) seenCursors.add(cursor);
|
|
2967
|
+
} while (cursor);
|
|
2968
|
+
return { webmcp: foldWebMcpTools(traces, args.window) };
|
|
2969
|
+
}
|
|
2757
2970
|
/** Run a log query against the durable store. */
|
|
2758
2971
|
queryLogs(args) {
|
|
2759
2972
|
return this.store.queryLogs(args);
|
|
@@ -3757,6 +3970,30 @@ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
|
3757
3970
|
}
|
|
3758
3971
|
return;
|
|
3759
3972
|
}
|
|
3973
|
+
if (req.method === "POST" && url === "/api/query/webmcp") {
|
|
3974
|
+
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
3975
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
3976
|
+
return;
|
|
3977
|
+
}
|
|
3978
|
+
try {
|
|
3979
|
+
const body = await readJsonBody(req);
|
|
3980
|
+
const result = devtools.queryWebMcp({
|
|
3981
|
+
window: body.window,
|
|
3982
|
+
limit: body.limit
|
|
3983
|
+
});
|
|
3984
|
+
if (result.errors_parse) {
|
|
3985
|
+
sendJson(res, 400, { errors: result.errors_parse });
|
|
3986
|
+
return;
|
|
3987
|
+
}
|
|
3988
|
+
sendJson(res, 200, { webmcp: result.webmcp });
|
|
3989
|
+
} catch (e) {
|
|
3990
|
+
sendJson(res, 400, {
|
|
3991
|
+
error: "Invalid query request",
|
|
3992
|
+
message: e instanceof Error ? e.message : String(e)
|
|
3993
|
+
});
|
|
3994
|
+
}
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3760
3997
|
if (req.method === "GET" && url === "/api/metrics") {
|
|
3761
3998
|
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
3762
3999
|
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,181 @@ 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
|
+
calls: 0,
|
|
363
|
+
errors: 0,
|
|
364
|
+
envelopeCalls: 0,
|
|
365
|
+
envelopeBytes: 0,
|
|
366
|
+
substitutedCalls: 0,
|
|
367
|
+
resultBytes: 0,
|
|
368
|
+
medianResultBytes: 0,
|
|
369
|
+
resultSizes: [],
|
|
370
|
+
recentCalls: [],
|
|
371
|
+
traceId: span.traceId,
|
|
372
|
+
spanId: span.spanId
|
|
373
|
+
};
|
|
374
|
+
tools.set(key, tool);
|
|
375
|
+
}
|
|
376
|
+
tool.firstSeen = Math.min(tool.firstSeen, span.startTime);
|
|
377
|
+
tool.lastSeen = Math.max(tool.lastSeen, span.startTime);
|
|
378
|
+
tool.sessionId ??= asString(attrs["session.id"]);
|
|
379
|
+
switch (span.name) {
|
|
380
|
+
case REGISTER:
|
|
381
|
+
installations.set(installationKey, true);
|
|
382
|
+
tool.observedAtRegistration = true;
|
|
383
|
+
tool.offered = true;
|
|
384
|
+
tool.descriptionLength = asNumber(attrs["webmcp.tool.description.length"]);
|
|
385
|
+
tool.hasInputSchema = asBoolean(attrs["webmcp.tool.has_input_schema"]);
|
|
386
|
+
tool.annotationsSent = csv(attrs["webmcp.annotations.sent"]);
|
|
387
|
+
tool.annotationsDropped = csv(attrs["webmcp.annotations.dropped"]);
|
|
388
|
+
tool.traceId = span.traceId;
|
|
389
|
+
tool.spanId = span.spanId;
|
|
390
|
+
break;
|
|
391
|
+
case WITHDRAW:
|
|
392
|
+
tool.offered = false;
|
|
393
|
+
break;
|
|
394
|
+
case EXECUTE: {
|
|
395
|
+
if (activityWindow && span.startTime < activityWindow.start) break;
|
|
396
|
+
tool.calls += 1;
|
|
397
|
+
const bytes = asNumber(attrs["webmcp.result.bytes"]) ?? 0;
|
|
398
|
+
tool.resultBytes += bytes;
|
|
399
|
+
tool.resultSizes.push(bytes);
|
|
400
|
+
if (asBoolean(attrs["webmcp.result.envelope"])) {
|
|
401
|
+
tool.envelopeCalls += 1;
|
|
402
|
+
tool.envelopeBytes += ENVELOPE_OVERHEAD_BYTES;
|
|
403
|
+
}
|
|
404
|
+
if (asBoolean(attrs["webmcp.result.substituted"])) tool.substitutedCalls += 1;
|
|
405
|
+
const failed = span.status.code === "ERROR" || (asBoolean(attrs["webmcp.result.error"]) ?? false) || asString(attrs["error.type"]) !== void 0;
|
|
406
|
+
if (failed) tool.errors += 1;
|
|
407
|
+
tool.recentCalls.push({
|
|
408
|
+
timestamp: span.startTime,
|
|
409
|
+
durationMs: span.duration,
|
|
410
|
+
resultBytes: bytes,
|
|
411
|
+
resultType: asString(attrs["webmcp.result.type"]),
|
|
412
|
+
envelope: asBoolean(attrs["webmcp.result.envelope"]) ?? false,
|
|
413
|
+
substituted: asBoolean(attrs["webmcp.result.substituted"]) ?? false,
|
|
414
|
+
error: failed,
|
|
415
|
+
input: asString(attrs["webmcp.input"]) ?? asString(attrs["gen_ai.tool.call.arguments"]),
|
|
416
|
+
result: asString(attrs["webmcp.result"]) ?? asString(attrs["gen_ai.tool.call.result"]),
|
|
417
|
+
traceId: span.traceId,
|
|
418
|
+
spanId: span.spanId
|
|
419
|
+
});
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const list = [...tools.values()].map((tool) => {
|
|
425
|
+
const { resultSizes, ...rest } = tool;
|
|
426
|
+
return {
|
|
427
|
+
...rest,
|
|
428
|
+
offered: rest.offered && (latestInstallation.get(rest.service) ?? rest.installationId) === rest.installationId,
|
|
429
|
+
medianResultBytes: median(resultSizes),
|
|
430
|
+
recentCalls: [...tool.recentCalls].sort((a, b) => b.timestamp - a.timestamp).slice(0, RECENT_CALLS)
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
list.sort((a, b) => Number(b.offered) - Number(a.offered) || b.calls - a.calls || a.name.localeCompare(b.name));
|
|
434
|
+
return {
|
|
435
|
+
tools: list,
|
|
436
|
+
summary: summarize(list, installations)
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function summarize(tools, installations) {
|
|
440
|
+
const summary = {
|
|
441
|
+
installations: installations.size,
|
|
442
|
+
emptyInstallations: [...installations.values()].filter((saw) => !saw).length,
|
|
443
|
+
toolsOffered: 0,
|
|
444
|
+
toolsWithdrawn: 0,
|
|
445
|
+
calls: 0,
|
|
446
|
+
errors: 0,
|
|
447
|
+
resultBytes: 0,
|
|
448
|
+
envelopeBytes: 0,
|
|
449
|
+
toolsWithDroppedAnnotations: 0,
|
|
450
|
+
toolsWithoutInputSchema: 0
|
|
451
|
+
};
|
|
452
|
+
for (const tool of tools) {
|
|
453
|
+
if (tool.offered) summary.toolsOffered += 1;
|
|
454
|
+
else summary.toolsWithdrawn += 1;
|
|
455
|
+
summary.calls += tool.calls;
|
|
456
|
+
summary.errors += tool.errors;
|
|
457
|
+
summary.resultBytes += tool.resultBytes;
|
|
458
|
+
summary.envelopeBytes += tool.envelopeBytes;
|
|
459
|
+
if (tool.annotationsDropped.length > 0) summary.toolsWithDroppedAnnotations += 1;
|
|
460
|
+
if (tool.observedAtRegistration && tool.hasInputSchema === false) summary.toolsWithoutInputSchema += 1;
|
|
461
|
+
}
|
|
462
|
+
return summary;
|
|
463
|
+
}
|
|
464
|
+
|
|
290
465
|
//#endregion
|
|
291
466
|
//#region src/server/telemetry-limits.ts
|
|
292
467
|
const defaultLimit = 100;
|
|
@@ -2725,6 +2900,44 @@ var DevtoolsServer = class {
|
|
|
2725
2900
|
} while (cursor);
|
|
2726
2901
|
return { errors: aggregator.getErrorGroups() };
|
|
2727
2902
|
}
|
|
2903
|
+
/**
|
|
2904
|
+
* Fold WebMCP lifecycle history through the window end into the tool surface
|
|
2905
|
+
* an agent is offered, while counting executions only inside the window.
|
|
2906
|
+
*
|
|
2907
|
+
* Drains every page rather than folding the first one: an inventory built
|
|
2908
|
+
* from a page of results does not fail, it *under-reports* — "2 tools dropped
|
|
2909
|
+
* annotations" when the answer is 6 — and gets more wrong the more traffic
|
|
2910
|
+
* there is, which is backwards for an observability answer.
|
|
2911
|
+
*
|
|
2912
|
+
* The span-name filter is composed here rather than accepted from the client:
|
|
2913
|
+
* this endpoint answers one question, and a caller-supplied predicate could
|
|
2914
|
+
* only narrow it into a wrong answer.
|
|
2915
|
+
*/
|
|
2916
|
+
queryWebMcp(args) {
|
|
2917
|
+
const traces = [];
|
|
2918
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
2919
|
+
let cursor;
|
|
2920
|
+
do {
|
|
2921
|
+
const result = this.store.queryTraces({
|
|
2922
|
+
query: "name ^ \"webmcp.\"",
|
|
2923
|
+
window: args.window ? {
|
|
2924
|
+
start: 0,
|
|
2925
|
+
end: args.window.end
|
|
2926
|
+
} : void 0,
|
|
2927
|
+
limit: args.limit,
|
|
2928
|
+
cursor
|
|
2929
|
+
});
|
|
2930
|
+
if (result.errors) return {
|
|
2931
|
+
webmcp: foldWebMcpTools([]),
|
|
2932
|
+
errors_parse: result.errors
|
|
2933
|
+
};
|
|
2934
|
+
traces.push(...result.traces);
|
|
2935
|
+
cursor = result.nextCursor ?? void 0;
|
|
2936
|
+
if (cursor && seenCursors.has(cursor)) break;
|
|
2937
|
+
if (cursor) seenCursors.add(cursor);
|
|
2938
|
+
} while (cursor);
|
|
2939
|
+
return { webmcp: foldWebMcpTools(traces, args.window) };
|
|
2940
|
+
}
|
|
2728
2941
|
/** Run a log query against the durable store. */
|
|
2729
2942
|
queryLogs(args) {
|
|
2730
2943
|
return this.store.queryLogs(args);
|
|
@@ -3728,6 +3941,30 @@ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
|
3728
3941
|
}
|
|
3729
3942
|
return;
|
|
3730
3943
|
}
|
|
3944
|
+
if (req.method === "POST" && url === "/api/query/webmcp") {
|
|
3945
|
+
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
3946
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
try {
|
|
3950
|
+
const body = await readJsonBody(req);
|
|
3951
|
+
const result = devtools.queryWebMcp({
|
|
3952
|
+
window: body.window,
|
|
3953
|
+
limit: body.limit
|
|
3954
|
+
});
|
|
3955
|
+
if (result.errors_parse) {
|
|
3956
|
+
sendJson(res, 400, { errors: result.errors_parse });
|
|
3957
|
+
return;
|
|
3958
|
+
}
|
|
3959
|
+
sendJson(res, 200, { webmcp: result.webmcp });
|
|
3960
|
+
} catch (e) {
|
|
3961
|
+
sendJson(res, 400, {
|
|
3962
|
+
error: "Invalid query request",
|
|
3963
|
+
message: e instanceof Error ? e.message : String(e)
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
return;
|
|
3967
|
+
}
|
|
3731
3968
|
if (req.method === "GET" && url === "/api/metrics") {
|
|
3732
3969
|
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
3733
3970
|
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-CahjEnpE.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-B86Qkqg5.cjs";
|
|
2
|
+
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-C-ORUrT2.cjs";
|
|
3
|
+
import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-AkgWRDpc.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-C0at0wzF.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-B86Qkqg5.js";
|
|
2
|
+
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-BhbHVeiy.js";
|
|
3
|
+
import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-yo7lLDIs.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-C25_JiVI.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-z8UbJsqk.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-AkgWRDpc.cjs";
|
|
2
2
|
export { DevtoolsSpanExporter };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as DevtoolsSpanExporter } from "../exporter-
|
|
1
|
+
import { t as DevtoolsSpanExporter } from "../exporter-yo7lLDIs.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-CahjEnpE.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-CSW1Evnt.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-B86Qkqg5.cjs";
|
|
2
|
+
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, r as ErrorOccurrence, t as DevtoolsData } from "../types-C-ORUrT2.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-AkgWRDpc.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-C0at0wzF.cjs";
|
|
7
7
|
import { AgentRawEvent, OtelMetricRecord } from "autotel-agents";
|
|
8
8
|
import { Server } from "node:http";
|
|
9
9
|
//#region src/server/http.d.ts
|
package/dist/server/index.d.ts
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-B86Qkqg5.js";
|
|
2
|
+
import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, r as ErrorOccurrence, t as DevtoolsData } from "../types-BhbHVeiy.js";
|
|
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-yo7lLDIs.js";
|
|
4
4
|
import { DevtoolsLogExporter } from "./log-exporter.js";
|
|
5
5
|
import { DevtoolsRemoteExporter, DevtoolsRemoteExporterOptions } from "./remote-exporter.js";
|
|
6
|
-
import { t as ErrorAggregator } from "../error-aggregator-
|
|
6
|
+
import { t as ErrorAggregator } from "../error-aggregator-C25_JiVI.js";
|
|
7
7
|
import { Server } from "node:http";
|
|
8
8
|
import { AgentRawEvent, OtelMetricRecord } from "autotel-agents";
|
|
9
9
|
//#region src/server/http.d.ts
|
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as ErrorAggregator, S as resolveTelemetryLimits, _ as isLoopbackHostname, a as probePortHolder, b as appendWithLimit, c as decodeOtlpTraceRequest, d as parseOtlpLogs, f as parseOtlpTraces, g as hostHeaderIsLoopback, h as allowSensitiveRequest, i as DEVTOOLS_IDENTITY, l as DevtoolsServer, m as SPAN_SCHEMA, n as createDevtoolsHttpServer, o as decodeOtlpLogsRequest, p as DevtoolsStore, s as decodeOtlpMetricsRequest, t as attachDevtoolsRoutes, u as isProtobufContentType, v as originIsLoopback, x as applyTelemetryLimits, y as appendManyWithLimit } from "../http-
|
|
1
|
+
import { C as ErrorAggregator, S as resolveTelemetryLimits, _ as isLoopbackHostname, a as probePortHolder, b as appendWithLimit, c as decodeOtlpTraceRequest, d as parseOtlpLogs, f as parseOtlpTraces, g as hostHeaderIsLoopback, h as allowSensitiveRequest, i as DEVTOOLS_IDENTITY, l as DevtoolsServer, m as SPAN_SCHEMA, n as createDevtoolsHttpServer, o as decodeOtlpLogsRequest, p as DevtoolsStore, s as decodeOtlpMetricsRequest, t as attachDevtoolsRoutes, u as isProtobufContentType, v as originIsLoopback, x as applyTelemetryLimits, y as appendManyWithLimit } from "../http-z8UbJsqk.js";
|
|
2
2
|
import { DevtoolsSpanExporter } from "./exporter.js";
|
|
3
3
|
import { DevtoolsLogExporter } from "./log-exporter.js";
|
|
4
4
|
import { DevtoolsRemoteExporter } from "./remote-exporter.js";
|
|
5
|
-
import { t as startOtlpGrpcReceiver } from "../grpc-
|
|
5
|
+
import { t as startOtlpGrpcReceiver } from "../grpc-D1tt8UPi.js";
|
|
6
6
|
|
|
7
7
|
export { DEVTOOLS_IDENTITY, DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, DevtoolsStore, ErrorAggregator, SPAN_SCHEMA, allowSensitiveRequest, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, hostHeaderIsLoopback, isLoopbackHostname, isProtobufContentType, originIsLoopback, parseOtlpLogs, parseOtlpTraces, probePortHolder, resolveTelemetryLimits, startOtlpGrpcReceiver };
|