@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.
Files changed (54) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-CicCP3Hb.js → CompareDrawer-BDzfRSt1.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-NtOD8Ex_.js +115 -0
  4. package/.output/public/assets/{ReplayDialog-Bc8q3ujm.js → ReplayDialog-p-5TKg75.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-Coyy3zcH.js → RequestAnatomy-G0dV8-D_.js} +1 -1
  6. package/.output/public/assets/{ResponseView-BWJzgh0c.js → ResponseView-D2wE6lD-.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-BSC3uuE5.js → StreamingChunkSequence-D8JRK-j_.js} +1 -1
  8. package/.output/public/assets/_sessionId-zimzVrJE.js +1 -0
  9. package/.output/public/assets/index-Cv5vPgpr.js +1 -0
  10. package/.output/public/assets/{index-DsiKfWCp.css → index-CwlHPmgL.css} +1 -1
  11. package/.output/public/assets/{main-Brj0Gn91.js → main-XHHZsOBK.js} +2 -2
  12. package/.output/server/_libs/lucide-react.mjs +13 -13
  13. package/.output/server/{_sessionId-DDdyKVG-.mjs → _sessionId-CK9QEhkB.mjs} +3 -3
  14. package/.output/server/_ssr/{CompareDrawer-XSExe2ml.mjs → CompareDrawer-BJVPFMIx.mjs} +2 -2
  15. package/.output/server/_ssr/{ProxyViewerContainer-B5pIiBZN.mjs → ProxyViewerContainer-DhbmJ2zj.mjs} +83 -20
  16. package/.output/server/_ssr/{ReplayDialog-BOAu0ric.mjs → ReplayDialog-DXRj95Gj.mjs} +4 -4
  17. package/.output/server/_ssr/{RequestAnatomy-C8Q5lozD.mjs → RequestAnatomy-CrYJTXdJ.mjs} +2 -2
  18. package/.output/server/_ssr/{ResponseView-a_iJfPUF.mjs → ResponseView-4ESqSITr.mjs} +2 -2
  19. package/.output/server/_ssr/{StreamingChunkSequence-YMKC0j8U.mjs → StreamingChunkSequence-Czdko5n_.mjs} +2 -2
  20. package/.output/server/_ssr/{index-CkDwBPzI.mjs → index-bei0I7qL.mjs} +2 -2
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{router-DXOXdFFM.mjs → router-sQh4Gk8J.mjs} +1338 -137
  23. package/.output/server/_tanstack-start-manifest_v-C0fdSWnA.mjs +4 -0
  24. package/.output/server/index.mjs +64 -64
  25. package/README.md +24 -1
  26. package/package.json +1 -1
  27. package/src/components/ProxyViewer.tsx +3 -0
  28. package/src/components/providers/ProviderCard.tsx +32 -0
  29. package/src/components/proxy-viewer/LogEntryHeader.tsx +38 -1
  30. package/src/lib/groupContract.ts +139 -0
  31. package/src/lib/providerTestContract.ts +3 -0
  32. package/src/lib/sessionInfoContract.ts +3 -0
  33. package/src/mcp/server.ts +278 -0
  34. package/src/mcp/toolHandlers.ts +126 -0
  35. package/src/proxy/evidenceAnalysis.ts +5 -0
  36. package/src/proxy/evidenceExporter.ts +8 -3
  37. package/src/proxy/groupEvidenceExporter.ts +341 -0
  38. package/src/proxy/groupStore.ts +259 -0
  39. package/src/proxy/handler.ts +8 -1
  40. package/src/proxy/logFinalizer.ts +36 -0
  41. package/src/proxy/schemas.ts +3 -0
  42. package/src/proxy/sessionInfo.ts +3 -0
  43. package/src/proxy/store.ts +3 -0
  44. package/src/proxy/streamTiming.ts +31 -0
  45. package/src/routes/api/groups.$groupId.evidence.ts +62 -0
  46. package/src/routes/api/groups.$groupId.sessions.ts +50 -0
  47. package/src/routes/api/groups.$groupId.ts +50 -0
  48. package/src/routes/api/groups.ts +39 -0
  49. package/src/routes/api/logs.$id.replay.ts +25 -2
  50. package/src/routes/api/providers.$providerId.test.log.ts +34 -2
  51. package/.output/public/assets/ProxyViewerContainer-BccuA6p5.js +0 -115
  52. package/.output/public/assets/_sessionId-9rEF0uSE.js +0 -1
  53. package/.output/public/assets/index-B9_VaAWl.js +0 -1
  54. package/.output/server/_tanstack-start-manifest_v-DPf3uJys.mjs +0 -4
@@ -50,8 +50,8 @@ import "../_libs/mimic-function.mjs";
50
50
  import "../_libs/semver.mjs";
51
51
  import "../_libs/uint8array-extras.mjs";
52
52
  const faviconSvg = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2064%2064'%20role='img'%20aria-label='Agent%20Inspector'%3e%3crect%20width='64'%20height='64'%20rx='14'%20fill='%23111827'%20/%3e%3cg%20fill='none'%20stroke='%23f59e0b'%20stroke-width='4.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3e%3cpath%20fill='%23f59e0b'%20d='M15%2036c0-11%207-18%2017-18s17%207%2017%2018c0%208-7%2013-17%2013s-17-5-17-13z'%20/%3e%3cpath%20d='M16%2031c-6-5-12-3-12%204%200%205%206%206%2011%202'%20/%3e%3cpath%20d='M48%2031c6-5%2012-3%2012%204%200%205-6%206-11%202'%20/%3e%3cpath%20d='M27%2019l-3-7'%20/%3e%3cpath%20d='M37%2019l3-7'%20/%3e%3cpath%20d='M19%2045l-6%209'%20/%3e%3cpath%20d='M27%2048l-3%209'%20/%3e%3cpath%20d='M37%2048l3%209'%20/%3e%3cpath%20d='M45%2045l6%209'%20/%3e%3c/g%3e%3cpath%20d='M14%2047l9-8%208%209c-5%203.5-12%203-17-1z'%20fill='%232f6b3f'%20opacity='.95'%20/%3e%3cpath%20d='M18%2046l5-5%205%206c-3%201.6-7%201.4-10-1z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M23%2041v10'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='1.6'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='31'%20cy='48'%20r='1.8'%20fill='%23c2412d'%20/%3e%3cpath%20d='M24%2044l13-11%2014%2013c-8%205.8-18%205.3-27-2z'%20fill='%232f6b3f'%20opacity='.97'%20/%3e%3cpath%20d='M30%2043l7-7%209%209c-5%202.7-11%202.5-16-2z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M37%2036v14'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='2'%20stroke-linecap='round'%20/%3e%3cpath%20d='M30%2043c5-2%2011-1.5%2016%202'%20fill='none'%20stroke='%239fca78'%20stroke-width='1.8'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='51'%20cy='46'%20r='2'%20fill='%23c2412d'%20/%3e%3cpath%20d='M40%2050l8-8%207%208c-4.2%203.2-10%203-15%200z'%20fill='%232f6b3f'%20opacity='.95'%20/%3e%3cpath%20d='M43%2049l5-5%204.5%205.5c-3%201.5-6%201.2-9.5-.5z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M48%2044v9'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='1.5'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='55'%20cy='50'%20r='1.7'%20fill='%23c2412d'%20/%3e%3ccircle%20cx='24'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='40'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='25'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3ccircle%20cx='39'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3c/svg%3e";
