@tonyclaw/agent-inspector 2.0.28 → 2.0.30
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/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-CicCP3Hb.js → CompareDrawer-BDzfRSt1.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-NtOD8Ex_.js +115 -0
- package/.output/public/assets/{ReplayDialog-Bc8q3ujm.js → ReplayDialog-p-5TKg75.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-Coyy3zcH.js → RequestAnatomy-G0dV8-D_.js} +1 -1
- package/.output/public/assets/{ResponseView-BWJzgh0c.js → ResponseView-D2wE6lD-.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-BSC3uuE5.js → StreamingChunkSequence-D8JRK-j_.js} +1 -1
- package/.output/public/assets/_sessionId-zimzVrJE.js +1 -0
- package/.output/public/assets/index-Cv5vPgpr.js +1 -0
- package/.output/public/assets/{index-DsiKfWCp.css → index-CwlHPmgL.css} +1 -1
- package/.output/public/assets/{main-Brj0Gn91.js → main-XHHZsOBK.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +13 -13
- package/.output/server/{_sessionId-DDdyKVG-.mjs → _sessionId-CK9QEhkB.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-XSExe2ml.mjs → CompareDrawer-BJVPFMIx.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-B5pIiBZN.mjs → ProxyViewerContainer-DhbmJ2zj.mjs} +83 -20
- package/.output/server/_ssr/{ReplayDialog-BOAu0ric.mjs → ReplayDialog-DXRj95Gj.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-C8Q5lozD.mjs → RequestAnatomy-CrYJTXdJ.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-a_iJfPUF.mjs → ResponseView-4ESqSITr.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-YMKC0j8U.mjs → StreamingChunkSequence-Czdko5n_.mjs} +2 -2
- package/.output/server/_ssr/{index-CkDwBPzI.mjs → index-bei0I7qL.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DXOXdFFM.mjs → router-sQh4Gk8J.mjs} +1338 -137
- package/.output/server/_tanstack-start-manifest_v-C0fdSWnA.mjs +4 -0
- package/.output/server/index.mjs +64 -64
- package/README.md +24 -1
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +3 -0
- package/src/components/providers/ProviderCard.tsx +32 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +38 -1
- package/src/lib/groupContract.ts +139 -0
- package/src/lib/providerTestContract.ts +3 -0
- package/src/lib/sessionInfoContract.ts +3 -0
- package/src/mcp/server.ts +278 -0
- package/src/mcp/toolHandlers.ts +126 -0
- package/src/proxy/evidenceAnalysis.ts +5 -0
- package/src/proxy/evidenceExporter.ts +8 -3
- package/src/proxy/groupEvidenceExporter.ts +341 -0
- package/src/proxy/groupStore.ts +259 -0
- package/src/proxy/handler.ts +8 -1
- package/src/proxy/logFinalizer.ts +36 -0
- package/src/proxy/schemas.ts +3 -0
- package/src/proxy/sessionInfo.ts +3 -0
- package/src/proxy/store.ts +3 -0
- package/src/proxy/streamTiming.ts +31 -0
- package/src/routes/api/groups.$groupId.evidence.ts +62 -0
- package/src/routes/api/groups.$groupId.sessions.ts +50 -0
- package/src/routes/api/groups.$groupId.ts +50 -0
- package/src/routes/api/groups.ts +39 -0
- package/src/routes/api/logs.$id.replay.ts +25 -2
- package/src/routes/api/providers.$providerId.test.log.ts +34 -2
- package/.output/public/assets/ProxyViewerContainer-BccuA6p5.js +0 -115
- package/.output/public/assets/_sessionId-9rEF0uSE.js +0 -1
- package/.output/public/assets/index-B9_VaAWl.js +0 -1
- package/.output/server/_tanstack-start-manifest_v-DPf3uJys.mjs +0 -4
package/src/mcp/server.ts
CHANGED
|
@@ -25,6 +25,14 @@ import {
|
|
|
25
25
|
type Variables,
|
|
26
26
|
WebStandardStreamableHTTPServerTransport,
|
|
27
27
|
} from "@modelcontextprotocol/server";
|
|
28
|
+
import {
|
|
29
|
+
AddInspectorGroupSessionInputSchema,
|
|
30
|
+
GroupEvidenceExportOptionsSchema,
|
|
31
|
+
InspectorGroupSchema,
|
|
32
|
+
InspectorGroupStatusSchema,
|
|
33
|
+
InspectorGroupsListResponseSchema,
|
|
34
|
+
UpdateInspectorGroupInputSchema,
|
|
35
|
+
} from "../lib/groupContract";
|
|
28
36
|
import {
|
|
29
37
|
InspectorRunSchema,
|
|
30
38
|
InspectorRunStatusSchema,
|
|
@@ -34,10 +42,14 @@ import { setCurrentPort } from "../lib/serverPort";
|
|
|
34
42
|
import { JsonValueSchema } from "../proxy/schemas";
|
|
35
43
|
import { callApi } from "./loopback";
|
|
36
44
|
import {
|
|
45
|
+
addGroupSessionImpl,
|
|
37
46
|
addProviderImpl,
|
|
47
|
+
createGroupImpl,
|
|
38
48
|
createRunImpl,
|
|
39
49
|
createSessionKnowledgeImpl,
|
|
40
50
|
exportEvidenceImpl,
|
|
51
|
+
exportGroupEvidenceImpl,
|
|
52
|
+
getGroupImpl,
|
|
41
53
|
getRecentFailuresImpl,
|
|
42
54
|
getProjectContextImpl,
|
|
43
55
|
getRunImpl,
|
|
@@ -45,6 +57,7 @@ import {
|
|
|
45
57
|
getLogChunksImpl,
|
|
46
58
|
getLogImpl,
|
|
47
59
|
getProviderImpl,
|
|
60
|
+
listGroupsImpl,
|
|
48
61
|
listKnowledgeCandidatesImpl,
|
|
49
62
|
listLogsImpl,
|
|
50
63
|
listModelsImpl,
|
|
@@ -56,6 +69,7 @@ import {
|
|
|
56
69
|
searchLogsImpl,
|
|
57
70
|
searchKnowledgeImpl,
|
|
58
71
|
testProviderImpl,
|
|
72
|
+
updateGroupImpl,
|
|
59
73
|
updateRunImpl,
|
|
60
74
|
updateProviderImpl,
|
|
61
75
|
} from "./toolHandlers";
|
|
@@ -172,6 +186,10 @@ const EvidenceReadResponseSchema = z.object({
|
|
|
172
186
|
markdown: z.string(),
|
|
173
187
|
});
|
|
174
188
|
|
|
189
|
+
const GroupEvidenceReadResponseSchema = z.object({
|
|
190
|
+
markdown: z.string(),
|
|
191
|
+
});
|
|
192
|
+
|
|
175
193
|
type ResourceDescriptor = {
|
|
176
194
|
uri: string;
|
|
177
195
|
name: string;
|
|
@@ -270,6 +288,66 @@ async function listRunResources(): Promise<ListResourcesResult> {
|
|
|
270
288
|
}
|
|
271
289
|
}
|
|
272
290
|
|
|
291
|
+
async function listGroupResources(): Promise<ListResourcesResult> {
|
|
292
|
+
try {
|
|
293
|
+
const response = await callApi("/api/groups");
|
|
294
|
+
if (!response.ok) return listResult([]);
|
|
295
|
+
const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
|
|
296
|
+
if (!parsed.success) return listResult([]);
|
|
297
|
+
return listResult(
|
|
298
|
+
parsed.data.groups.map((group) => ({
|
|
299
|
+
uri: `inspector://groups/${encodeURIComponent(group.id)}`,
|
|
300
|
+
name: `group:${group.id}`,
|
|
301
|
+
title: group.title,
|
|
302
|
+
mimeType: "application/json",
|
|
303
|
+
})),
|
|
304
|
+
);
|
|
305
|
+
} catch {
|
|
306
|
+
return listResult([]);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function listGroupSessionResources(): Promise<ListResourcesResult> {
|
|
311
|
+
try {
|
|
312
|
+
const response = await callApi("/api/groups");
|
|
313
|
+
if (!response.ok) return listResult([]);
|
|
314
|
+
const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
|
|
315
|
+
if (!parsed.success) return listResult([]);
|
|
316
|
+
return listResult(
|
|
317
|
+
parsed.data.groups.map((group) => ({
|
|
318
|
+
uri: `inspector://groups/${encodeURIComponent(group.id)}/sessions`,
|
|
319
|
+
name: `group-sessions:${group.id}`,
|
|
320
|
+
title: `${group.title} sessions`,
|
|
321
|
+
mimeType: "application/json",
|
|
322
|
+
})),
|
|
323
|
+
);
|
|
324
|
+
} catch {
|
|
325
|
+
return listResult([]);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function listGroupEvidenceResources(): Promise<ListResourcesResult> {
|
|
330
|
+
try {
|
|
331
|
+
const response = await callApi("/api/groups");
|
|
332
|
+
if (!response.ok) return listResult([]);
|
|
333
|
+
const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
|
|
334
|
+
if (!parsed.success) return listResult([]);
|
|
335
|
+
return listResult(
|
|
336
|
+
parsed.data.groups
|
|
337
|
+
.filter((group) => group.evidence !== null)
|
|
338
|
+
.map((group) => ({
|
|
339
|
+
uri: `inspector://groups/${encodeURIComponent(group.id)}/evidence`,
|
|
340
|
+
name: `group-evidence:${group.id}`,
|
|
341
|
+
title: `${group.title} evidence pack`,
|
|
342
|
+
mimeType: "text/markdown",
|
|
343
|
+
description: group.evidence?.markdownPath,
|
|
344
|
+
})),
|
|
345
|
+
);
|
|
346
|
+
} catch {
|
|
347
|
+
return listResult([]);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
273
351
|
async function listEvidenceResources(): Promise<ListResourcesResult> {
|
|
274
352
|
try {
|
|
275
353
|
const response = await callApi("/api/runs");
|
|
@@ -520,6 +598,92 @@ function registerTools(server: McpServer): void {
|
|
|
520
598
|
(args) => safeCall(() => getRecentFailuresImpl(callApi, args)),
|
|
521
599
|
);
|
|
522
600
|
|
|
601
|
+
server.registerTool(
|
|
602
|
+
"inspector_list_groups",
|
|
603
|
+
{
|
|
604
|
+
title: "List Inspector groups",
|
|
605
|
+
description:
|
|
606
|
+
"Lists declared Inspector groups. Groups aggregate multiple sessions/runs, commonly one evaluation matrix spanning several providers or models.",
|
|
607
|
+
inputSchema: z.object({}),
|
|
608
|
+
},
|
|
609
|
+
() => safeCall(() => listGroupsImpl(callApi)),
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
server.registerTool(
|
|
613
|
+
"inspector_create_group",
|
|
614
|
+
{
|
|
615
|
+
title: "Create or declare an Inspector group",
|
|
616
|
+
description:
|
|
617
|
+
"Creates an evaluation/batch group that external systems can use to aggregate multiple OpenCode/Codex sessions across models such as MiniMax, DeepSeek, or GLM.",
|
|
618
|
+
inputSchema: z.object({
|
|
619
|
+
groupId: z.string().min(1).optional().describe("Stable group id. Generated when omitted."),
|
|
620
|
+
title: z.string().min(1).optional().describe("Human-readable group title."),
|
|
621
|
+
kind: z
|
|
622
|
+
.enum(["evaluation", "batch", "manual"])
|
|
623
|
+
.optional()
|
|
624
|
+
.describe("Group kind. Defaults to evaluation."),
|
|
625
|
+
task: z.string().nullable().optional().describe("Task, benchmark, or evaluation goal."),
|
|
626
|
+
project: z.string().nullable().optional().describe("Project or repository under test."),
|
|
627
|
+
status: InspectorGroupStatusSchema.optional().describe("Initial group status."),
|
|
628
|
+
tags: z.array(z.string()).optional().describe("Free-form group labels."),
|
|
629
|
+
metadata: z.record(z.string(), JsonValueSchema).optional().describe("JSON metadata."),
|
|
630
|
+
}),
|
|
631
|
+
},
|
|
632
|
+
(args) => safeCall(() => createGroupImpl(callApi, args)),
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
server.registerTool(
|
|
636
|
+
"inspector_get_group",
|
|
637
|
+
{
|
|
638
|
+
title: "Get an Inspector group",
|
|
639
|
+
description:
|
|
640
|
+
"Returns one declared Inspector group with its attached session/run members and exported evidence paths when available.",
|
|
641
|
+
inputSchema: z.object({
|
|
642
|
+
groupId: z.string().min(1).describe("The Inspector group id."),
|
|
643
|
+
}),
|
|
644
|
+
},
|
|
645
|
+
(args) => safeCall(() => getGroupImpl(callApi, args)),
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
server.registerTool(
|
|
649
|
+
"inspector_update_group",
|
|
650
|
+
{
|
|
651
|
+
title: "Update an Inspector group",
|
|
652
|
+
description:
|
|
653
|
+
"PATCH-style update for a declared group. Use this to mark a whole evaluation running, failed, completed, or to merge CI metadata.",
|
|
654
|
+
inputSchema: UpdateInspectorGroupInputSchema.extend({
|
|
655
|
+
groupId: z.string().min(1).describe("The Inspector group id."),
|
|
656
|
+
}),
|
|
657
|
+
},
|
|
658
|
+
(args) => safeCall(() => updateGroupImpl(callApi, args)),
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
server.registerTool(
|
|
662
|
+
"inspector_add_group_session",
|
|
663
|
+
{
|
|
664
|
+
title: "Attach a session to an Inspector group",
|
|
665
|
+
description:
|
|
666
|
+
"Adds or updates one group member. Use it after each OpenCode process discovers its session id, or while an evaluation matrix is running to attach model/provider labels.",
|
|
667
|
+
inputSchema: AddInspectorGroupSessionInputSchema.extend({
|
|
668
|
+
groupId: z.string().min(1).describe("The Inspector group id."),
|
|
669
|
+
}),
|
|
670
|
+
},
|
|
671
|
+
(args) => safeCall(() => addGroupSessionImpl(callApi, args)),
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
server.registerTool(
|
|
675
|
+
"inspector_export_group_evidence",
|
|
676
|
+
{
|
|
677
|
+
title: "Export an Inspector group evidence pack",
|
|
678
|
+
description:
|
|
679
|
+
"Exports a whole group's evidence pack to local JSON, Markdown, and HTML files under <dataDir>/evidence/groups/<groupId>. The export contains a provider/model matrix and compact session summaries.",
|
|
680
|
+
inputSchema: GroupEvidenceExportOptionsSchema.unwrap().extend({
|
|
681
|
+
groupId: z.string().min(1).describe("The Inspector group id."),
|
|
682
|
+
}),
|
|
683
|
+
},
|
|
684
|
+
(args) => safeCall(() => exportGroupEvidenceImpl(callApi, args)),
|
|
685
|
+
);
|
|
686
|
+
|
|
523
687
|
server.registerTool(
|
|
524
688
|
"inspector_list_models",
|
|
525
689
|
{
|
|
@@ -763,6 +927,49 @@ function registerResources(server: McpServer): void {
|
|
|
763
927
|
(uri) => readApiJsonResource(uri, "/api/runs"),
|
|
764
928
|
);
|
|
765
929
|
|
|
930
|
+
server.registerResource(
|
|
931
|
+
"inspector_groups",
|
|
932
|
+
"inspector://groups",
|
|
933
|
+
{
|
|
934
|
+
title: "Inspector groups",
|
|
935
|
+
description: "Declared Inspector groups aggregating multiple sessions or runs.",
|
|
936
|
+
mimeType: "application/json",
|
|
937
|
+
},
|
|
938
|
+
(uri) => readApiJsonResource(uri, "/api/groups"),
|
|
939
|
+
);
|
|
940
|
+
|
|
941
|
+
server.registerResource(
|
|
942
|
+
"inspector_group",
|
|
943
|
+
new ResourceTemplate("inspector://groups/{groupId}", { list: listGroupResources }),
|
|
944
|
+
{
|
|
945
|
+
title: "Inspector group",
|
|
946
|
+
description: "One declared Inspector group with attached session/run members.",
|
|
947
|
+
mimeType: "application/json",
|
|
948
|
+
},
|
|
949
|
+
(uri, variables) => {
|
|
950
|
+
const groupId = variableString(variables, "groupId");
|
|
951
|
+
if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
|
|
952
|
+
return readApiJsonResource(uri, `/api/groups/${encodeURIComponent(groupId)}`);
|
|
953
|
+
},
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
server.registerResource(
|
|
957
|
+
"inspector_group_sessions",
|
|
958
|
+
new ResourceTemplate("inspector://groups/{groupId}/sessions", {
|
|
959
|
+
list: listGroupSessionResources,
|
|
960
|
+
}),
|
|
961
|
+
{
|
|
962
|
+
title: "Inspector group sessions",
|
|
963
|
+
description: "Attached session/run members for one Inspector group.",
|
|
964
|
+
mimeType: "application/json",
|
|
965
|
+
},
|
|
966
|
+
(uri, variables) => {
|
|
967
|
+
const groupId = variableString(variables, "groupId");
|
|
968
|
+
if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
|
|
969
|
+
return readApiJsonResource(uri, `/api/groups/${encodeURIComponent(groupId)}/sessions`);
|
|
970
|
+
},
|
|
971
|
+
);
|
|
972
|
+
|
|
766
973
|
server.registerResource(
|
|
767
974
|
"inspector_recent_failures",
|
|
768
975
|
"inspector://failures/recent",
|
|
@@ -819,6 +1026,38 @@ function registerResources(server: McpServer): void {
|
|
|
819
1026
|
}
|
|
820
1027
|
},
|
|
821
1028
|
);
|
|
1029
|
+
|
|
1030
|
+
server.registerResource(
|
|
1031
|
+
"inspector_group_evidence",
|
|
1032
|
+
new ResourceTemplate("inspector://groups/{groupId}/evidence", {
|
|
1033
|
+
list: listGroupEvidenceResources,
|
|
1034
|
+
}),
|
|
1035
|
+
{
|
|
1036
|
+
title: "Inspector group evidence pack",
|
|
1037
|
+
description:
|
|
1038
|
+
"Markdown evidence pack for an Inspector group. Generate it first with inspector_export_group_evidence.",
|
|
1039
|
+
mimeType: "text/markdown",
|
|
1040
|
+
},
|
|
1041
|
+
async (uri, variables) => {
|
|
1042
|
+
const groupId = variableString(variables, "groupId");
|
|
1043
|
+
if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
|
|
1044
|
+
const path = `/api/groups/${encodeURIComponent(groupId)}/evidence`;
|
|
1045
|
+
try {
|
|
1046
|
+
const response = await callApi(path);
|
|
1047
|
+
if (!response.ok) {
|
|
1048
|
+
return textResource(uri, "text/plain", `GET ${path} returned ${response.status}`);
|
|
1049
|
+
}
|
|
1050
|
+
const parsed = GroupEvidenceReadResponseSchema.safeParse(await response.json());
|
|
1051
|
+
if (!parsed.success) {
|
|
1052
|
+
return textResource(uri, "text/plain", "Evidence endpoint returned an unparseable body");
|
|
1053
|
+
}
|
|
1054
|
+
return textResource(uri, "text/markdown", parsed.data.markdown);
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1057
|
+
return textResource(uri, "text/plain", `Resource read failed: ${message}`);
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
);
|
|
822
1061
|
}
|
|
823
1062
|
|
|
824
1063
|
function registerPrompts(server: McpServer): void {
|
|
@@ -911,6 +1150,34 @@ The report should include:
|
|
|
911
1150
|
5. Reproducible next action for a developer`),
|
|
912
1151
|
);
|
|
913
1152
|
|
|
1153
|
+
server.registerPrompt(
|
|
1154
|
+
"inspector_generate_group_report",
|
|
1155
|
+
{
|
|
1156
|
+
title: "Generate group report",
|
|
1157
|
+
description:
|
|
1158
|
+
"Generate a model/provider evaluation report from an Inspector group and group evidence pack.",
|
|
1159
|
+
argsSchema: z.object({
|
|
1160
|
+
groupId: z.string().min(1).describe("The Inspector group id."),
|
|
1161
|
+
}),
|
|
1162
|
+
},
|
|
1163
|
+
({ groupId }) =>
|
|
1164
|
+
userPrompt(`Generate a model/provider evaluation report for this Inspector group.
|
|
1165
|
+
|
|
1166
|
+
Evidence to read:
|
|
1167
|
+
- Group resource: inspector://groups/${encodeURIComponent(groupId)}
|
|
1168
|
+
- Group session members: inspector://groups/${encodeURIComponent(groupId)}/sessions
|
|
1169
|
+
- Group evidence pack: inspector://groups/${encodeURIComponent(groupId)}/evidence
|
|
1170
|
+
|
|
1171
|
+
If the evidence pack resource says it has not been exported, call inspector_export_group_evidence for this group first.
|
|
1172
|
+
|
|
1173
|
+
The report should include:
|
|
1174
|
+
1. Evaluation status summary
|
|
1175
|
+
2. Provider/model matrix with session links
|
|
1176
|
+
3. Request counts, token totals, streaming timing, and error counts
|
|
1177
|
+
4. Winner/loser observations when the evidence supports them
|
|
1178
|
+
5. Concrete next actions for failed or missing sessions`),
|
|
1179
|
+
);
|
|
1180
|
+
|
|
914
1181
|
server.registerPrompt(
|
|
915
1182
|
"inspector_extract_repro_steps",
|
|
916
1183
|
{
|
|
@@ -950,6 +1217,12 @@ export const TOOL_NAMES = [
|
|
|
950
1217
|
"inspector_update_run",
|
|
951
1218
|
"inspector_export_evidence",
|
|
952
1219
|
"inspector_get_recent_failures",
|
|
1220
|
+
"inspector_list_groups",
|
|
1221
|
+
"inspector_create_group",
|
|
1222
|
+
"inspector_get_group",
|
|
1223
|
+
"inspector_update_group",
|
|
1224
|
+
"inspector_add_group_session",
|
|
1225
|
+
"inspector_export_group_evidence",
|
|
953
1226
|
"inspector_list_models",
|
|
954
1227
|
"inspector_list_providers",
|
|
955
1228
|
"inspector_get_provider",
|
|
@@ -972,11 +1245,16 @@ export const RESOURCE_NAMES = [
|
|
|
972
1245
|
"inspector_recent_failures",
|
|
973
1246
|
"inspector_run",
|
|
974
1247
|
"inspector_evidence",
|
|
1248
|
+
"inspector_groups",
|
|
1249
|
+
"inspector_group",
|
|
1250
|
+
"inspector_group_sessions",
|
|
1251
|
+
"inspector_group_evidence",
|
|
975
1252
|
] as const;
|
|
976
1253
|
|
|
977
1254
|
export const PROMPT_NAMES = [
|
|
978
1255
|
"inspector_analyze_session_failure",
|
|
979
1256
|
"inspector_triage_recent_failures",
|
|
980
1257
|
"inspector_generate_jenkins_report",
|
|
1258
|
+
"inspector_generate_group_report",
|
|
981
1259
|
"inspector_extract_repro_steps",
|
|
982
1260
|
] as const;
|
package/src/mcp/toolHandlers.ts
CHANGED
|
@@ -17,6 +17,14 @@ import { Buffer } from "node:buffer";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
import { LoopbackTimeoutError, type CallApiOptions } from "./loopback";
|
|
19
19
|
import { extractLastUserMessagePreview, extractResponsePreview } from "./previewExtractor";
|
|
20
|
+
import {
|
|
21
|
+
GroupEvidenceExportResultSchema,
|
|
22
|
+
InspectorGroupSchema,
|
|
23
|
+
InspectorGroupsListResponseSchema,
|
|
24
|
+
type AddInspectorGroupSessionInput,
|
|
25
|
+
type CreateInspectorGroupInput,
|
|
26
|
+
type UpdateInspectorGroupInput,
|
|
27
|
+
} from "../lib/groupContract";
|
|
20
28
|
import {
|
|
21
29
|
EvidenceExportResultSchema,
|
|
22
30
|
InspectorRunSchema,
|
|
@@ -132,6 +140,9 @@ export function buildLogSummary(log: CapturedLog) {
|
|
|
132
140
|
isStreaming: log.streaming,
|
|
133
141
|
hasError,
|
|
134
142
|
latencyMs: log.elapsedMs,
|
|
143
|
+
firstChunkMs: log.firstChunkMs ?? null,
|
|
144
|
+
totalStreamMs: log.totalStreamMs ?? null,
|
|
145
|
+
tokensPerSecond: log.tokensPerSecond ?? null,
|
|
135
146
|
tokens: {
|
|
136
147
|
input: log.inputTokens,
|
|
137
148
|
output: log.outputTokens,
|
|
@@ -372,6 +383,121 @@ export async function exportEvidenceImpl(
|
|
|
372
383
|
return textJson(parsed.data);
|
|
373
384
|
}
|
|
374
385
|
|
|
386
|
+
export async function listGroupsImpl(callApi: CallApiFn): Promise<ToolResult> {
|
|
387
|
+
const res = await callApi("/api/groups");
|
|
388
|
+
if (!res.ok) return toolError(`GET /api/groups returned ${res.status}`);
|
|
389
|
+
const rawBody: unknown = await res.json();
|
|
390
|
+
const parsed = InspectorGroupsListResponseSchema.safeParse(rawBody);
|
|
391
|
+
if (!parsed.success) return toolError("GET /api/groups returned an unparseable group list");
|
|
392
|
+
return textJson(parsed.data);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export async function createGroupImpl(
|
|
396
|
+
callApi: CallApiFn,
|
|
397
|
+
args: CreateInspectorGroupInput,
|
|
398
|
+
): Promise<ToolResult> {
|
|
399
|
+
const res = await callApi("/api/groups", {
|
|
400
|
+
method: "POST",
|
|
401
|
+
headers: { "content-type": "application/json" },
|
|
402
|
+
body: JSON.stringify(args ?? {}),
|
|
403
|
+
});
|
|
404
|
+
if (!res.ok) return toolError(`POST /api/groups returned ${res.status}`);
|
|
405
|
+
const rawBody: unknown = await res.json();
|
|
406
|
+
const parsed = InspectorGroupSchema.safeParse(rawBody);
|
|
407
|
+
if (!parsed.success) return toolError("POST /api/groups returned an unparseable group");
|
|
408
|
+
return textJson(parsed.data);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export type GetGroupArgs = {
|
|
412
|
+
groupId: string;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
export async function getGroupImpl(callApi: CallApiFn, args: GetGroupArgs): Promise<ToolResult> {
|
|
416
|
+
const path = `/api/groups/${encodeURIComponent(args.groupId)}`;
|
|
417
|
+
const res = await callApi(path);
|
|
418
|
+
if (!res.ok) return toolError(`GET ${path} returned ${res.status}`);
|
|
419
|
+
const rawBody: unknown = await res.json();
|
|
420
|
+
const parsed = InspectorGroupSchema.safeParse(rawBody);
|
|
421
|
+
if (!parsed.success) return toolError("GET /api/groups/{groupId} returned an unparseable group");
|
|
422
|
+
return textJson(parsed.data);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export type UpdateGroupArgs = {
|
|
426
|
+
groupId: string;
|
|
427
|
+
} & UpdateInspectorGroupInput;
|
|
428
|
+
|
|
429
|
+
export async function updateGroupImpl(
|
|
430
|
+
callApi: CallApiFn,
|
|
431
|
+
args: UpdateGroupArgs,
|
|
432
|
+
): Promise<ToolResult> {
|
|
433
|
+
const { groupId, ...patch } = args;
|
|
434
|
+
const path = `/api/groups/${encodeURIComponent(groupId)}`;
|
|
435
|
+
const res = await callApi(path, {
|
|
436
|
+
method: "PATCH",
|
|
437
|
+
headers: { "content-type": "application/json" },
|
|
438
|
+
body: JSON.stringify(patch),
|
|
439
|
+
});
|
|
440
|
+
if (!res.ok) return toolError(`PATCH ${path} returned ${res.status}`);
|
|
441
|
+
const rawBody: unknown = await res.json();
|
|
442
|
+
const parsed = InspectorGroupSchema.safeParse(rawBody);
|
|
443
|
+
if (!parsed.success) {
|
|
444
|
+
return toolError("PATCH /api/groups/{groupId} returned an unparseable group");
|
|
445
|
+
}
|
|
446
|
+
return textJson(parsed.data);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export type AddGroupSessionArgs = {
|
|
450
|
+
groupId: string;
|
|
451
|
+
} & AddInspectorGroupSessionInput;
|
|
452
|
+
|
|
453
|
+
export async function addGroupSessionImpl(
|
|
454
|
+
callApi: CallApiFn,
|
|
455
|
+
args: AddGroupSessionArgs,
|
|
456
|
+
): Promise<ToolResult> {
|
|
457
|
+
const { groupId, ...session } = args;
|
|
458
|
+
const path = `/api/groups/${encodeURIComponent(groupId)}/sessions`;
|
|
459
|
+
const res = await callApi(path, {
|
|
460
|
+
method: "POST",
|
|
461
|
+
headers: { "content-type": "application/json" },
|
|
462
|
+
body: JSON.stringify(session),
|
|
463
|
+
});
|
|
464
|
+
if (!res.ok) return toolError(`POST ${path} returned ${res.status}`);
|
|
465
|
+
const rawBody: unknown = await res.json();
|
|
466
|
+
const parsed = InspectorGroupSchema.safeParse(rawBody);
|
|
467
|
+
if (!parsed.success) {
|
|
468
|
+
return toolError("POST /api/groups/{groupId}/sessions returned an unparseable group");
|
|
469
|
+
}
|
|
470
|
+
return textJson(parsed.data);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export type ExportGroupEvidenceArgs = {
|
|
474
|
+
groupId: string;
|
|
475
|
+
includeHistory?: boolean;
|
|
476
|
+
latestLogLimit?: number;
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
export async function exportGroupEvidenceImpl(
|
|
480
|
+
callApi: CallApiFn,
|
|
481
|
+
args: ExportGroupEvidenceArgs,
|
|
482
|
+
): Promise<ToolResult> {
|
|
483
|
+
const path = `/api/groups/${encodeURIComponent(args.groupId)}/evidence`;
|
|
484
|
+
const res = await callApi(path, {
|
|
485
|
+
method: "POST",
|
|
486
|
+
headers: { "content-type": "application/json" },
|
|
487
|
+
body: JSON.stringify({
|
|
488
|
+
includeHistory: args.includeHistory,
|
|
489
|
+
latestLogLimit: args.latestLogLimit,
|
|
490
|
+
}),
|
|
491
|
+
});
|
|
492
|
+
if (!res.ok) return toolError(`POST ${path} returned ${res.status}`);
|
|
493
|
+
const rawBody: unknown = await res.json();
|
|
494
|
+
const parsed = GroupEvidenceExportResultSchema.safeParse(rawBody);
|
|
495
|
+
if (!parsed.success) {
|
|
496
|
+
return toolError("POST /api/groups/{groupId}/evidence returned an unparseable evidence result");
|
|
497
|
+
}
|
|
498
|
+
return textJson(parsed.data);
|
|
499
|
+
}
|
|
500
|
+
|
|
375
501
|
export async function listModelsImpl(callApi: CallApiFn): Promise<ToolResult> {
|
|
376
502
|
const res = await callApi("/api/models");
|
|
377
503
|
if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
|
|
@@ -318,6 +318,11 @@ function logDetails(log: SessionLogSummary): string {
|
|
|
318
318
|
`streaming=${log.isStreaming ? "yes" : "no"}`,
|
|
319
319
|
`latencyMs=${formatNullable(log.latencyMs)}`,
|
|
320
320
|
];
|
|
321
|
+
if (log.isStreaming) {
|
|
322
|
+
parts.push(`firstChunkMs=${formatNullable(log.firstChunkMs ?? null)}`);
|
|
323
|
+
parts.push(`totalStreamMs=${formatNullable(log.totalStreamMs ?? null)}`);
|
|
324
|
+
parts.push(`tokensPerSecond=${formatNullable(log.tokensPerSecond ?? null)}`);
|
|
325
|
+
}
|
|
321
326
|
if (log.error !== null) {
|
|
322
327
|
parts.push(`error=${log.error}`);
|
|
323
328
|
}
|
|
@@ -112,14 +112,19 @@ function buildMarkdown(document: EvidenceDocument): string {
|
|
|
112
112
|
lines.push("No compact logs were available.", "");
|
|
113
113
|
} else {
|
|
114
114
|
lines.push(
|
|
115
|
-
"| ID | Status | Model | Latency | Path | Error |",
|
|
116
|
-
"| --- | --- | --- | --- | --- | --- |",
|
|
115
|
+
"| ID | Status | Model | Latency | Stream | Path | Error |",
|
|
116
|
+
"| --- | --- | --- | --- | --- | --- | --- |",
|
|
117
117
|
);
|
|
118
118
|
for (const log of session.latestLogs) {
|
|
119
|
+
const streamTiming = log.isStreaming
|
|
120
|
+
? `first=${log.firstChunkMs ?? "n/a"}ms total=${log.totalStreamMs ?? "n/a"}ms tps=${
|
|
121
|
+
log.tokensPerSecond ?? "n/a"
|
|
122
|
+
}`
|
|
123
|
+
: "n/a";
|
|
119
124
|
lines.push(
|
|
120
125
|
`| ${log.id} | ${log.status ?? "n/a"} | ${log.model ?? "n/a"} | ${
|
|
121
126
|
log.latencyMs ?? "n/a"
|
|
122
|
-
} | ${log.path} | ${log.hasError ? "yes" : "no"} |`,
|
|
127
|
+
} | ${streamTiming} | ${log.path} | ${log.hasError ? "yes" : "no"} |`,
|
|
123
128
|
);
|
|
124
129
|
}
|
|
125
130
|
lines.push("");
|