53
- const appCss = "/assets/index-DsiKfWCp.css";
54
- const Route$v = createRootRoute({
53
+ const appCss = "/assets/index-CwlHPmgL.css";
54
+ const Route$z = createRootRoute({
55
55
  head: () => ({
56
56
  meta: [
57
57
  { charSet: "utf-8" },
@@ -93,8 +93,8 @@ function RootDocument({ children }) {
93
93
  ] })
94
94
  ] });
95
95
  }
96
- const $$splitComponentImporter$1 = () => import("./index-CkDwBPzI.mjs");
97
- const Route$u = createFileRoute("/")({
96
+ const $$splitComponentImporter$1 = () => import("./index-bei0I7qL.mjs");
97
+ const Route$y = createFileRoute("/")({
98
98
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
99
99
  });
100
100
  const B64URL_RE = /^[A-Za-z0-9_-]+$/;
@@ -136,8 +136,8 @@ function decodeSessionIdFromPath(encoded) {
136
136
  function getSessionPath(sessionId) {
137
137
  return `/session/${encodeSessionIdForPath(sessionId)}`;
138
138
  }
139
- const $$splitComponentImporter = () => import("../_sessionId-DDdyKVG-.mjs");
140
- const Route$t = createFileRoute("/session/$sessionId")({
139
+ const $$splitComponentImporter = () => import("../_sessionId-CK9QEhkB.mjs");
140
+ const Route$x = createFileRoute("/session/$sessionId")({
141
141
  component: lazyRouteComponent($$splitComponentImporter, "component"),
142
142
  parseParams: (params) => ({
143
143
  sessionId: decodeSessionIdFromPath(params.sessionId)
@@ -713,6 +713,9 @@ const CapturedLogSchema = object({
713
713
  cacheCreationInputTokens: number().nullable(),
714
714
  cacheReadInputTokens: number().nullable(),
715
715
  elapsedMs: number().nullable(),
716
+ firstChunkMs: number().nullable().optional(),
717
+ totalStreamMs: number().nullable().optional(),
718
+ tokensPerSecond: number().nullable().optional(),
716
719
  streaming: boolean(),
717
720
  userAgent: string().nullable(),
718
721
  origin: string().nullable(),
@@ -1066,6 +1069,9 @@ function buildSessionLogSummary(log) {
1066
1069
  hasError: hasLogError(log),
1067
1070
  error: truncateError(log.error),
1068
1071
  latencyMs: log.elapsedMs,
1072
+ firstChunkMs: log.firstChunkMs ?? null,
1073
+ totalStreamMs: log.totalStreamMs ?? null,
1074
+ tokensPerSecond: log.tokensPerSecond ?? null,
1069
1075
  tokens: {
1070
1076
  input: log.inputTokens,
1071
1077
  output: log.outputTokens,
@@ -1079,7 +1085,7 @@ function nonNegativeInteger(value) {
1079
1085
  if (value === null || !Number.isFinite(value) || value < 0) return 0;
1080
1086
  return Math.floor(value);
1081
1087
  }
1082
- function addTokenUsage(total, summary) {
1088
+ function addTokenUsage$1(total, summary) {
1083
1089
  const input = nonNegativeInteger(summary.tokens.input);
1084
1090
  const output = nonNegativeInteger(summary.tokens.output);
1085
1091
  const cacheCreate = nonNegativeInteger(summary.tokens.cacheCreate);
@@ -1109,7 +1115,7 @@ function buildApiPath(sessionId) {
1109
1115
  function buildCompactLogsPath(sessionId) {
1110
1116
  return buildRelativePath("/api/logs", new URLSearchParams({ compact: "1", sessionId }));
1111
1117
  }
1112
- function collectSortedValues(values) {
1118
+ function collectSortedValues$1(values) {
1113
1119
  const set = /* @__PURE__ */ new Set();
1114
1120
  for (const value of values) {
1115
1121
  if (value !== null && value.length > 0) {
@@ -1153,7 +1159,7 @@ function buildSessionInfo(input) {
1153
1159
  const errorCount = Math.max(countErrorLogs(summaries), input.snapshot?.failedRequests ?? 0);
1154
1160
  const completedLogCount = countCompletedLogs(summaries);
1155
1161
  const tokenUsage = summaries.reduce(
1156
- (total, summary) => addTokenUsage(total, summary),
1162
+ (total, summary) => addTokenUsage$1(total, summary),
1157
1163
  { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, total: 0 }
1158
1164
  );
1159
1165
  const requestCount = Math.max(input.snapshot?.requestCount ?? 0, logCount);
@@ -1191,8 +1197,8 @@ function buildSessionInfo(input) {
1191
1197
  lastTaskError,
1192
1198
  logCount,
1193
1199
  errorCount,
1194
- models: collectSortedValues(summaries.map((summary) => summary.model)),
1195
- providers: collectSortedValues(summaries.map((summary) => summary.provider)),
1200
+ models: collectSortedValues$1(summaries.map((summary) => summary.model)),
1201
+ providers: collectSortedValues$1(summaries.map((summary) => summary.provider)),
1196
1202
  tokenUsage,
1197
1203
  lastLogId: input.snapshot?.lastLogId ?? latestLog?.id ?? null,
1198
1204
  lastModel: normalizeString(input.snapshot?.lastModel) ?? latestLog?.model ?? null,
@@ -1602,6 +1608,9 @@ async function createLog(method, path2, requestBody, headers, clientInfo, rawHea
1602
1608
  cacheCreationInputTokens: null,
1603
1609
  cacheReadInputTokens: null,
1604
1610
  elapsedMs: null,
1611
+ firstChunkMs: null,
1612
+ totalStreamMs: null,
1613
+ tokensPerSecond: null,
1605
1614
  streaming: false,
1606
1615
  userAgent,
1607
1616
  origin,
@@ -4145,6 +4154,23 @@ function stripOpenAIOrphanToolMessages(rawBody) {
4145
4154
  parsed.messages = kept;
4146
4155
  return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
4147
4156
  }
4157
+ function computeTokensPerSecond({
4158
+ outputTokens,
4159
+ firstChunkMs,
4160
+ totalStreamMs
4161
+ }) {
4162
+ if (outputTokens === null || outputTokens === void 0 || outputTokens <= 0) return null;
4163
+ if (totalStreamMs === null || totalStreamMs === void 0 || totalStreamMs <= 0) return null;
4164
+ const generationMs = firstChunkMs === null || firstChunkMs === void 0 ? totalStreamMs : totalStreamMs - firstChunkMs;
4165
+ if (!Number.isFinite(generationMs) || generationMs <= 0) return null;
4166
+ return Number((outputTokens / (generationMs / 1e3)).toFixed(2));
4167
+ }
4168
+ function firstChunkMsFromChunks(chunks) {
4169
+ const first = chunks?.[0];
4170
+ if (first === void 0) return null;
4171
+ if (!Number.isFinite(first.timestamp) || first.timestamp < 0) return null;
4172
+ return Math.floor(first.timestamp);
4173
+ }
4148
4174
  const DEFAULT_MAX_FINALIZE_STREAM_BYTES = 20 * 1024 * 1024;
4149
4175
  function resolveMaxFinalizeStreamBytes() {
4150
4176
  const raw = process.env["AGENT_INSPECTOR_MAX_FINALIZE_STREAM_BYTES"];
@@ -4169,6 +4195,9 @@ function buildFileLogEntry(log, upstreamUrl) {
4169
4195
  cacheCreationInputTokens: log.cacheCreationInputTokens,
4170
4196
  cacheReadInputTokens: log.cacheReadInputTokens,
4171
4197
  elapsedMs: log.elapsedMs,
4198
+ firstChunkMs: log.firstChunkMs,
4199
+ totalStreamMs: log.totalStreamMs,
4200
+ tokensPerSecond: log.tokensPerSecond,
4172
4201
  streaming: log.streaming,
4173
4202
  userAgent: log.userAgent,
4174
4203
  origin: log.origin,
@@ -4204,9 +4233,29 @@ function finalizeWithError(job, log, fallbackStatus, fallbackResponseText, err,
4204
4233
  log.responseStatus = log.responseStatus ?? fallbackStatus;
4205
4234
  log.responseText = log.responseText ?? fallbackResponseText;
4206
4235
  log.elapsedMs = job.elapsedMs;
4236
+ applyOptionalStreamingTiming(log, job);
4207
4237
  log.error = message;
4208
4238
  return { log, upstreamUrl: job.upstreamUrl, error: message, cleanupRawStreamPath };
4209
4239
  }
4240
+ function applyOptionalStreamingTiming(log, job) {
4241
+ switch (job.type) {
4242
+ case "non-streaming":
4243
+ log.firstChunkMs = null;
4244
+ log.totalStreamMs = null;
4245
+ log.tokensPerSecond = null;
4246
+ return;
4247
+ case "streaming":
4248
+ case "stream-abort":
4249
+ log.firstChunkMs = job.firstChunkMs;
4250
+ log.totalStreamMs = job.totalStreamMs;
4251
+ log.tokensPerSecond = computeTokensPerSecond({
4252
+ outputTokens: log.outputTokens,
4253
+ firstChunkMs: job.firstChunkMs,
4254
+ totalStreamMs: job.totalStreamMs
4255
+ });
4256
+ return;
4257
+ }
4258
+ }
4210
4259
  function finalizeNonStreaming(job, log) {
4211
4260
  const formatHandler = formatForPath(log.path);
4212
4261
  if (formatHandler === null) {
@@ -4215,6 +4264,9 @@ function finalizeNonStreaming(job, log) {
4215
4264
  try {
4216
4265
  const tokens = formatHandler.extractTokens(job.responseBody);
4217
4266
  log.elapsedMs = job.elapsedMs;
4267
+ log.firstChunkMs = null;
4268
+ log.totalStreamMs = null;
4269
+ log.tokensPerSecond = null;
4218
4270
  log.responseStatus = job.responseStatus;
4219
4271
  log.responseText = job.responseBody;
4220
4272
  log.inputTokens = tokens.inputTokens;
@@ -4295,6 +4347,7 @@ function finalizeStreaming(job, log) {
4295
4347
  const oversized = getOversizedRawStream(job.rawStream);
4296
4348
  if (oversized !== null) {
4297
4349
  log.elapsedMs = job.elapsedMs;
4350
+ applyOptionalStreamingTiming(log, job);
4298
4351
  log.responseStatus = job.responseStatus;
4299
4352
  log.responseText = oversizedRawStreamText(oversized);
4300
4353
  return {
@@ -4318,6 +4371,7 @@ function finalizeStreaming(job, log) {
4318
4371
  log.model ?? void 0,
4319
4372
  job.collectStreamingChunks
4320
4373
  );
4374
+ applyOptionalStreamingTiming(log, job);
4321
4375
  persistStreamingChunks(log);
4322
4376
  return { log, upstreamUrl: job.upstreamUrl, error: null, cleanupRawStreamPath };
4323
4377
  } catch (err) {
@@ -4347,6 +4401,7 @@ function finalizeStreamAbort(job, log) {
4347
4401
  let cleanupRawStreamPath = null;
4348
4402
  try {
4349
4403
  log.elapsedMs = job.elapsedMs;
4404
+ applyOptionalStreamingTiming(log, job);
4350
4405
  if (job.hasChunks && formatHandler !== null) {
4351
4406
  const oversized = getOversizedRawStream(job.rawStream);
4352
4407
  if (oversized !== null) {
@@ -4367,6 +4422,7 @@ function finalizeStreamAbort(job, log) {
4367
4422
  log.model ?? void 0,
4368
4423
  job.collectStreamingChunks
4369
4424
  );
4425
+ applyOptionalStreamingTiming(log, job);
4370
4426
  persistStreamingChunks(log);
4371
4427
  } else {
4372
4428
  log.responseText = "Client aborted";
@@ -4713,10 +4769,12 @@ function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log,
4713
4769
  const capture = new RawStreamCapture();
4714
4770
  const decoder = new TextDecoder();
4715
4771
  let finalizationScheduled = false;
4772
+ let firstChunkMs = null;
4716
4773
  const scheduleStreamingFinalization = (type) => {
4717
4774
  if (finalizationScheduled) return;
4718
4775
  finalizationScheduled = true;
4719
- const elapsedMs = Date.now() - startTime;
4776
+ const totalStreamMs = Date.now() - startTime;
4777
+ const elapsedMs = totalStreamMs;
4720
4778
  const rawStream = capture.snapshot();
4721
4779
  switch (type) {
4722
4780
  case "streaming":
@@ -4727,7 +4785,9 @@ function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log,
4727
4785
  elapsedMs,
4728
4786
  responseStatus: upstreamRes.status,
4729
4787
  rawStream,
4730
- collectStreamingChunks
4788
+ collectStreamingChunks,
4789
+ firstChunkMs,
4790
+ totalStreamMs
4731
4791
  });
4732
4792
  return;
4733
4793
  case "stream-abort":
@@ -4738,13 +4798,16 @@ function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log,
4738
4798
  elapsedMs,
4739
4799
  rawStream,
4740
4800
  hasChunks: capture.hasChunks,
4741
- collectStreamingChunks
4801
+ collectStreamingChunks,
4802
+ firstChunkMs,
4803
+ totalStreamMs
4742
4804
  });
4743
4805
  return;
4744
4806
  }
4745
4807
  };
4746
4808
  const transform = new TransformStream({
4747
4809
  async transform(chunk, controller) {
4810
+ if (firstChunkMs === null) firstChunkMs = Date.now() - startTime;
4748
4811
  controller.enqueue(chunk);
4749
4812
  await capture.append(decoder.decode(chunk, { stream: true }));
4750
4813
  },
@@ -4872,7 +4935,7 @@ async function handleProxy(req) {
4872
4935
  }
4873
4936
  return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log, captureFullDetails);
4874
4937
  }
4875
- const Route$s = createFileRoute("/proxy/$")({
4938
+ const Route$w = createFileRoute("/proxy/$")({
4876
4939
  server: {
4877
4940
  handlers: {
4878
4941
  GET: ({ request }) => handleProxy(request),
@@ -4893,7 +4956,7 @@ function parsePositiveInt$2(value) {
4893
4956
  function parseBooleanFlag(value) {
4894
4957
  return value === "1" || value === "true";
4895
4958
  }
4896
- const Route$r = createFileRoute("/api/sessions")({
4959
+ const Route$v = createFileRoute("/api/sessions")({
4897
4960
  server: {
4898
4961
  handlers: {
4899
4962
  GET: async ({ request }) => {
@@ -4942,6 +5005,9 @@ const SessionLogSummarySchema = object({
4942
5005
  hasError: boolean(),
4943
5006
  error: string().nullable(),
4944
5007
  latencyMs: number().nullable(),
5008
+ firstChunkMs: number().nullable().optional(),
5009
+ totalStreamMs: number().nullable().optional(),
5010
+ tokensPerSecond: number().nullable().optional(),
4945
5011
  tokens: object({
4946
5012
  input: number().nullable(),
4947
5013
  output: number().nullable(),
@@ -5113,10 +5179,10 @@ const RunsFileSchema = object({
5113
5179
  function runsFilePath() {
5114
5180
  return join(getDataDir(), "runs.json");
5115
5181
  }
5116
- function nowIso() {
5182
+ function nowIso$1() {
5117
5183
  return (/* @__PURE__ */ new Date()).toISOString();
5118
5184
  }
5119
- function stableId(input) {
5185
+ function stableId$1(input) {
5120
5186
  return input.trim().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
5121
5187
  }
5122
5188
  function createRunId() {
@@ -5124,7 +5190,7 @@ function createRunId() {
5124
5190
  }
5125
5191
  function normalizeRunId(input) {
5126
5192
  if (input === void 0) return createRunId();
5127
- const normalized = stableId(input);
5193
+ const normalized = stableId$1(input);
5128
5194
  return normalized.length > 0 ? normalized : createRunId();
5129
5195
  }
5130
5196
  function normalizeSessionId(input, runId) {
@@ -5159,7 +5225,7 @@ function createRun(input) {
5159
5225
  const runId = normalizeRunId(parsed?.runId);
5160
5226
  const existing = getRun(runId);
5161
5227
  if (existing !== null) return existing;
5162
- const timestamp = nowIso();
5228
+ const timestamp = nowIso$1();
5163
5229
  const sessionId = normalizeSessionId(parsed?.sessionId, runId);
5164
5230
  const run = {
5165
5231
  id: runId,
@@ -5198,7 +5264,7 @@ function updateRun(runId, input) {
5198
5264
  const parsed = UpdateInspectorRunInputSchema.parse(input);
5199
5265
  const runs = readRuns();
5200
5266
  let updatedRun = null;
5201
- const updatedAt = nowIso();
5267
+ const updatedAt = nowIso$1();
5202
5268
  const updatedRuns = runs.map((run) => {
5203
5269
  if (run.id !== runId) return run;
5204
5270
  updatedRun = {
@@ -5285,7 +5351,7 @@ function listRecentFailures(limit) {
5285
5351
  limit: clampedLimit
5286
5352
  };
5287
5353
  }
5288
- async function readJsonBody$2(request) {
5354
+ async function readJsonBody$6(request) {
5289
5355
  try {
5290
5356
  const raw = await request.text();
5291
5357
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
@@ -5300,7 +5366,7 @@ function parsePositiveInt$1(value, fallback) {
5300
5366
  if (!Number.isInteger(parsed) || parsed < 1) return fallback;
5301
5367
  return parsed;
5302
5368
  }
5303
- const Route$q = createFileRoute("/api/runs")({
5369
+ const Route$u = createFileRoute("/api/runs")({
5304
5370
  server: {
5305
5371
  handlers: {
5306
5372
  GET: ({ request }) => {
@@ -5315,7 +5381,7 @@ const Route$q = createFileRoute("/api/runs")({
5315
5381
  return Response.json({ runs: listRuns() });
5316
5382
  },
5317
5383
  POST: async ({ request }) => {
5318
- const body = await readJsonBody$2(request);
5384
+ const body = await readJsonBody$6(request);
5319
5385
  if (!body.ok) {
5320
5386
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
5321
5387
  }
@@ -5345,7 +5411,7 @@ const ProviderInputSchema = object({
5345
5411
  modelMetadata: array(ProviderModelMetadataSchema).optional(),
5346
5412
  source: _enum(["company", "personal"]).optional()
5347
5413
  });
5348
- const Route$p = createFileRoute("/api/providers")({
5414
+ const Route$t = createFileRoute("/api/providers")({
5349
5415
  server: {
5350
5416
  handlers: {
5351
5417
  GET: ({ request }) => {
@@ -5379,13 +5445,117 @@ const Route$p = createFileRoute("/api/providers")({
5379
5445
  }
5380
5446
  }
5381
5447
  });
5382
- const Route$o = createFileRoute("/api/models")({
5448
+ const Route$s = createFileRoute("/api/models")({
5383
5449
  server: {
5384
5450
  handlers: {
5385
5451
  GET: () => Response.json(getModels())
5386
5452
  }
5387
5453
  }
5388
5454
  });
5455
+ const InspectorGroupStatusSchema = _enum([
5456
+ "created",
5457
+ "running",
5458
+ "completed",
5459
+ "failed",
5460
+ "cancelled"
5461
+ ]);
5462
+ const InspectorGroupKindSchema = _enum(["evaluation", "batch", "manual"]);
5463
+ const InspectorGroupEvidenceSchema = object({
5464
+ jsonPath: string(),
5465
+ markdownPath: string(),
5466
+ htmlPath: string(),
5467
+ exportedAt: string()
5468
+ });
5469
+ const InspectorGroupMemberSchema = object({
5470
+ id: string(),
5471
+ groupId: string(),
5472
+ sessionId: string(),
5473
+ runId: string().nullable(),
5474
+ label: string().nullable(),
5475
+ agent: string().nullable(),
5476
+ provider: string().nullable(),
5477
+ model: string().nullable(),
5478
+ status: InspectorRunStatusSchema.nullable(),
5479
+ metadata: record(string(), JsonValueSchema),
5480
+ addedAt: string(),
5481
+ updatedAt: string()
5482
+ });
5483
+ const InspectorGroupSchema = object({
5484
+ id: string(),
5485
+ title: string(),
5486
+ kind: InspectorGroupKindSchema,
5487
+ task: string().nullable(),
5488
+ project: string().nullable(),
5489
+ status: InspectorGroupStatusSchema,
5490
+ tags: array(string()),
5491
+ metadata: record(string(), JsonValueSchema),
5492
+ evidence: InspectorGroupEvidenceSchema.nullable(),
5493
+ members: array(InspectorGroupMemberSchema),
5494
+ createdAt: string(),
5495
+ updatedAt: string()
5496
+ });
5497
+ const CreateInspectorGroupInputSchema = object({
5498
+ groupId: string().min(1).optional(),
5499
+ title: string().min(1).optional(),
5500
+ kind: InspectorGroupKindSchema.optional(),
5501
+ task: string().nullable().optional(),
5502
+ project: string().nullable().optional(),
5503
+ status: InspectorGroupStatusSchema.optional(),
5504
+ tags: array(string()).optional(),
5505
+ metadata: record(string(), JsonValueSchema).optional()
5506
+ }).optional();
5507
+ const UpdateInspectorGroupInputSchema = object({
5508
+ title: string().min(1).optional(),
5509
+ kind: InspectorGroupKindSchema.optional(),
5510
+ task: string().nullable().optional(),
5511
+ project: string().nullable().optional(),
5512
+ status: InspectorGroupStatusSchema.optional(),
5513
+ tags: array(string()).optional(),
5514
+ metadata: record(string(), JsonValueSchema).optional()
5515
+ });
5516
+ const AddInspectorGroupSessionInputSchema = object({
5517
+ memberId: string().min(1).optional(),
5518
+ sessionId: string().min(1),
5519
+ runId: string().min(1).nullable().optional(),
5520
+ label: string().min(1).nullable().optional(),
5521
+ agent: string().min(1).nullable().optional(),
5522
+ provider: string().min(1).nullable().optional(),
5523
+ model: string().min(1).nullable().optional(),
5524
+ status: InspectorRunStatusSchema.nullable().optional(),
5525
+ metadata: record(string(), JsonValueSchema).optional()
5526
+ });
5527
+ const GroupEvidenceExportOptionsSchema = object({
5528
+ includeHistory: boolean().optional(),
5529
+ latestLogLimit: number().int().positive().max(50).optional()
5530
+ }).optional();
5531
+ const InspectorGroupExportMemberSchema = object({
5532
+ member: InspectorGroupMemberSchema,
5533
+ run: InspectorRunSchema.nullable(),
5534
+ session: SessionInfoSchema.nullable()
5535
+ });
5536
+ const InspectorGroupEvidenceSummarySchema = object({
5537
+ memberCount: number().int().nonnegative(),
5538
+ sessionsWithData: number().int().nonnegative(),
5539
+ missingSessions: number().int().nonnegative(),
5540
+ requestCount: number().int().nonnegative(),
5541
+ errorCount: number().int().nonnegative(),
5542
+ tokenUsage: SessionTokenUsageSchema,
5543
+ models: array(string()),
5544
+ providers: array(string()),
5545
+ completedMembers: number().int().nonnegative(),
5546
+ failedMembers: number().int().nonnegative(),
5547
+ runningMembers: number().int().nonnegative()
5548
+ });
5549
+ const GroupEvidenceExportResultSchema = object({
5550
+ group: InspectorGroupSchema,
5551
+ evidence: InspectorGroupEvidenceSchema,
5552
+ members: array(InspectorGroupExportMemberSchema),
5553
+ summary: InspectorGroupEvidenceSummarySchema,
5554
+ jenkinsReport: JenkinsReportSchema
5555
+ });
5556
+ const InspectorGroupsListResponseSchema = object({
5557
+ groups: array(InspectorGroupSchema)
5558
+ });
5389
5559
  let overridePort = null;
5390
5560
  function setCurrentPort(port) {
5391
5561
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
@@ -5616,6 +5786,9 @@ function buildLogSummary(log) {
5616
5786
  isStreaming: log.streaming,
5617
5787
  hasError,
5618
5788
  latencyMs: log.elapsedMs,
5789
+ firstChunkMs: log.firstChunkMs ?? null,
5790
+ totalStreamMs: log.totalStreamMs ?? null,
5791
+ tokensPerSecond: log.tokensPerSecond ?? null,
5619
5792
  tokens: {
5620
5793
  input: log.inputTokens,
5621
5794
  output: log.outputTokens,
@@ -5782,6 +5955,85 @@ async function exportEvidenceImpl(callApi2, args) {
5782
5955
  }
5783
5956
  return textJson(parsed.data);
5784
5957
  }
5958
+ async function listGroupsImpl(callApi2) {
5959
+ const res = await callApi2("/api/groups");
5960
+ if (!res.ok) return toolError(`GET /api/groups returned ${res.status}`);
5961
+ const rawBody = await res.json();
5962
+ const parsed = InspectorGroupsListResponseSchema.safeParse(rawBody);
5963
+ if (!parsed.success) return toolError("GET /api/groups returned an unparseable group list");
5964
+ return textJson(parsed.data);
5965
+ }
5966
+ async function createGroupImpl(callApi2, args) {
5967
+ const res = await callApi2("/api/groups", {
5968
+ method: "POST",
5969
+ headers: { "content-type": "application/json" },
5970
+ body: JSON.stringify(args ?? {})
5971
+ });
5972
+ if (!res.ok) return toolError(`POST /api/groups returned ${res.status}`);
5973
+ const rawBody = await res.json();
5974
+ const parsed = InspectorGroupSchema.safeParse(rawBody);
5975
+ if (!parsed.success) return toolError("POST /api/groups returned an unparseable group");
5976
+ return textJson(parsed.data);
5977
+ }
5978
+ async function getGroupImpl(callApi2, args) {
5979
+ const path2 = `/api/groups/${encodeURIComponent(args.groupId)}`;
5980
+ const res = await callApi2(path2);
5981
+ if (!res.ok) return toolError(`GET ${path2} returned ${res.status}`);
5982
+ const rawBody = await res.json();
5983
+ const parsed = InspectorGroupSchema.safeParse(rawBody);
5984
+ if (!parsed.success) return toolError("GET /api/groups/{groupId} returned an unparseable group");
5985
+ return textJson(parsed.data);
5986
+ }
5987
+ async function updateGroupImpl(callApi2, args) {
5988
+ const { groupId, ...patch } = args;
5989
+ const path2 = `/api/groups/${encodeURIComponent(groupId)}`;
5990
+ const res = await callApi2(path2, {
5991
+ method: "PATCH",
5992
+ headers: { "content-type": "application/json" },
5993
+ body: JSON.stringify(patch)
5994
+ });
5995
+ if (!res.ok) return toolError(`PATCH ${path2} returned ${res.status}`);
5996
+ const rawBody = await res.json();
5997
+ const parsed = InspectorGroupSchema.safeParse(rawBody);
5998
+ if (!parsed.success) {
5999
+ return toolError("PATCH /api/groups/{groupId} returned an unparseable group");
6000
+ }
6001
+ return textJson(parsed.data);
6002
+ }
6003
+ async function addGroupSessionImpl(callApi2, args) {
6004
+ const { groupId, ...session } = args;
6005
+ const path2 = `/api/groups/${encodeURIComponent(groupId)}/sessions`;
6006
+ const res = await callApi2(path2, {
6007
+ method: "POST",
6008
+ headers: { "content-type": "application/json" },
6009
+ body: JSON.stringify(session)
6010
+ });
6011
+ if (!res.ok) return toolError(`POST ${path2} returned ${res.status}`);
6012
+ const rawBody = await res.json();
6013
+ const parsed = InspectorGroupSchema.safeParse(rawBody);
6014
+ if (!parsed.success) {
6015
+ return toolError("POST /api/groups/{groupId}/sessions returned an unparseable group");
6016
+ }
6017
+ return textJson(parsed.data);
6018
+ }
6019
+ async function exportGroupEvidenceImpl(callApi2, args) {
6020
+ const path2 = `/api/groups/${encodeURIComponent(args.groupId)}/evidence`;
6021
+ const res = await callApi2(path2, {
6022
+ method: "POST",
6023
+ headers: { "content-type": "application/json" },
6024
+ body: JSON.stringify({
6025
+ includeHistory: args.includeHistory,
6026
+ latestLogLimit: args.latestLogLimit
6027
+ })
6028
+ });
6029
+ if (!res.ok) return toolError(`POST ${path2} returned ${res.status}`);
6030
+ const rawBody = await res.json();
6031
+ const parsed = GroupEvidenceExportResultSchema.safeParse(rawBody);
6032
+ if (!parsed.success) {
6033
+ return toolError("POST /api/groups/{groupId}/evidence returned an unparseable evidence result");
6034
+ }
6035
+ return textJson(parsed.data);
6036
+ }
5785
6037
  async function listModelsImpl(callApi2) {
5786
6038
  const res = await callApi2("/api/models");
5787
6039
  if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
@@ -5973,6 +6225,9 @@ const LogResourceListResponseSchema = object({
5973
6225
  const EvidenceReadResponseSchema = object({
5974
6226
  markdown: string()
5975
6227
  });
6228
+ const GroupEvidenceReadResponseSchema = object({
6229
+ markdown: string()
6230
+ });
5976
6231
  function textResource(uri, mimeType, text) {
5977
6232
  return {
5978
6233
  contents: [{ uri: uri.href, mimeType, text }]
@@ -6055,6 +6310,61 @@ async function listRunResources() {
6055
6310
  return listResult([]);
6056
6311
  }
6057
6312
  }
6313
+ async function listGroupResources() {
6314
+ try {
6315
+ const response = await callApi("/api/groups");
6316
+ if (!response.ok) return listResult([]);
6317
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
6318
+ if (!parsed.success) return listResult([]);
6319
+ return listResult(
6320
+ parsed.data.groups.map((group) => ({
6321
+ uri: `inspector://groups/${encodeURIComponent(group.id)}`,
6322
+ name: `group:${group.id}`,
6323
+ title: group.title,
6324
+ mimeType: "application/json"
6325
+ }))
6326
+ );
6327
+ } catch {
6328
+ return listResult([]);
6329
+ }
6330
+ }
6331
+ async function listGroupSessionResources() {
6332
+ try {
6333
+ const response = await callApi("/api/groups");
6334
+ if (!response.ok) return listResult([]);
6335
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
6336
+ if (!parsed.success) return listResult([]);
6337
+ return listResult(
6338
+ parsed.data.groups.map((group) => ({
6339
+ uri: `inspector://groups/${encodeURIComponent(group.id)}/sessions`,
6340
+ name: `group-sessions:${group.id}`,
6341
+ title: `${group.title} sessions`,
6342
+ mimeType: "application/json"
6343
+ }))
6344
+ );
6345
+ } catch {
6346
+ return listResult([]);
6347
+ }
6348
+ }
6349
+ async function listGroupEvidenceResources() {
6350
+ try {
6351
+ const response = await callApi("/api/groups");
6352
+ if (!response.ok) return listResult([]);
6353
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
6354
+ if (!parsed.success) return listResult([]);
6355
+ return listResult(
6356
+ parsed.data.groups.filter((group) => group.evidence !== null).map((group) => ({
6357
+ uri: `inspector://groups/${encodeURIComponent(group.id)}/evidence`,
6358
+ name: `group-evidence:${group.id}`,
6359
+ title: `${group.title} evidence pack`,
6360
+ mimeType: "text/markdown",
6361
+ description: group.evidence?.markdownPath
6362
+ }))
6363
+ );
6364
+ } catch {
6365
+ return listResult([]);
6366
+ }
6367
+ }
6058
6368
  async function listEvidenceResources() {
6059
6369
  try {
6060
6370
  const response = await callApi("/api/runs");
@@ -6227,6 +6537,77 @@ function registerTools(server) {
6227
6537
  },
6228
6538
  (args) => safeCall(() => getRecentFailuresImpl(callApi, args))
6229
6539
  );
6540
+ server.registerTool(
6541
+ "inspector_list_groups",
6542
+ {
6543
+ title: "List Inspector groups",
6544
+ description: "Lists declared Inspector groups. Groups aggregate multiple sessions/runs, commonly one evaluation matrix spanning several providers or models.",
6545
+ inputSchema: object({})
6546
+ },
6547
+ () => safeCall(() => listGroupsImpl(callApi))
6548
+ );
6549
+ server.registerTool(
6550
+ "inspector_create_group",
6551
+ {
6552
+ title: "Create or declare an Inspector group",
6553
+ description: "Creates an evaluation/batch group that external systems can use to aggregate multiple OpenCode/Codex sessions across models such as MiniMax, DeepSeek, or GLM.",
6554
+ inputSchema: object({
6555
+ groupId: string().min(1).optional().describe("Stable group id. Generated when omitted."),
6556
+ title: string().min(1).optional().describe("Human-readable group title."),
6557
+ kind: _enum(["evaluation", "batch", "manual"]).optional().describe("Group kind. Defaults to evaluation."),
6558
+ task: string().nullable().optional().describe("Task, benchmark, or evaluation goal."),
6559
+ project: string().nullable().optional().describe("Project or repository under test."),
6560
+ status: InspectorGroupStatusSchema.optional().describe("Initial group status."),
6561
+ tags: array(string()).optional().describe("Free-form group labels."),
6562
+ metadata: record(string(), JsonValueSchema).optional().describe("JSON metadata.")
6563
+ })
6564
+ },
6565
+ (args) => safeCall(() => createGroupImpl(callApi, args))
6566
+ );
6567
+ server.registerTool(
6568
+ "inspector_get_group",
6569
+ {
6570
+ title: "Get an Inspector group",
6571
+ description: "Returns one declared Inspector group with its attached session/run members and exported evidence paths when available.",
6572
+ inputSchema: object({
6573
+ groupId: string().min(1).describe("The Inspector group id.")
6574
+ })
6575
+ },
6576
+ (args) => safeCall(() => getGroupImpl(callApi, args))
6577
+ );
6578
+ server.registerTool(
6579
+ "inspector_update_group",
6580
+ {
6581
+ title: "Update an Inspector group",
6582
+ description: "PATCH-style update for a declared group. Use this to mark a whole evaluation running, failed, completed, or to merge CI metadata.",
6583
+ inputSchema: UpdateInspectorGroupInputSchema.extend({
6584
+ groupId: string().min(1).describe("The Inspector group id.")
6585
+ })
6586
+ },
6587
+ (args) => safeCall(() => updateGroupImpl(callApi, args))
6588
+ );
6589
+ server.registerTool(
6590
+ "inspector_add_group_session",
6591
+ {
6592
+ title: "Attach a session to an Inspector group",
6593
+ description: "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.",
6594
+ inputSchema: AddInspectorGroupSessionInputSchema.extend({
6595
+ groupId: string().min(1).describe("The Inspector group id.")
6596
+ })
6597
+ },
6598
+ (args) => safeCall(() => addGroupSessionImpl(callApi, args))
6599
+ );
6600
+ server.registerTool(
6601
+ "inspector_export_group_evidence",
6602
+ {
6603
+ title: "Export an Inspector group evidence pack",
6604
+ description: "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.",
6605
+ inputSchema: GroupEvidenceExportOptionsSchema.unwrap().extend({
6606
+ groupId: string().min(1).describe("The Inspector group id.")
6607
+ })
6608
+ },
6609
+ (args) => safeCall(() => exportGroupEvidenceImpl(callApi, args))
6610
+ );
6230
6611
  server.registerTool(
6231
6612
  "inspector_list_models",
6232
6613
  {
@@ -6434,6 +6815,46 @@ function registerResources(server) {
6434
6815
  },
6435
6816
  (uri) => readApiJsonResource(uri, "/api/runs")
6436
6817
  );
6818
+ server.registerResource(
6819
+ "inspector_groups",
6820
+ "inspector://groups",
6821
+ {
6822
+ title: "Inspector groups",
6823
+ description: "Declared Inspector groups aggregating multiple sessions or runs.",
6824
+ mimeType: "application/json"
6825
+ },
6826
+ (uri) => readApiJsonResource(uri, "/api/groups")
6827
+ );
6828
+ server.registerResource(
6829
+ "inspector_group",
6830
+ new ResourceTemplate("inspector://groups/{groupId}", { list: listGroupResources }),
6831
+ {
6832
+ title: "Inspector group",
6833
+ description: "One declared Inspector group with attached session/run members.",
6834
+ mimeType: "application/json"
6835
+ },
6836
+ (uri, variables) => {
6837
+ const groupId = variableString(variables, "groupId");
6838
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
6839
+ return readApiJsonResource(uri, `/api/groups/${encodeURIComponent(groupId)}`);
6840
+ }
6841
+ );
6842
+ server.registerResource(
6843
+ "inspector_group_sessions",
6844
+ new ResourceTemplate("inspector://groups/{groupId}/sessions", {
6845
+ list: listGroupSessionResources
6846
+ }),
6847
+ {
6848
+ title: "Inspector group sessions",
6849
+ description: "Attached session/run members for one Inspector group.",
6850
+ mimeType: "application/json"
6851
+ },
6852
+ (uri, variables) => {
6853
+ const groupId = variableString(variables, "groupId");
6854
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
6855
+ return readApiJsonResource(uri, `/api/groups/${encodeURIComponent(groupId)}/sessions`);
6856
+ }
6857
+ );
6437
6858
  server.registerResource(
6438
6859
  "inspector_recent_failures",
6439
6860
  "inspector://failures/recent",
@@ -6486,6 +6907,36 @@ function registerResources(server) {
6486
6907
  }
6487
6908
  }
6488
6909
  );
6910
+ server.registerResource(
6911
+ "inspector_group_evidence",
6912
+ new ResourceTemplate("inspector://groups/{groupId}/evidence", {
6913
+ list: listGroupEvidenceResources
6914
+ }),
6915
+ {
6916
+ title: "Inspector group evidence pack",
6917
+ description: "Markdown evidence pack for an Inspector group. Generate it first with inspector_export_group_evidence.",
6918
+ mimeType: "text/markdown"
6919
+ },
6920
+ async (uri, variables) => {
6921
+ const groupId = variableString(variables, "groupId");
6922
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
6923
+ const path2 = `/api/groups/${encodeURIComponent(groupId)}/evidence`;
6924
+ try {
6925
+ const response = await callApi(path2);
6926
+ if (!response.ok) {
6927
+ return textResource(uri, "text/plain", `GET ${path2} returned ${response.status}`);
6928
+ }
6929
+ const parsed = GroupEvidenceReadResponseSchema.safeParse(await response.json());
6930
+ if (!parsed.success) {
6931
+ return textResource(uri, "text/plain", "Evidence endpoint returned an unparseable body");
6932
+ }
6933
+ return textResource(uri, "text/markdown", parsed.data.markdown);
6934
+ } catch (err) {
6935
+ const message = err instanceof Error ? err.message : String(err);
6936
+ return textResource(uri, "text/plain", `Resource read failed: ${message}`);
6937
+ }
6938
+ }
6939
+ );
6489
6940
  }
6490
6941
  function registerPrompts(server) {
6491
6942
  server.registerPrompt(
@@ -6558,6 +7009,31 @@ The report should include:
6558
7009
  3. Models/providers used, token totals, request counts, and error counts
6559
7010
  4. Failure analysis or success evidence
6560
7011
  5. Reproducible next action for a developer`)
7012
+ );
7013
+ server.registerPrompt(
7014
+ "inspector_generate_group_report",
7015
+ {
7016
+ title: "Generate group report",
7017
+ description: "Generate a model/provider evaluation report from an Inspector group and group evidence pack.",
7018
+ argsSchema: object({
7019
+ groupId: string().min(1).describe("The Inspector group id.")
7020
+ })
7021
+ },
7022
+ ({ groupId }) => userPrompt(`Generate a model/provider evaluation report for this Inspector group.
7023
+
7024
+ Evidence to read:
7025
+ - Group resource: inspector://groups/${encodeURIComponent(groupId)}
7026
+ - Group session members: inspector://groups/${encodeURIComponent(groupId)}/sessions
7027
+ - Group evidence pack: inspector://groups/${encodeURIComponent(groupId)}/evidence
7028
+
7029
+ If the evidence pack resource says it has not been exported, call inspector_export_group_evidence for this group first.
7030
+
7031
+ The report should include:
7032
+ 1. Evaluation status summary
7033
+ 2. Provider/model matrix with session links
7034
+ 3. Request counts, token totals, streaming timing, and error counts
7035
+ 4. Winner/loser observations when the evidence supports them
7036
+ 5. Concrete next actions for failed or missing sessions`)
6561
7037
  );
6562
7038
  server.registerPrompt(
6563
7039
  "inspector_extract_repro_steps",
@@ -6582,7 +7058,7 @@ Return:
6582
7058
  5. Minimal verification command or UI check`)
6583
7059
  );
6584
7060
  }
6585
- const Route$n = createFileRoute("/api/mcp")({
7061
+ const Route$r = createFileRoute("/api/mcp")({
6586
7062
  server: {
6587
7063
  handlers: {
6588
7064
  // The SDK may issue either POST, GET, or DELETE. TanStack Start only
@@ -6692,7 +7168,7 @@ function parsePositiveInt(value, fallback) {
6692
7168
  if (!Number.isInteger(parsed) || parsed < 1) return fallback;
6693
7169
  return parsed;
6694
7170
  }
6695
- const Route$m = createFileRoute("/api/logs")({
7171
+ const Route$q = createFileRoute("/api/logs")({
6696
7172
  server: {
6697
7173
  handlers: {
6698
7174
  GET: async ({ request }) => {
@@ -6768,7 +7244,7 @@ const Route$m = createFileRoute("/api/logs")({
6768
7244
  }
6769
7245
  });
6770
7246
  logger.debug("Health endpoint loaded");
6771
- const Route$l = createFileRoute("/api/health")({
7247
+ const Route$p = createFileRoute("/api/health")({
6772
7248
  server: {
6773
7249
  handlers: {
6774
7250
  GET: () => {
@@ -6777,22 +7253,235 @@ const Route$l = createFileRoute("/api/health")({
6777
7253
  }
6778
7254
  }
6779
7255
  });
6780
- const RuntimeConfigPatchSchema = object({
6781
- stripClaudeCodeBillingHeader: boolean().optional(),
6782
- hasSeenOnboarding: boolean().optional(),
6783
- slowResponseThresholdSeconds: number().int().min(0).max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS).optional(),
6784
- providerTestTimeoutSeconds: number().int().min(1).max(MAX_PROVIDER_TEST_TIMEOUT_SECONDS).optional(),
6785
- timeDisplayFormat: TimeDisplayFormatSchema.optional()
6786
- }).strict().refine((v) => Object.keys(v).length > 0, {
6787
- message: "At least one field must be provided"
7256
+ const GroupsFileSchema = object({
7257
+ groups: array(InspectorGroupSchema)
6788
7258
  });
6789
- const Route$k = createFileRoute("/api/config")({
6790
- server: {
6791
- handlers: {
6792
- GET: () => {
6793
- return Response.json(getConfig());
6794
- },
6795
- PATCH: async ({ request }) => {
7259
+ function groupsFilePath() {
7260
+ return join(getDataDir(), "groups.json");
7261
+ }
7262
+ function nowIso() {
7263
+ return (/* @__PURE__ */ new Date()).toISOString();
7264
+ }
7265
+ function stableId(input) {
7266
+ return input.trim().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
7267
+ }
7268
+ function createGroupId() {
7269
+ return `grp_${Date.now().toString(36)}_${randomUUID$1().slice(0, 8)}`;
7270
+ }
7271
+ function createMemberId() {
7272
+ return `mem_${Date.now().toString(36)}_${randomUUID$1().slice(0, 8)}`;
7273
+ }
7274
+ function normalizeGroupId(input) {
7275
+ if (input === void 0) return createGroupId();
7276
+ const normalized = stableId(input);
7277
+ return normalized.length > 0 ? normalized : createGroupId();
7278
+ }
7279
+ function normalizeMemberId(input, sessionId) {
7280
+ if (input === void 0) {
7281
+ const normalizedSession = stableId(sessionId);
7282
+ return normalizedSession.length > 0 ? `mem_${normalizedSession}` : createMemberId();
7283
+ }
7284
+ const normalized = stableId(input);
7285
+ return normalized.length > 0 ? normalized : createMemberId();
7286
+ }
7287
+ function nullableText(value) {
7288
+ if (value === void 0 || value === null) return null;
7289
+ const trimmed = value.trim();
7290
+ return trimmed.length > 0 ? trimmed : null;
7291
+ }
7292
+ function readGroups() {
7293
+ const filePath = groupsFilePath();
7294
+ if (!existsSync(filePath)) return [];
7295
+ try {
7296
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
7297
+ const result = GroupsFileSchema.safeParse(parsed);
7298
+ return result.success ? result.data.groups : [];
7299
+ } catch {
7300
+ return [];
7301
+ }
7302
+ }
7303
+ function writeGroups(groups) {
7304
+ const filePath = groupsFilePath();
7305
+ mkdirSync(dirname(filePath), { recursive: true });
7306
+ writeFileSync(filePath, `${JSON.stringify({ groups }, null, 2)}
7307
+ `, "utf8");
7308
+ }
7309
+ function sortedGroups(groups) {
7310
+ return [...groups].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
7311
+ }
7312
+ function listGroups() {
7313
+ return sortedGroups(readGroups());
7314
+ }
7315
+ function getGroup(groupId) {
7316
+ return readGroups().find((group) => group.id === groupId) ?? null;
7317
+ }
7318
+ function createGroup(input) {
7319
+ const parsed = CreateInspectorGroupInputSchema.parse(input);
7320
+ const groupId = normalizeGroupId(parsed?.groupId);
7321
+ const existing = getGroup(groupId);
7322
+ if (existing !== null) return existing;
7323
+ const timestamp = nowIso();
7324
+ const group = {
7325
+ id: groupId,
7326
+ title: parsed?.title ?? `Inspector Group ${groupId}`,
7327
+ kind: parsed?.kind ?? "evaluation",
7328
+ task: parsed?.task ?? null,
7329
+ project: parsed?.project ?? null,
7330
+ status: parsed?.status ?? "created",
7331
+ tags: parsed?.tags ?? [],
7332
+ metadata: parsed?.metadata ?? {},
7333
+ evidence: null,
7334
+ members: [],
7335
+ createdAt: timestamp,
7336
+ updatedAt: timestamp
7337
+ };
7338
+ writeGroups([...readGroups(), group]);
7339
+ return group;
7340
+ }
7341
+ function updateGroup(groupId, input) {
7342
+ const parsed = UpdateInspectorGroupInputSchema.parse(input);
7343
+ const groups = readGroups();
7344
+ let updatedGroup = null;
7345
+ const updatedAt = nowIso();
7346
+ const updatedGroups = groups.map((group) => {
7347
+ if (group.id !== groupId) return group;
7348
+ updatedGroup = {
7349
+ ...group,
7350
+ title: parsed.title ?? group.title,
7351
+ kind: parsed.kind ?? group.kind,
7352
+ task: parsed.task === void 0 ? group.task : parsed.task,
7353
+ project: parsed.project === void 0 ? group.project : parsed.project,
7354
+ status: parsed.status ?? group.status,
7355
+ tags: parsed.tags ?? group.tags,
7356
+ metadata: parsed.metadata === void 0 ? group.metadata : { ...group.metadata, ...parsed.metadata },
7357
+ updatedAt
7358
+ };
7359
+ return updatedGroup;
7360
+ });
7361
+ if (updatedGroup === null) return null;
7362
+ writeGroups(updatedGroups);
7363
+ return updatedGroup;
7364
+ }
7365
+ function matchesExistingMember(member, memberId, sessionId, runId) {
7366
+ if (member.id === memberId) return true;
7367
+ if (member.sessionId !== sessionId) return false;
7368
+ return member.runId === runId;
7369
+ }
7370
+ function upsertMember(group, input, updatedAt) {
7371
+ const parsed = AddInspectorGroupSessionInputSchema.parse(input);
7372
+ const sessionId = parsed.sessionId.trim();
7373
+ const runId = nullableText(parsed.runId);
7374
+ const memberId = normalizeMemberId(parsed.memberId, sessionId);
7375
+ let matched = false;
7376
+ const members = group.members.map((member) => {
7377
+ if (!matchesExistingMember(member, memberId, sessionId, runId)) return member;
7378
+ matched = true;
7379
+ return {
7380
+ ...member,
7381
+ sessionId,
7382
+ runId,
7383
+ label: parsed.label === void 0 ? member.label : nullableText(parsed.label),
7384
+ agent: parsed.agent === void 0 ? member.agent : nullableText(parsed.agent),
7385
+ provider: parsed.provider === void 0 ? member.provider : nullableText(parsed.provider),
7386
+ model: parsed.model === void 0 ? member.model : nullableText(parsed.model),
7387
+ status: parsed.status === void 0 ? member.status : parsed.status,
7388
+ metadata: parsed.metadata === void 0 ? member.metadata : { ...member.metadata, ...parsed.metadata },
7389
+ updatedAt
7390
+ };
7391
+ });
7392
+ if (!matched) {
7393
+ members.push({
7394
+ id: memberId,
7395
+ groupId: group.id,
7396
+ sessionId,
7397
+ runId,
7398
+ label: nullableText(parsed.label),
7399
+ agent: nullableText(parsed.agent),
7400
+ provider: nullableText(parsed.provider),
7401
+ model: nullableText(parsed.model),
7402
+ status: parsed.status ?? null,
7403
+ metadata: parsed.metadata ?? {},
7404
+ addedAt: updatedAt,
7405
+ updatedAt
7406
+ });
7407
+ }
7408
+ return { ...group, members, updatedAt };
7409
+ }
7410
+ function addGroupSession(groupId, input) {
7411
+ const groups = readGroups();
7412
+ let updatedGroup = null;
7413
+ const updatedAt = nowIso();
7414
+ const updatedGroups = groups.map((group) => {
7415
+ if (group.id !== groupId) return group;
7416
+ updatedGroup = upsertMember(group, input, updatedAt);
7417
+ return updatedGroup;
7418
+ });
7419
+ if (updatedGroup === null) return null;
7420
+ writeGroups(updatedGroups);
7421
+ return updatedGroup;
7422
+ }
7423
+ function updateGroupEvidence(groupId, evidence) {
7424
+ const groups = readGroups();
7425
+ let updatedGroup = null;
7426
+ const updatedGroups = groups.map((group) => {
7427
+ if (group.id !== groupId) return group;
7428
+ updatedGroup = {
7429
+ ...group,
7430
+ evidence,
7431
+ updatedAt: evidence.exportedAt
7432
+ };
7433
+ return updatedGroup;
7434
+ });
7435
+ if (updatedGroup === null) return null;
7436
+ writeGroups(updatedGroups);
7437
+ return updatedGroup;
7438
+ }
7439
+ async function readJsonBody$5(request) {
7440
+ try {
7441
+ const raw = await request.text();
7442
+ if (raw.trim().length === 0) return { ok: true, value: void 0 };
7443
+ return { ok: true, value: JSON.parse(raw) };
7444
+ } catch {
7445
+ return { ok: false };
7446
+ }
7447
+ }
7448
+ const Route$o = createFileRoute("/api/groups")({
7449
+ server: {
7450
+ handlers: {
7451
+ GET: () => Response.json({ groups: listGroups() }),
7452
+ POST: async ({ request }) => {
7453
+ const body = await readJsonBody$5(request);
7454
+ if (!body.ok) {
7455
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
7456
+ }
7457
+ const parsed = CreateInspectorGroupInputSchema.safeParse(body.value);
7458
+ if (!parsed.success) {
7459
+ return Response.json(
7460
+ { error: "Invalid request body", details: parsed.error.format() },
7461
+ { status: 400 }
7462
+ );
7463
+ }
7464
+ return Response.json(createGroup(parsed.data), { status: 201 });
7465
+ }
7466
+ }
7467
+ }
7468
+ });
7469
+ const RuntimeConfigPatchSchema = object({
7470
+ stripClaudeCodeBillingHeader: boolean().optional(),
7471
+ hasSeenOnboarding: boolean().optional(),
7472
+ slowResponseThresholdSeconds: number().int().min(0).max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS).optional(),
7473
+ providerTestTimeoutSeconds: number().int().min(1).max(MAX_PROVIDER_TEST_TIMEOUT_SECONDS).optional(),
7474
+ timeDisplayFormat: TimeDisplayFormatSchema.optional()
7475
+ }).strict().refine((v) => Object.keys(v).length > 0, {
7476
+ message: "At least one field must be provided"
7477
+ });
7478
+ const Route$n = createFileRoute("/api/config")({
7479
+ server: {
7480
+ handlers: {
7481
+ GET: () => {
7482
+ return Response.json(getConfig());
7483
+ },
7484
+ PATCH: async ({ request }) => {
6796
7485
  let body;
6797
7486
  try {
6798
7487
  body = await request.json();
@@ -6815,7 +7504,7 @@ const Route$k = createFileRoute("/api/config")({
6815
7504
  }
6816
7505
  }
6817
7506
  });
6818
- async function readJsonBody$1(request) {
7507
+ async function readJsonBody$4(request) {
6819
7508
  try {
6820
7509
  const raw = await request.text();
6821
7510
  if (raw.trim().length === 0) return { ok: true, value: {} };
@@ -6824,7 +7513,7 @@ async function readJsonBody$1(request) {
6824
7513
  return { ok: false };
6825
7514
  }
6826
7515
  }
6827
- const Route$j = createFileRoute("/api/runs/$runId")({
7516
+ const Route$m = createFileRoute("/api/runs/$runId")({
6828
7517
  server: {
6829
7518
  handlers: {
6830
7519
  GET: ({ params }) => {
@@ -6835,7 +7524,7 @@ const Route$j = createFileRoute("/api/runs/$runId")({
6835
7524
  return Response.json(run);
6836
7525
  },
6837
7526
  PATCH: async ({ params, request }) => {
6838
- const body = await readJsonBody$1(request);
7527
+ const body = await readJsonBody$4(request);
6839
7528
  if (!body.ok) {
6840
7529
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
6841
7530
  }
@@ -7281,7 +7970,7 @@ function scanExternalProviders() {
7281
7970
  }
7282
7971
  return { providers: filteredProviders, warnings };
7283
7972
  }
7284
- const Route$i = createFileRoute("/api/providers/scan")({
7973
+ const Route$l = createFileRoute("/api/providers/scan")({
7285
7974
  server: {
7286
7975
  handlers: {
7287
7976
  GET: () => {
@@ -7302,7 +7991,7 @@ const Route$i = createFileRoute("/api/providers/scan")({
7302
7991
  }
7303
7992
  });
7304
7993
  union([string(), object({ providers: unknown() })]);
7305
- const Route$h = createFileRoute("/api/providers/import")({
7994
+ const Route$k = createFileRoute("/api/providers/import")({
7306
7995
  server: {
7307
7996
  handlers: {
7308
7997
  POST: async ({ request }) => {
@@ -7335,7 +8024,7 @@ const Route$h = createFileRoute("/api/providers/import")({
7335
8024
  }
7336
8025
  }
7337
8026
  });
7338
- const Route$g = createFileRoute("/api/providers/export")({
8027
+ const Route$j = createFileRoute("/api/providers/export")({
7339
8028
  server: {
7340
8029
  handlers: {
7341
8030
  GET: ({ request }) => {
@@ -7368,7 +8057,7 @@ const ProviderUpdateSchema = object({
7368
8057
  modelMetadata: array(ProviderModelMetadataSchema).optional(),
7369
8058
  source: _enum(["company", "personal"]).optional()
7370
8059
  });
7371
- const Route$f = createFileRoute("/api/providers/$providerId")({
8060
+ const Route$i = createFileRoute("/api/providers/$providerId")({
7372
8061
  server: {
7373
8062
  handlers: {
7374
8063
  GET: ({ params, request }) => {
@@ -7403,7 +8092,7 @@ const Route$f = createFileRoute("/api/providers/$providerId")({
7403
8092
  }
7404
8093
  });
7405
8094
  const INITIAL_STREAM_LOG_LIMIT = 100;
7406
- const Route$e = createFileRoute("/api/logs/stream")({
8095
+ const Route$h = createFileRoute("/api/logs/stream")({
7407
8096
  server: {
7408
8097
  handlers: {
7409
8098
  GET: ({ request }) => {
@@ -7476,7 +8165,7 @@ const Route$e = createFileRoute("/api/logs/stream")({
7476
8165
  }
7477
8166
  }
7478
8167
  });
7479
- const Route$d = createFileRoute("/api/logs/$id")({
8168
+ const Route$g = createFileRoute("/api/logs/$id")({
7480
8169
  server: {
7481
8170
  handlers: {
7482
8171
  GET: async ({ params }) => {
@@ -8013,7 +8702,7 @@ const SearchBodySchema = object({
8013
8702
  query: string().min(1),
8014
8703
  project: string().optional()
8015
8704
  });
8016
- const Route$c = createFileRoute("/api/knowledge/search")({
8705
+ const Route$f = createFileRoute("/api/knowledge/search")({
8017
8706
  server: {
8018
8707
  handlers: {
8019
8708
  GET: async ({ request }) => {
@@ -8035,7 +8724,7 @@ const Route$c = createFileRoute("/api/knowledge/search")({
8035
8724
  }
8036
8725
  }
8037
8726
  });
8038
- const Route$b = createFileRoute("/api/knowledge/project-context")({
8727
+ const Route$e = createFileRoute("/api/knowledge/project-context")({
8039
8728
  server: {
8040
8729
  handlers: {
8041
8730
  GET: async ({ request }) => {
@@ -8117,14 +8806,54 @@ function updateCandidateDraft(id, updates) {
8117
8806
  candidates.set(id, updated);
8118
8807
  return updated;
8119
8808
  }
8120
- const Route$a = createFileRoute("/api/knowledge/candidates")({
8809
+ const Route$d = createFileRoute("/api/knowledge/candidates")({
8121
8810
  server: {
8122
8811
  handlers: {
8123
8812
  GET: () => Response.json({ candidates: listCandidates() })
8124
8813
  }
8125
8814
  }
8126
8815
  });
8127
- const Route$9 = createFileRoute("/api/config/paths")({
8816
+ async function readJsonBody$3(request) {
8817
+ try {
8818
+ const raw = await request.text();
8819
+ if (raw.trim().length === 0) return { ok: true, value: {} };
8820
+ return { ok: true, value: JSON.parse(raw) };
8821
+ } catch {
8822
+ return { ok: false };
8823
+ }
8824
+ }
8825
+ const Route$c = createFileRoute("/api/groups/$groupId")({
8826
+ server: {
8827
+ handlers: {
8828
+ GET: ({ params }) => {
8829
+ const group = getGroup(params.groupId);
8830
+ if (group === null) {
8831
+ return Response.json({ error: "Group not found" }, { status: 404 });
8832
+ }
8833
+ return Response.json(group);
8834
+ },
8835
+ PATCH: async ({ params, request }) => {
8836
+ const body = await readJsonBody$3(request);
8837
+ if (!body.ok) {
8838
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
8839
+ }
8840
+ const parsed = UpdateInspectorGroupInputSchema.safeParse(body.value);
8841
+ if (!parsed.success) {
8842
+ return Response.json(
8843
+ { error: "Invalid request body", details: parsed.error.format() },
8844
+ { status: 400 }
8845
+ );
8846
+ }
8847
+ const updated = updateGroup(params.groupId, parsed.data);
8848
+ if (updated === null) {
8849
+ return Response.json({ error: "Group not found" }, { status: 404 });
8850
+ }
8851
+ return Response.json(updated);
8852
+ }
8853
+ }
8854
+ }
8855
+ });
8856
+ const Route$b = createFileRoute("/api/config/paths")({
8128
8857
  server: {
8129
8858
  handlers: {
8130
8859
  GET: () => {
@@ -8391,6 +9120,11 @@ function logDetails(log) {
8391
9120
  `streaming=${log.isStreaming ? "yes" : "no"}`,
8392
9121
  `latencyMs=${formatNullable(log.latencyMs)}`
8393
9122
  ];
9123
+ if (log.isStreaming) {
9124
+ parts.push(`firstChunkMs=${formatNullable(log.firstChunkMs ?? null)}`);
9125
+ parts.push(`totalStreamMs=${formatNullable(log.totalStreamMs ?? null)}`);
9126
+ parts.push(`tokensPerSecond=${formatNullable(log.tokensPerSecond ?? null)}`);
9127
+ }
8394
9128
  if (log.error !== null) {
8395
9129
  parts.push(`error=${log.error}`);
8396
9130
  }
@@ -8502,7 +9236,7 @@ function timelineMarkdown(timeline) {
8502
9236
  })
8503
9237
  ];
8504
9238
  }
8505
- function buildJenkinsReport(input) {
9239
+ function buildJenkinsReport$1(input) {
8506
9240
  const status = reportStatus(input.classification);
8507
9241
  const session = input.session;
8508
9242
  const summary = `[${status}] ${input.classification.summary}`;
@@ -8550,7 +9284,7 @@ function buildJenkinsReport(input) {
8550
9284
  function analyzeEvidence(input) {
8551
9285
  const classification = classifyRunFailure(input.run, input.session);
8552
9286
  const timeline = buildRunTimeline(input.run, input.session, input.exportedAt);
8553
- const jenkinsReport = buildJenkinsReport({
9287
+ const jenkinsReport = buildJenkinsReport$1({
8554
9288
  run: input.run,
8555
9289
  session: input.session,
8556
9290
  timeline,
@@ -8562,22 +9296,22 @@ function analyzeEvidence(input) {
8562
9296
  function evidenceDir(runId) {
8563
9297
  return join(getDataDir(), "evidence", runId);
8564
9298
  }
8565
- function jsonPath(runId) {
9299
+ function jsonPath$1(runId) {
8566
9300
  return join(evidenceDir(runId), "evidence.json");
8567
9301
  }
8568
- function markdownPath(runId) {
9302
+ function markdownPath$1(runId) {
8569
9303
  return join(evidenceDir(runId), "evidence.md");
8570
9304
  }
8571
- function htmlPath(runId) {
9305
+ function htmlPath$1(runId) {
8572
9306
  return join(evidenceDir(runId), "evidence.html");
8573
9307
  }
8574
9308
  function formatList(values) {
8575
9309
  return values.length > 0 ? values.join(", ") : "n/a";
8576
9310
  }
8577
- function escapeHtml(value) {
9311
+ function escapeHtml$1(value) {
8578
9312
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8579
9313
  }
8580
- function buildMarkdown(document) {
9314
+ function buildMarkdown$1(document) {
8581
9315
  const session = document.session;
8582
9316
  const lines = [
8583
9317
  `# ${document.run.title}`,
@@ -8643,12 +9377,13 @@ function buildMarkdown(document) {
8643
9377
  lines.push("No compact logs were available.", "");
8644
9378
  } else {
8645
9379
  lines.push(
8646
- "| ID | Status | Model | Latency | Path | Error |",
8647
- "| --- | --- | --- | --- | --- | --- |"
9380
+ "| ID | Status | Model | Latency | Stream | Path | Error |",
9381
+ "| --- | --- | --- | --- | --- | --- | --- |"
8648
9382
  );
8649
9383
  for (const log of session.latestLogs) {
9384
+ const streamTiming = log.isStreaming ? `first=${log.firstChunkMs ?? "n/a"}ms total=${log.totalStreamMs ?? "n/a"}ms tps=${log.tokensPerSecond ?? "n/a"}` : "n/a";
8650
9385
  lines.push(
8651
- `| ${log.id} | ${log.status ?? "n/a"} | ${log.model ?? "n/a"} | ${log.latencyMs ?? "n/a"} | ${log.path} | ${log.hasError ? "yes" : "no"} |`
9386
+ `| ${log.id} | ${log.status ?? "n/a"} | ${log.model ?? "n/a"} | ${log.latencyMs ?? "n/a"} | ${streamTiming} | ${log.path} | ${log.hasError ? "yes" : "no"} |`
8652
9387
  );
8653
9388
  }
8654
9389
  lines.push("");
@@ -8658,19 +9393,19 @@ function buildMarkdown(document) {
8658
9393
  return `${lines.join("\n")}
8659
9394
  `;
8660
9395
  }
8661
- function buildHtml(markdown, title) {
9396
+ function buildHtml$1(markdown, title) {
8662
9397
  return `<!doctype html>
8663
9398
  <html lang="en">
8664
9399
  <head>
8665
9400
  <meta charset="utf-8">
8666
- <title>${escapeHtml(title)}</title>
9401
+ <title>${escapeHtml$1(title)}</title>
8667
9402
  <style>
8668
9403
  body { font-family: system-ui, sans-serif; max-width: 960px; margin: 40px auto; line-height: 1.5; }
8669
9404
  pre { white-space: pre-wrap; background: #f6f8fa; padding: 16px; border-radius: 8px; }
8670
9405
  </style>
8671
9406
  </head>
8672
9407
  <body>
8673
- <pre>${escapeHtml(markdown)}</pre>
9408
+ <pre>${escapeHtml$1(markdown)}</pre>
8674
9409
  </body>
8675
9410
  </html>
8676
9411
  `;
@@ -8683,9 +9418,9 @@ async function exportRunEvidence(run, options, baseUrl) {
8683
9418
  });
8684
9419
  const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
8685
9420
  const evidence = {
8686
- jsonPath: jsonPath(run.id),
8687
- markdownPath: markdownPath(run.id),
8688
- htmlPath: htmlPath(run.id),
9421
+ jsonPath: jsonPath$1(run.id),
9422
+ markdownPath: markdownPath$1(run.id),
9423
+ htmlPath: htmlPath$1(run.id),
8689
9424
  exportedAt
8690
9425
  };
8691
9426
  const documentRun = {
@@ -8711,7 +9446,7 @@ async function exportRunEvidence(run, options, baseUrl) {
8711
9446
  classification: analysis.classification,
8712
9447
  jenkinsReport: analysis.jenkinsReport
8713
9448
  };
8714
- const markdown = buildMarkdown(document);
9449
+ const markdown = buildMarkdown$1(document);
8715
9450
  mkdirSync(evidenceDir(run.id), { recursive: true });
8716
9451
  writeFileSync(
8717
9452
  evidence.jsonPath,
@@ -8720,7 +9455,7 @@ async function exportRunEvidence(run, options, baseUrl) {
8720
9455
  "utf8"
8721
9456
  );
8722
9457
  writeFileSync(evidence.markdownPath, markdown, "utf8");
8723
- writeFileSync(evidence.htmlPath, buildHtml(markdown, run.title), "utf8");
9458
+ writeFileSync(evidence.htmlPath, buildHtml$1(markdown, run.title), "utf8");
8724
9459
  const updatedRun = updateRunEvidence(run.id, evidence) ?? documentRun;
8725
9460
  return { ...document, run: updatedRun };
8726
9461
  }
@@ -8733,7 +9468,7 @@ function readEvidenceMarkdown(run) {
8733
9468
  return null;
8734
9469
  }
8735
9470
  }
8736
- async function readJsonBody(request) {
9471
+ async function readJsonBody$2(request) {
8737
9472
  try {
8738
9473
  const raw = await request.text();
8739
9474
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
@@ -8742,7 +9477,7 @@ async function readJsonBody(request) {
8742
9477
  return { ok: false };
8743
9478
  }
8744
9479
  }
8745
- const Route$8 = createFileRoute("/api/runs/$runId/evidence")({
9480
+ const Route$a = createFileRoute("/api/runs/$runId/evidence")({
8746
9481
  server: {
8747
9482
  handlers: {
8748
9483
  GET: ({ params }) => {
@@ -8764,7 +9499,7 @@ const Route$8 = createFileRoute("/api/runs/$runId/evidence")({
8764
9499
  if (run === null) {
8765
9500
  return Response.json({ error: "Run not found" }, { status: 404 });
8766
9501
  }
8767
- const body = await readJsonBody(request);
9502
+ const body = await readJsonBody$2(request);
8768
9503
  if (!body.ok) {
8769
9504
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
8770
9505
  }
@@ -8998,7 +9733,7 @@ async function readResponseText(response) {
8998
9733
  return `Failed to read response body: ${String(err)}`;
8999
9734
  }
9000
9735
  }
9001
- function createErrorResult(error, latencyMs, streaming, responseStatus, debug) {
9736
+ function createErrorResult(error, latencyMs, streaming, responseStatus, debug, streamTiming) {
9002
9737
  const { type, details } = classifyError(error, responseStatus);
9003
9738
  const result = {
9004
9739
  success: false,
@@ -9011,6 +9746,11 @@ function createErrorResult(error, latencyMs, streaming, responseStatus, debug) {
9011
9746
  latencyMs,
9012
9747
  streaming
9013
9748
  };
9749
+ if (streamTiming !== void 0) {
9750
+ result.firstChunkMs = streamTiming.firstChunkMs;
9751
+ result.totalStreamMs = streamTiming.totalStreamMs;
9752
+ result.tokensPerSecond = streamTiming.tokensPerSecond;
9753
+ }
9014
9754
  if (debug !== void 0) {
9015
9755
  result.debug = debug;
9016
9756
  if (debug.requestHeaders !== void 0) {
@@ -9232,14 +9972,17 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9232
9972
  const decoder = new TextDecoder();
9233
9973
  const MAX_CHUNKS_SIZE = 10 * 1024 * 1024;
9234
9974
  let totalSize = 0;
9975
+ let firstChunkMs = null;
9235
9976
  try {
9236
9977
  while (true) {
9237
9978
  const { done, value } = await reader.read();
9238
9979
  if (done) break;
9980
+ if (firstChunkMs === null) firstChunkMs = Date.now() - startTime;
9239
9981
  if (totalSize >= MAX_CHUNKS_SIZE) {
9982
+ const totalStreamMs2 = Date.now() - startTime;
9240
9983
  return createErrorResult(
9241
9984
  "Response too large (exceeded 10MB limit)",
9242
- Date.now() - startTime,
9985
+ totalStreamMs2,
9243
9986
  true,
9244
9987
  response.status,
9245
9988
  createTestDebug({
@@ -9248,7 +9991,8 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9248
9991
  requestBody: body,
9249
9992
  response,
9250
9993
  responseBody: chunks.join("")
9251
- })
9994
+ }),
9995
+ { firstChunkMs, totalStreamMs: totalStreamMs2, tokensPerSecond: null }
9252
9996
  );
9253
9997
  }
9254
9998
  const decoded = decoder.decode(value, { stream: true });
@@ -9260,6 +10004,7 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9260
10004
  chunks.push(finalChunk);
9261
10005
  }
9262
10006
  } catch (readErr) {
10007
+ const totalStreamMs2 = Date.now() - startTime;
9263
10008
  const error = readErr instanceof Error && readErr.name === "AbortError" ? readErr : `Stream read error: ${String(readErr)}`;
9264
10009
  return createErrorResult(
9265
10010
  error,
@@ -9272,10 +10017,12 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9272
10017
  requestBody: body,
9273
10018
  response,
9274
10019
  responseBody: chunks.join("")
9275
- })
10020
+ }),
10021
+ { firstChunkMs, totalStreamMs: totalStreamMs2, tokensPerSecond: null }
9276
10022
  );
9277
10023
  }
9278
10024
  const fullResponse = chunks.join("");
10025
+ const totalStreamMs = Date.now() - startTime;
9279
10026
  const safeRequestHeaders = redactHeaders(requestHeaders);
9280
10027
  const mockLog = {
9281
10028
  id: 0,
@@ -9336,6 +10083,11 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9336
10083
  }
9337
10084
  }
9338
10085
  }
10086
+ const tokensPerSecond = computeTokensPerSecond({
10087
+ outputTokens,
10088
+ firstChunkMs,
10089
+ totalStreamMs
10090
+ });
9339
10091
  return {
9340
10092
  success: true,
9341
10093
  model: responseModel ?? model,
@@ -9344,6 +10096,9 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9344
10096
  cacheCreationInputTokens,
9345
10097
  cacheReadInputTokens,
9346
10098
  latencyMs,
10099
+ firstChunkMs,
10100
+ totalStreamMs,
10101
+ tokensPerSecond,
9347
10102
  content: content.length > 0 ? content : void 0,
9348
10103
  rawResponse: reconstructedJson,
9349
10104
  streaming: true,
@@ -9362,7 +10117,8 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9362
10117
  requestBody: body,
9363
10118
  response,
9364
10119
  responseBody: fullResponse
9365
- })
10120
+ }),
10121
+ { firstChunkMs, totalStreamMs, tokensPerSecond: null }
9366
10122
  );
9367
10123
  }
9368
10124
  } catch (err) {
@@ -9377,7 +10133,7 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9377
10133
  clearTimeout(timeoutId);
9378
10134
  }
9379
10135
  }
9380
- const Route$7 = createFileRoute("/api/providers/$providerId/test")({
10136
+ const Route$9 = createFileRoute("/api/providers/$providerId/test")({
9381
10137
  server: {
9382
10138
  handlers: {
9383
10139
  POST: async ({ params }) => {
@@ -9471,7 +10227,7 @@ async function readRegistryJson(response) {
9471
10227
  return null;
9472
10228
  }
9473
10229
  }
9474
- const Route$6 = createFileRoute("/api/providers/$providerId/model-metadata")({
10230
+ const Route$8 = createFileRoute("/api/providers/$providerId/model-metadata")({
9475
10231
  server: {
9476
10232
  handlers: {
9477
10233
  POST: async ({ params, request }) => {
@@ -9561,7 +10317,7 @@ const Route$6 = createFileRoute("/api/providers/$providerId/model-metadata")({
9561
10317
  const ReplayRequestSchema = object({
9562
10318
  modifiedBody: string()
9563
10319
  });
9564
- const Route$5 = createFileRoute("/api/logs/$id/replay")({
10320
+ const Route$7 = createFileRoute("/api/logs/$id/replay")({
9565
10321
  server: {
9566
10322
  handlers: {
9567
10323
  POST: async ({ params, request }) => {
@@ -9633,26 +10389,35 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9633
10389
  error: String(err)
9634
10390
  });
9635
10391
  }
9636
- const elapsedMs = Date.now() - startTime;
9637
10392
  const isStream = upstreamRes.headers.get(HEADER_CONTENT_TYPE)?.includes(CONTENT_TYPE_EVENT_STREAM) ?? false;
9638
10393
  if (isStream) {
9639
10394
  const chunks = [];
9640
10395
  const decoder = new TextDecoder();
9641
10396
  const reader = upstreamRes.body?.getReader();
10397
+ let firstChunkMs = null;
9642
10398
  if (reader) {
9643
10399
  try {
9644
10400
  while (true) {
9645
10401
  const { done, value } = await reader.read();
9646
10402
  if (done) break;
10403
+ if (firstChunkMs === null) firstChunkMs = Date.now() - startTime;
9647
10404
  chunks.push(decoder.decode(value, { stream: true }));
9648
10405
  }
9649
10406
  } catch {
9650
10407
  }
9651
10408
  }
9652
10409
  const fullResponse = chunks.join("");
10410
+ const elapsedMs = Date.now() - startTime;
10411
+ const totalStreamMs = elapsedMs;
9653
10412
  const mockLog = { ...log };
9654
10413
  const responseText = formatHandler.extractStream(fullResponse, mockLog, model, false);
9655
10414
  const tokens = formatHandler.extractTokens(responseText);
10415
+ const outputTokens = tokens.outputTokens ?? null;
10416
+ const tokensPerSecond = computeTokensPerSecond({
10417
+ outputTokens,
10418
+ firstChunkMs,
10419
+ totalStreamMs
10420
+ });
9656
10421
  const savedReplayLog = await addTestLogEntry({
9657
10422
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9658
10423
  method: log.method,
@@ -9663,10 +10428,13 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9663
10428
  responseStatus: upstreamRes.status,
9664
10429
  responseText,
9665
10430
  inputTokens: tokens.inputTokens ?? null,
9666
- outputTokens: tokens.outputTokens ?? null,
10431
+ outputTokens,
9667
10432
  cacheCreationInputTokens: tokens.cacheCreationInputTokens ?? null,
9668
10433
  cacheReadInputTokens: tokens.cacheReadInputTokens ?? null,
9669
10434
  elapsedMs,
10435
+ firstChunkMs,
10436
+ totalStreamMs,
10437
+ tokensPerSecond,
9670
10438
  streaming: true,
9671
10439
  userAgent: log.userAgent,
9672
10440
  origin: log.origin,
@@ -9691,11 +10459,15 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9691
10459
  inputTokens: tokens.inputTokens ?? void 0,
9692
10460
  outputTokens: tokens.outputTokens ?? void 0,
9693
10461
  elapsedMs,
10462
+ firstChunkMs,
10463
+ totalStreamMs,
10464
+ tokensPerSecond,
9694
10465
  streaming: true,
9695
10466
  replayLogId: savedReplayLog.id
9696
10467
  });
9697
10468
  } else {
9698
10469
  const responseText = await upstreamRes.text();
10470
+ const elapsedMs = Date.now() - startTime;
9699
10471
  const tokens = formatHandler.extractTokens(responseText);
9700
10472
  const savedReplayLog = await addTestLogEntry({
9701
10473
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -9711,6 +10483,9 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9711
10483
  cacheCreationInputTokens: tokens.cacheCreationInputTokens ?? null,
9712
10484
  cacheReadInputTokens: tokens.cacheReadInputTokens ?? null,
9713
10485
  elapsedMs,
10486
+ firstChunkMs: null,
10487
+ totalStreamMs: null,
10488
+ tokensPerSecond: null,
9714
10489
  streaming: false,
9715
10490
  userAgent: log.userAgent,
9716
10491
  origin: log.origin,
@@ -9743,7 +10518,7 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9743
10518
  }
9744
10519
  }
9745
10520
  });
9746
- const Route$4 = createFileRoute("/api/logs/$id/chunks")({
10521
+ const Route$6 = createFileRoute("/api/logs/$id/chunks")({
9747
10522
  server: {
9748
10523
  handlers: {
9749
10524
  GET: async ({ params }) => {
@@ -9778,7 +10553,7 @@ const CandidateUpdateSchema = object({
9778
10553
  object({
9779
10554
  candidate: KnowledgeCandidateSchema
9780
10555
  });
9781
- const Route$3 = createFileRoute("/api/knowledge/candidates/$candidateId")({
10556
+ const Route$5 = createFileRoute("/api/knowledge/candidates/$candidateId")({
9782
10557
  server: {
9783
10558
  handlers: {
9784
10559
  PATCH: async ({ params, request }) => {
@@ -9806,6 +10581,372 @@ const Route$3 = createFileRoute("/api/knowledge/candidates/$candidateId")({
9806
10581
  }
9807
10582
  }
9808
10583
  });
10584
+ async function readJsonBody$1(request) {
10585
+ try {
10586
+ const raw = await request.text();
10587
+ if (raw.trim().length === 0) return { ok: true, value: {} };
10588
+ return { ok: true, value: JSON.parse(raw) };
10589
+ } catch {
10590
+ return { ok: false };
10591
+ }
10592
+ }
10593
+ const Route$4 = createFileRoute("/api/groups/$groupId/sessions")({
10594
+ server: {
10595
+ handlers: {
10596
+ GET: ({ params }) => {
10597
+ const group = getGroup(params.groupId);
10598
+ if (group === null) {
10599
+ return Response.json({ error: "Group not found" }, { status: 404 });
10600
+ }
10601
+ return Response.json({ groupId: group.id, members: group.members });
10602
+ },
10603
+ POST: async ({ params, request }) => {
10604
+ const body = await readJsonBody$1(request);
10605
+ if (!body.ok) {
10606
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
10607
+ }
10608
+ const parsed = AddInspectorGroupSessionInputSchema.safeParse(body.value);
10609
+ if (!parsed.success) {
10610
+ return Response.json(
10611
+ { error: "Invalid request body", details: parsed.error.format() },
10612
+ { status: 400 }
10613
+ );
10614
+ }
10615
+ const updated = addGroupSession(params.groupId, parsed.data);
10616
+ if (updated === null) {
10617
+ return Response.json({ error: "Group not found" }, { status: 404 });
10618
+ }
10619
+ return Response.json(updated);
10620
+ }
10621
+ }
10622
+ }
10623
+ });
10624
+ function groupEvidenceDir(groupId) {
10625
+ return join(getDataDir(), "evidence", "groups", groupId);
10626
+ }
10627
+ function jsonPath(groupId) {
10628
+ return join(groupEvidenceDir(groupId), "evidence.json");
10629
+ }
10630
+ function markdownPath(groupId) {
10631
+ return join(groupEvidenceDir(groupId), "evidence.md");
10632
+ }
10633
+ function htmlPath(groupId) {
10634
+ return join(groupEvidenceDir(groupId), "evidence.html");
10635
+ }
10636
+ function escapeMarkdownCell(value) {
10637
+ return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
10638
+ }
10639
+ function escapeHtml(value) {
10640
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
10641
+ }
10642
+ function formatText(value) {
10643
+ if (value === null || value === void 0 || value.length === 0) return "n/a";
10644
+ return value;
10645
+ }
10646
+ function formatNumber(value) {
10647
+ if (value === null || value === void 0) return "n/a";
10648
+ return String(value);
10649
+ }
10650
+ function collectSortedValues(values) {
10651
+ const set = /* @__PURE__ */ new Set();
10652
+ for (const value of values) {
10653
+ if (value !== null && value !== void 0 && value.length > 0) {
10654
+ set.add(value);
10655
+ }
10656
+ }
10657
+ return [...set].sort();
10658
+ }
10659
+ function addTokenUsage(total, session) {
10660
+ if (session === null) return total;
10661
+ return {
10662
+ input: total.input + session.tokenUsage.input,
10663
+ output: total.output + session.tokenUsage.output,
10664
+ cacheCreate: total.cacheCreate + session.tokenUsage.cacheCreate,
10665
+ cacheRead: total.cacheRead + session.tokenUsage.cacheRead,
10666
+ total: total.total + session.tokenUsage.total
10667
+ };
10668
+ }
10669
+ function memberEffectiveStatus(member) {
10670
+ if (member.member.status !== null) return member.member.status;
10671
+ if (member.run !== null) return member.run.status;
10672
+ return member.session?.status ?? "unknown";
10673
+ }
10674
+ function isCompletedMember(member) {
10675
+ const status = memberEffectiveStatus(member);
10676
+ return status === "completed";
10677
+ }
10678
+ function isFailedMember(member) {
10679
+ const status = memberEffectiveStatus(member);
10680
+ return status === "failed" || status === "cancelled";
10681
+ }
10682
+ function isRunningMember(member) {
10683
+ const status = memberEffectiveStatus(member);
10684
+ return status === "running" || status === "active";
10685
+ }
10686
+ function latestLogMetric(session, field) {
10687
+ if (session === null) return null;
10688
+ for (const log of session.latestLogs) {
10689
+ const value = log[field];
10690
+ if (value !== void 0 && value !== null) return value;
10691
+ }
10692
+ return null;
10693
+ }
10694
+ function buildSummary(members) {
10695
+ const tokenUsage = members.reduce(
10696
+ (total, member) => addTokenUsage(total, member.session),
10697
+ { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, total: 0 }
10698
+ );
10699
+ const sessionsWithData = members.filter((member) => member.session !== null).length;
10700
+ const requestCount = members.reduce(
10701
+ (total, member) => total + (member.session?.requestCount ?? 0),
10702
+ 0
10703
+ );
10704
+ const errorCount = members.reduce(
10705
+ (total, member) => total + (member.session?.errorCount ?? 0),
10706
+ 0
10707
+ );
10708
+ return {
10709
+ memberCount: members.length,
10710
+ sessionsWithData,
10711
+ missingSessions: members.length - sessionsWithData,
10712
+ requestCount,
10713
+ errorCount,
10714
+ tokenUsage,
10715
+ models: collectSortedValues(
10716
+ members.flatMap((member) => [member.member.model, ...member.session?.models ?? []])
10717
+ ),
10718
+ providers: collectSortedValues(
10719
+ members.flatMap((member) => [member.member.provider, ...member.session?.providers ?? []])
10720
+ ),
10721
+ completedMembers: members.filter(isCompletedMember).length,
10722
+ failedMembers: members.filter(isFailedMember).length,
10723
+ runningMembers: members.filter(isRunningMember).length
10724
+ };
10725
+ }
10726
+ function buildJenkinsReport(group, summary) {
10727
+ if (summary.failedMembers > 0 || summary.errorCount > 0 || group.status === "failed") {
10728
+ return {
10729
+ status: "FAIL",
10730
+ summary: `[FAIL] ${summary.failedMembers} failed members, ${summary.errorCount} captured errors.`,
10731
+ markdown: `# Jenkins Report: ${group.title}
10732
+
10733
+ Status: FAIL
10734
+
10735
+ ${summary.failedMembers} failed members and ${summary.errorCount} captured errors need triage.`
10736
+ };
10737
+ }
10738
+ if (summary.runningMembers > 0 || group.status === "running") {
10739
+ return {
10740
+ status: "RUNNING",
10741
+ summary: `[RUNNING] ${summary.runningMembers} group members are still active.`,
10742
+ markdown: `# Jenkins Report: ${group.title}
10743
+
10744
+ Status: RUNNING
10745
+
10746
+ ${summary.runningMembers} group members are still active.`
10747
+ };
10748
+ }
10749
+ if (summary.memberCount > 0 && summary.sessionsWithData > 0 && summary.errorCount === 0 && group.status === "completed") {
10750
+ return {
10751
+ status: "PASS",
10752
+ summary: `[PASS] ${summary.memberCount} group members completed with no captured errors.`,
10753
+ markdown: `# Jenkins Report: ${group.title}
10754
+
10755
+ Status: PASS
10756
+
10757
+ ${summary.memberCount} group members completed with no captured errors.`
10758
+ };
10759
+ }
10760
+ return {
10761
+ status: "UNKNOWN",
10762
+ summary: `[UNKNOWN] Group has ${summary.memberCount} members and ${summary.missingSessions} missing sessions.`,
10763
+ markdown: `# Jenkins Report: ${group.title}
10764
+
10765
+ Status: UNKNOWN
10766
+
10767
+ Exported group evidence, but the outcome is not conclusive yet.`
10768
+ };
10769
+ }
10770
+ function buildMemberRows(members) {
10771
+ if (members.length === 0) return ["No sessions have been attached to this group.", ""];
10772
+ const rows = [
10773
+ "| Label | Provider | Model | Session | Run | Status | Requests | Errors | Tokens | First Chunk | Stream Total | TPS | Inspector |",
10774
+ "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"
10775
+ ];
10776
+ for (const member of members) {
10777
+ const session = member.session;
10778
+ rows.push(
10779
+ [
10780
+ formatText(member.member.label),
10781
+ formatText(member.member.provider ?? session?.providers[0]),
10782
+ formatText(member.member.model ?? session?.lastModel),
10783
+ member.member.sessionId,
10784
+ formatText(member.member.runId),
10785
+ memberEffectiveStatus(member),
10786
+ formatNumber(session?.requestCount),
10787
+ formatNumber(session?.errorCount),
10788
+ formatNumber(session?.tokenUsage.total),
10789
+ formatNumber(latestLogMetric(session, "firstChunkMs")),
10790
+ formatNumber(latestLogMetric(session, "totalStreamMs")),
10791
+ formatNumber(latestLogMetric(session, "tokensPerSecond")),
10792
+ formatText(session?.inspectorUrl)
10793
+ ].map(escapeMarkdownCell).join(" | ").replace(/^/, "| ").replace(/$/, " |")
10794
+ );
10795
+ }
10796
+ rows.push("");
10797
+ return rows;
10798
+ }
10799
+ function buildMarkdown(document) {
10800
+ const group = document.group;
10801
+ const summary = document.summary;
10802
+ const lines = [
10803
+ `# ${group.title}`,
10804
+ "",
10805
+ `- Group ID: ${group.id}`,
10806
+ `- Kind: ${group.kind}`,
10807
+ `- Status: ${group.status}`,
10808
+ `- Project: ${group.project ?? "n/a"}`,
10809
+ `- Created: ${group.createdAt}`,
10810
+ `- Members: ${summary.memberCount}`,
10811
+ `- Sessions With Data: ${summary.sessionsWithData}`,
10812
+ `- Missing Sessions: ${summary.missingSessions}`,
10813
+ `- Requests: ${summary.requestCount}`,
10814
+ `- Errors: ${summary.errorCount}`,
10815
+ `- Tokens: ${summary.tokenUsage.total}`,
10816
+ `- Models: ${summary.models.length > 0 ? summary.models.join(", ") : "n/a"}`,
10817
+ `- Providers: ${summary.providers.length > 0 ? summary.providers.join(", ") : "n/a"}`,
10818
+ `- Evidence JSON: ${document.evidence.jsonPath}`,
10819
+ `- Evidence HTML: ${document.evidence.htmlPath}`,
10820
+ ""
10821
+ ];
10822
+ if (group.task !== null) {
10823
+ lines.push("## Task", "", group.task, "");
10824
+ }
10825
+ lines.push("## Evaluation Matrix", "", ...buildMemberRows(document.members));
10826
+ lines.push("## Jenkins Report", "", document.jenkinsReport.markdown, "");
10827
+ return `${lines.join("\n")}
10828
+ `;
10829
+ }
10830
+ function buildHtml(markdown, title) {
10831
+ return `<!doctype html>
10832
+ <html lang="en">
10833
+ <head>
10834
+ <meta charset="utf-8">
10835
+ <title>${escapeHtml(title)}</title>
10836
+ <style>
10837
+ body { font-family: system-ui, sans-serif; max-width: 1120px; margin: 40px auto; line-height: 1.5; }
10838
+ pre { white-space: pre-wrap; background: #f6f8fa; padding: 16px; border-radius: 8px; }
10839
+ </style>
10840
+ </head>
10841
+ <body>
10842
+ <pre>${escapeHtml(markdown)}</pre>
10843
+ </body>
10844
+ </html>
10845
+ `;
10846
+ }
10847
+ async function buildExportMembers(group, options, baseUrl) {
10848
+ const members = [];
10849
+ for (const member of group.members) {
10850
+ const run = member.runId === null ? null : getRun(member.runId);
10851
+ const session = await getSessionInfo(member.sessionId, {
10852
+ baseUrl,
10853
+ includeHistory: options?.includeHistory,
10854
+ latestLogLimit: options?.latestLogLimit
10855
+ });
10856
+ members.push({ member, run, session });
10857
+ }
10858
+ return members;
10859
+ }
10860
+ async function exportGroupEvidence(group, options, baseUrl) {
10861
+ const members = await buildExportMembers(group, options, baseUrl);
10862
+ const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
10863
+ const evidence = {
10864
+ jsonPath: jsonPath(group.id),
10865
+ markdownPath: markdownPath(group.id),
10866
+ htmlPath: htmlPath(group.id),
10867
+ exportedAt
10868
+ };
10869
+ const documentGroup = {
10870
+ ...group,
10871
+ evidence,
10872
+ updatedAt: exportedAt
10873
+ };
10874
+ const summary = buildSummary(members);
10875
+ const jenkinsReport = buildJenkinsReport(documentGroup, summary);
10876
+ const document = {
10877
+ group: documentGroup,
10878
+ evidence,
10879
+ members,
10880
+ summary,
10881
+ jenkinsReport
10882
+ };
10883
+ const markdown = buildMarkdown(document);
10884
+ mkdirSync(groupEvidenceDir(group.id), { recursive: true });
10885
+ writeFileSync(evidence.jsonPath, `${JSON.stringify(document, null, 2)}
10886
+ `, "utf8");
10887
+ writeFileSync(evidence.markdownPath, markdown, "utf8");
10888
+ writeFileSync(evidence.htmlPath, buildHtml(markdown, group.title), "utf8");
10889
+ const updatedGroup = updateGroupEvidence(group.id, evidence) ?? documentGroup;
10890
+ return { ...document, group: updatedGroup };
10891
+ }
10892
+ function readGroupEvidenceMarkdown(group) {
10893
+ const path2 = group.evidence?.markdownPath;
10894
+ if (path2 === void 0 || !existsSync(path2)) return null;
10895
+ try {
10896
+ return readFileSync(path2, "utf8");
10897
+ } catch {
10898
+ return null;
10899
+ }
10900
+ }
10901
+ async function readJsonBody(request) {
10902
+ try {
10903
+ const raw = await request.text();
10904
+ if (raw.trim().length === 0) return { ok: true, value: void 0 };
10905
+ return { ok: true, value: JSON.parse(raw) };
10906
+ } catch {
10907
+ return { ok: false };
10908
+ }
10909
+ }
10910
+ const Route$3 = createFileRoute("/api/groups/$groupId/evidence")({
10911
+ server: {
10912
+ handlers: {
10913
+ GET: ({ params }) => {
10914
+ const group = getGroup(params.groupId);
10915
+ if (group === null) {
10916
+ return Response.json({ error: "Group not found" }, { status: 404 });
10917
+ }
10918
+ const markdown = readGroupEvidenceMarkdown(group);
10919
+ if (group.evidence === null || markdown === null) {
10920
+ return Response.json(
10921
+ { error: "Evidence has not been exported for this group" },
10922
+ { status: 404 }
10923
+ );
10924
+ }
10925
+ return Response.json({ group, evidence: group.evidence, markdown });
10926
+ },
10927
+ POST: async ({ params, request }) => {
10928
+ const group = getGroup(params.groupId);
10929
+ if (group === null) {
10930
+ return Response.json({ error: "Group not found" }, { status: 404 });
10931
+ }
10932
+ const body = await readJsonBody(request);
10933
+ if (!body.ok) {
10934
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
10935
+ }
10936
+ const parsed = GroupEvidenceExportOptionsSchema.safeParse(body.value);
10937
+ if (!parsed.success) {
10938
+ return Response.json(
10939
+ { error: "Invalid request body", details: parsed.error.format() },
10940
+ { status: 400 }
10941
+ );
10942
+ }
10943
+ return Response.json(
10944
+ await exportGroupEvidence(group, parsed.data, new URL(request.url).origin)
10945
+ );
10946
+ }
10947
+ }
10948
+ }
10949
+ });
9809
10950
  const ProviderTestErrorTypeSchema = _enum([
9810
10951
  "timeout",
9811
10952
  "network_unreachable",
@@ -9846,6 +10987,9 @@ const ProviderTestResultSchema = object({
9846
10987
  cacheCreationInputTokens: number().optional(),
9847
10988
  cacheReadInputTokens: number().optional(),
9848
10989
  latencyMs: number().optional(),
10990
+ firstChunkMs: number().nullable().optional(),
10991
+ totalStreamMs: number().nullable().optional(),
10992
+ tokensPerSecond: number().nullable().optional(),
9849
10993
  content: array(ProviderTestContentSchema).optional(),
9850
10994
  rawResponse: string().optional(),
9851
10995
  streaming: boolean().optional(),
@@ -9915,6 +11059,19 @@ function getResultResponseText(result) {
9915
11059
  function getResultHeaders(result) {
9916
11060
  return result.requestHeaders ?? result.debug?.requestHeaders ?? {};
9917
11061
  }
11062
+ function getResultTotalStreamMs(result) {
11063
+ return result.totalStreamMs ?? (result.streaming === true ? result.latencyMs ?? null : null);
11064
+ }
11065
+ function getResultFirstChunkMs(result) {
11066
+ return result.firstChunkMs ?? firstChunkMsFromChunks(result.streamingChunks?.chunks);
11067
+ }
11068
+ function getResultTokensPerSecond(result) {
11069
+ return result.tokensPerSecond ?? computeTokensPerSecond({
11070
+ outputTokens: result.outputTokens,
11071
+ firstChunkMs: getResultFirstChunkMs(result),
11072
+ totalStreamMs: getResultTotalStreamMs(result)
11073
+ });
11074
+ }
9918
11075
  async function logModelResults(displayName, providerName, anthropicUrl, openaiUrl, modelResults, captureFullDetails) {
9919
11076
  const usageModel = getModelUsageName(displayName, providerName);
9920
11077
  if (anthropicUrl !== void 0) {
@@ -9938,6 +11095,9 @@ async function logModelResults(displayName, providerName, anthropicUrl, openaiUr
9938
11095
  cacheCreationInputTokens: nonStreamingResult.cacheCreationInputTokens ?? null,
9939
11096
  cacheReadInputTokens: nonStreamingResult.cacheReadInputTokens ?? null,
9940
11097
  elapsedMs: nonStreamingResult.latencyMs ?? 0,
11098
+ firstChunkMs: null,
11099
+ totalStreamMs: null,
11100
+ tokensPerSecond: null,
9941
11101
  streaming: false,
9942
11102
  userAgent: "provider-test",
9943
11103
  origin: null,
@@ -9962,7 +11122,10 @@ async function logModelResults(displayName, providerName, anthropicUrl, openaiUr
9962
11122
  outputTokens: streamingResult.outputTokens ?? null,
9963
11123
  cacheCreationInputTokens: streamingResult.cacheCreationInputTokens ?? null,
9964
11124
  cacheReadInputTokens: streamingResult.cacheReadInputTokens ?? null,
9965
- elapsedMs: streamingResult.latencyMs ?? 0,
11125
+ elapsedMs: getResultTotalStreamMs(streamingResult) ?? 0,
11126
+ firstChunkMs: getResultFirstChunkMs(streamingResult),
11127
+ totalStreamMs: getResultTotalStreamMs(streamingResult),
11128
+ tokensPerSecond: getResultTokensPerSecond(streamingResult),
9966
11129
  streaming: true,
9967
11130
  streamingChunks: captureFullDetails ? streamingResult.streamingChunks : void 0,
9968
11131
  userAgent: "provider-test",
@@ -9995,6 +11158,9 @@ async function logModelResults(displayName, providerName, anthropicUrl, openaiUr
9995
11158
  cacheCreationInputTokens: null,
9996
11159
  cacheReadInputTokens: null,
9997
11160
  elapsedMs: nonStreamingResult.latencyMs ?? 0,
11161
+ firstChunkMs: null,
11162
+ totalStreamMs: null,
11163
+ tokensPerSecond: null,
9998
11164
  streaming: false,
9999
11165
  userAgent: "provider-test",
10000
11166
  origin: null,
@@ -10019,7 +11185,10 @@ async function logModelResults(displayName, providerName, anthropicUrl, openaiUr
10019
11185
  outputTokens: streamingResult.outputTokens ?? null,
10020
11186
  cacheCreationInputTokens: null,
10021
11187
  cacheReadInputTokens: null,
10022
- elapsedMs: streamingResult.latencyMs ?? 0,
11188
+ elapsedMs: getResultTotalStreamMs(streamingResult) ?? 0,
11189
+ firstChunkMs: getResultFirstChunkMs(streamingResult),
11190
+ totalStreamMs: getResultTotalStreamMs(streamingResult),
11191
+ tokensPerSecond: getResultTokensPerSecond(streamingResult),
10023
11192
  streaming: true,
10024
11193
  streamingChunks: captureFullDetails ? streamingResult.streamingChunks : void 0,
10025
11194
  userAgent: "provider-test",
@@ -10201,146 +11370,166 @@ const Route = createFileRoute("/api/knowledge/candidates/$candidateId/promote")(
10201
11370
  }
10202
11371
  }
10203
11372
  });
10204
- const IndexRoute = Route$u.update({
11373
+ const IndexRoute = Route$y.update({
10205
11374
  id: "/",
10206
11375
  path: "/",
10207
- getParentRoute: () => Route$v
11376
+ getParentRoute: () => Route$z
10208
11377
  });
10209
- const SessionSessionIdRoute = Route$t.update({
11378
+ const SessionSessionIdRoute = Route$x.update({
10210
11379
  id: "/session/$sessionId",
10211
11380
  path: "/session/$sessionId",
10212
- getParentRoute: () => Route$v
11381
+ getParentRoute: () => Route$z
10213
11382
  });
10214
- const ProxySplatRoute = Route$s.update({
11383
+ const ProxySplatRoute = Route$w.update({
10215
11384
  id: "/proxy/$",
10216
11385
  path: "/proxy/$",
10217
- getParentRoute: () => Route$v
11386
+ getParentRoute: () => Route$z
10218
11387
  });
10219
- const ApiSessionsRoute = Route$r.update({
11388
+ const ApiSessionsRoute = Route$v.update({
10220
11389
  id: "/api/sessions",
10221
11390
  path: "/api/sessions",
10222
- getParentRoute: () => Route$v
11391
+ getParentRoute: () => Route$z
10223
11392
  });
10224
- const ApiRunsRoute = Route$q.update({
11393
+ const ApiRunsRoute = Route$u.update({
10225
11394
  id: "/api/runs",
10226
11395
  path: "/api/runs",
10227
- getParentRoute: () => Route$v
11396
+ getParentRoute: () => Route$z
10228
11397
  });
10229
- const ApiProvidersRoute = Route$p.update({
11398
+ const ApiProvidersRoute = Route$t.update({
10230
11399
  id: "/api/providers",
10231
11400
  path: "/api/providers",
10232
- getParentRoute: () => Route$v
11401
+ getParentRoute: () => Route$z
10233
11402
  });
10234
- const ApiModelsRoute = Route$o.update({
11403
+ const ApiModelsRoute = Route$s.update({
10235
11404
  id: "/api/models",
10236
11405
  path: "/api/models",
10237
- getParentRoute: () => Route$v
11406
+ getParentRoute: () => Route$z
10238
11407
  });
10239
- const ApiMcpRoute = Route$n.update({
11408
+ const ApiMcpRoute = Route$r.update({
10240
11409
  id: "/api/mcp",
10241
11410
  path: "/api/mcp",
10242
- getParentRoute: () => Route$v
11411
+ getParentRoute: () => Route$z
10243
11412
  });
10244
- const ApiLogsRoute = Route$m.update({
11413
+ const ApiLogsRoute = Route$q.update({
10245
11414
  id: "/api/logs",
10246
11415
  path: "/api/logs",
10247
- getParentRoute: () => Route$v
11416
+ getParentRoute: () => Route$z
10248
11417
  });
10249
- const ApiHealthRoute = Route$l.update({
11418
+ const ApiHealthRoute = Route$p.update({
10250
11419
  id: "/api/health",
10251
11420
  path: "/api/health",
10252
- getParentRoute: () => Route$v
11421
+ getParentRoute: () => Route$z
11422
+ });
11423
+ const ApiGroupsRoute = Route$o.update({
11424
+ id: "/api/groups",
11425
+ path: "/api/groups",
11426
+ getParentRoute: () => Route$z
10253
11427
  });
10254
- const ApiConfigRoute = Route$k.update({
11428
+ const ApiConfigRoute = Route$n.update({
10255
11429
  id: "/api/config",
10256
11430
  path: "/api/config",
10257
- getParentRoute: () => Route$v
11431
+ getParentRoute: () => Route$z
10258
11432
  });
10259
- const ApiRunsRunIdRoute = Route$j.update({
11433
+ const ApiRunsRunIdRoute = Route$m.update({
10260
11434
  id: "/$runId",
10261
11435
  path: "/$runId",
10262
11436
  getParentRoute: () => ApiRunsRoute
10263
11437
  });
10264
- const ApiProvidersScanRoute = Route$i.update({
11438
+ const ApiProvidersScanRoute = Route$l.update({
10265
11439
  id: "/scan",
10266
11440
  path: "/scan",
10267
11441
  getParentRoute: () => ApiProvidersRoute
10268
11442
  });
10269
- const ApiProvidersImportRoute = Route$h.update({
11443
+ const ApiProvidersImportRoute = Route$k.update({
10270
11444
  id: "/import",
10271
11445
  path: "/import",
10272
11446
  getParentRoute: () => ApiProvidersRoute
10273
11447
  });
10274
- const ApiProvidersExportRoute = Route$g.update({
11448
+ const ApiProvidersExportRoute = Route$j.update({
10275
11449
  id: "/export",
10276
11450
  path: "/export",
10277
11451
  getParentRoute: () => ApiProvidersRoute
10278
11452
  });
10279
- const ApiProvidersProviderIdRoute = Route$f.update({
11453
+ const ApiProvidersProviderIdRoute = Route$i.update({
10280
11454
  id: "/$providerId",
10281
11455
  path: "/$providerId",
10282
11456
  getParentRoute: () => ApiProvidersRoute
10283
11457
  });
10284
- const ApiLogsStreamRoute = Route$e.update({
11458
+ const ApiLogsStreamRoute = Route$h.update({
10285
11459
  id: "/stream",
10286
11460
  path: "/stream",
10287
11461
  getParentRoute: () => ApiLogsRoute
10288
11462
  });
10289
- const ApiLogsIdRoute = Route$d.update({
11463
+ const ApiLogsIdRoute = Route$g.update({
10290
11464
  id: "/$id",
10291
11465
  path: "/$id",
10292
11466
  getParentRoute: () => ApiLogsRoute
10293
11467
  });
10294
- const ApiKnowledgeSearchRoute = Route$c.update({
11468
+ const ApiKnowledgeSearchRoute = Route$f.update({
10295
11469
  id: "/api/knowledge/search",
10296
11470
  path: "/api/knowledge/search",
10297
- getParentRoute: () => Route$v
11471
+ getParentRoute: () => Route$z
10298
11472
  });
10299
- const ApiKnowledgeProjectContextRoute = Route$b.update({
11473
+ const ApiKnowledgeProjectContextRoute = Route$e.update({
10300
11474
  id: "/api/knowledge/project-context",
10301
11475
  path: "/api/knowledge/project-context",
10302
- getParentRoute: () => Route$v
11476
+ getParentRoute: () => Route$z
10303
11477
  });
10304
- const ApiKnowledgeCandidatesRoute = Route$a.update({
11478
+ const ApiKnowledgeCandidatesRoute = Route$d.update({
10305
11479
  id: "/api/knowledge/candidates",
10306
11480
  path: "/api/knowledge/candidates",
10307
- getParentRoute: () => Route$v
11481
+ getParentRoute: () => Route$z
11482
+ });
11483
+ const ApiGroupsGroupIdRoute = Route$c.update({
11484
+ id: "/$groupId",
11485
+ path: "/$groupId",
11486
+ getParentRoute: () => ApiGroupsRoute
10308
11487
  });
10309
- const ApiConfigPathsRoute = Route$9.update({
11488
+ const ApiConfigPathsRoute = Route$b.update({
10310
11489
  id: "/paths",
10311
11490
  path: "/paths",
10312
11491
  getParentRoute: () => ApiConfigRoute
10313
11492
  });
10314
- const ApiRunsRunIdEvidenceRoute = Route$8.update({
11493
+ const ApiRunsRunIdEvidenceRoute = Route$a.update({
10315
11494
  id: "/evidence",
10316
11495
  path: "/evidence",
10317
11496
  getParentRoute: () => ApiRunsRunIdRoute
10318
11497
  });
10319
- const ApiProvidersProviderIdTestRoute = Route$7.update({
11498
+ const ApiProvidersProviderIdTestRoute = Route$9.update({
10320
11499
  id: "/test",
10321
11500
  path: "/test",
10322
11501
  getParentRoute: () => ApiProvidersProviderIdRoute
10323
11502
  });
10324
- const ApiProvidersProviderIdModelMetadataRoute = Route$6.update({
11503
+ const ApiProvidersProviderIdModelMetadataRoute = Route$8.update({
10325
11504
  id: "/model-metadata",
10326
11505
  path: "/model-metadata",
10327
11506
  getParentRoute: () => ApiProvidersProviderIdRoute
10328
11507
  });
10329
- const ApiLogsIdReplayRoute = Route$5.update({
11508
+ const ApiLogsIdReplayRoute = Route$7.update({
10330
11509
  id: "/replay",
10331
11510
  path: "/replay",
10332
11511
  getParentRoute: () => ApiLogsIdRoute
10333
11512
  });
10334
- const ApiLogsIdChunksRoute = Route$4.update({
11513
+ const ApiLogsIdChunksRoute = Route$6.update({
10335
11514
  id: "/chunks",
10336
11515
  path: "/chunks",
10337
11516
  getParentRoute: () => ApiLogsIdRoute
10338
11517
  });
10339
- const ApiKnowledgeCandidatesCandidateIdRoute = Route$3.update({
11518
+ const ApiKnowledgeCandidatesCandidateIdRoute = Route$5.update({
10340
11519
  id: "/$candidateId",
10341
11520
  path: "/$candidateId",
10342
11521
  getParentRoute: () => ApiKnowledgeCandidatesRoute
10343
11522
  });
11523
+ const ApiGroupsGroupIdSessionsRoute = Route$4.update({
11524
+ id: "/sessions",
11525
+ path: "/sessions",
11526
+ getParentRoute: () => ApiGroupsGroupIdRoute
11527
+ });
11528
+ const ApiGroupsGroupIdEvidenceRoute = Route$3.update({
11529
+ id: "/evidence",
11530
+ path: "/evidence",
11531
+ getParentRoute: () => ApiGroupsGroupIdRoute
11532
+ });
10344
11533
  const ApiProvidersProviderIdTestLogRoute = Route$2.update({
10345
11534
  id: "/log",
10346
11535
  path: "/log",
@@ -10349,7 +11538,7 @@ const ApiProvidersProviderIdTestLogRoute = Route$2.update({
10349
11538
  const ApiKnowledgeSessionsSessionIdCandidatesRoute = Route$1.update({
10350
11539
  id: "/api/knowledge/sessions/$sessionId/candidates",
10351
11540
  path: "/api/knowledge/sessions/$sessionId/candidates",
10352
- getParentRoute: () => Route$v
11541
+ getParentRoute: () => Route$z
10353
11542
  });
10354
11543
  const ApiKnowledgeCandidatesCandidateIdPromoteRoute = Route.update({
10355
11544
  id: "/promote",
@@ -10362,6 +11551,17 @@ const ApiConfigRouteChildren = {
10362
11551
  const ApiConfigRouteWithChildren = ApiConfigRoute._addFileChildren(
10363
11552
  ApiConfigRouteChildren
10364
11553
  );
11554
+ const ApiGroupsGroupIdRouteChildren = {
11555
+ ApiGroupsGroupIdEvidenceRoute,
11556
+ ApiGroupsGroupIdSessionsRoute
11557
+ };
11558
+ const ApiGroupsGroupIdRouteWithChildren = ApiGroupsGroupIdRoute._addFileChildren(ApiGroupsGroupIdRouteChildren);
11559
+ const ApiGroupsRouteChildren = {
11560
+ ApiGroupsGroupIdRoute: ApiGroupsGroupIdRouteWithChildren
11561
+ };
11562
+ const ApiGroupsRouteWithChildren = ApiGroupsRoute._addFileChildren(
11563
+ ApiGroupsRouteChildren
11564
+ );
10365
11565
  const ApiLogsIdRouteChildren = {
10366
11566
  ApiLogsIdChunksRoute,
10367
11567
  ApiLogsIdReplayRoute
@@ -10421,6 +11621,7 @@ const ApiKnowledgeCandidatesRouteWithChildren = ApiKnowledgeCandidatesRoute._add
10421
11621
  const rootRouteChildren = {
10422
11622
  IndexRoute,
10423
11623
  ApiConfigRoute: ApiConfigRouteWithChildren,
11624
+ ApiGroupsRoute: ApiGroupsRouteWithChildren,
10424
11625
  ApiHealthRoute,
10425
11626
  ApiLogsRoute: ApiLogsRouteWithChildren,
10426
11627
  ApiMcpRoute,
@@ -10435,7 +11636,7 @@ const rootRouteChildren = {
10435
11636
  ApiKnowledgeSearchRoute,
10436
11637
  ApiKnowledgeSessionsSessionIdCandidatesRoute
10437
11638
  };
10438
- const routeTree = Route$v._addFileChildren(rootRouteChildren)._addFileTypes();
11639
+ const routeTree = Route$z._addFileChildren(rootRouteChildren)._addFileTypes();
10439
11640
  function getRouter() {
10440
11641
  const router2 = createRouter({
10441
11642
  routeTree,
@@ -10455,7 +11656,7 @@ export {
10455
11656
  MAX_SLOW_RESPONSE_THRESHOLD_SECONDS as M,
10456
11657
  OpenAIRequestSchema as O,
10457
11658
  ProviderConfigSchema as P,
10458
- Route$t as R,
11659
+ Route$x as R,
10459
11660
  TimeDisplayFormatSchema as T,
10460
11661
  DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS as a,
10461
11662
  DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS as b,